From 1a594616e41704cbc9981e7aa06a4822e82103bd Mon Sep 17 00:00:00 2001 From: Mahim Date: Wed, 1 Jul 2026 10:03:54 -0400 Subject: [PATCH 1/4] feat: mongoose --- Makefile | 13 +- lib/mongoose/mongoose.c | 31627 ++++++++++++++++++++++++++++++++++++++ lib/mongoose/mongoose.h | 5746 +++++++ run.sh | 5 +- src/gui/web/web_main.c | 58 + 5 files changed, 37446 insertions(+), 3 deletions(-) create mode 100644 lib/mongoose/mongoose.c create mode 100644 lib/mongoose/mongoose.h create mode 100644 src/gui/web/web_main.c diff --git a/Makefile b/Makefile index deb91d1..271114f 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ CC = gcc -CFLAGS = -Wall -Wextra -Wpedantic -I. -Iinclude +CFLAGS = -Wall -Wextra -Wpedantic -I. -Iinclude -Ilib/mongoose SRC = $(wildcard src/*.c) \ $(wildcard src/**/*.c) \ $(wildcard src/modules/datastructures/*.c) \ @@ -9,6 +9,9 @@ SRC = $(wildcard src/*.c) \ SRC := $(filter-out src/modules/builtin.c, $(SRC)) OUT = shell +WEB_SRC = src/gui/web/web_main.c +WEB_OUT = moss-web + TEST_SRC = $(wildcard tests/*.c) TEST_BINS = $(TEST_SRC:.c=) TEST_LIBS = $(filter-out src/main.c, $(SRC)) @@ -18,6 +21,11 @@ all: $(OUT) $(OUT): $(SRC) $(CC) $(CFLAGS) $(SRC) -o $(OUT) +web: $(WEB_OUT) + +$(WEB_OUT): $(WEB_SRC) + $(CC) $(CFLAGS) $(WEB_SRC) lib/mongoose/mongoose.c -o $(WEB_OUT) -lws2_32 + test: $(TEST_BINS) @echo "Running all tests..." @for test in $(TEST_BINS); do\ @@ -28,11 +36,12 @@ test: $(TEST_BINS) $(TEST_BINS): tests/%: tests/%.c $(TEST_LIBS) $(CC) $(CFLAGS) $< $(TEST_LIBS) -o $@ -lcmocka -.PHONY: all test clean scan check-mem +.PHONY: all test clean scan check-mem web clean: rm -f $(OUT) rm -f $(TEST_BINS) + rm -f $(WEB_OUT) scan: cppcheck --enable=all --force -Iinclude src/ diff --git a/lib/mongoose/mongoose.c b/lib/mongoose/mongoose.c new file mode 100644 index 0000000..b9af66e --- /dev/null +++ b/lib/mongoose/mongoose.c @@ -0,0 +1,31627 @@ +// Copyright (c) 2004-2013 Sergey Lyubka +// Copyright (c) 2013-2025 Cesanta Software Limited +// All rights reserved +// +// This software is dual-licensed: you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. For the terms of this +// license, see http://www.gnu.org/licenses/ +// +// You are free to use this software under the terms of the GNU General +// Public License, but WITHOUT ANY WARRANTY; without even the implied +// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// Alternatively, you can license this software under a commercial +// license, as set out in https://www.mongoose.ws/licensing/ +// +// SPDX-License-Identifier: GPL-2.0-only or commercial + +#include "mongoose.h" + +#ifdef MG_ENABLE_LINES +#line 1 "src/base64.c" +#endif + + +static int mg_base64_encode_single(int c) { + if (c < 26) { + return c + 'A'; + } else if (c < 52) { + return c - 26 + 'a'; + } else if (c < 62) { + return c - 52 + '0'; + } else { + return c == 62 ? '+' : '/'; + } +} + +static int mg_base64_decode_single(int c) { + if (c >= 'A' && c <= 'Z') { + return c - 'A'; + } else if (c >= 'a' && c <= 'z') { + return c + 26 - 'a'; + } else if (c >= '0' && c <= '9') { + return c + 52 - '0'; + } else if (c == '+') { + return 62; + } else if (c == '/') { + return 63; + } else if (c == '=') { + return 64; + } else { + return -1; + } +} + +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_base64_encode_single(ch >> 2); + to[++n] = (char) ((ch & 3) << 4); + } else if (rem == 1) { + to[n] = (char) mg_base64_encode_single(to[n] | (ch >> 4)); + to[++n] = (char) ((ch & 15) << 2); + } else { + to[n] = (char) mg_base64_encode_single(to[n] | (ch >> 6)); + to[++n] = (char) mg_base64_encode_single(ch & 63); + n++; + } + return 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--; + // printf(" %d[%.*s]\n", n, n, to); + while (n & 3) to[n++] = '='; + to[n] = '\0'; + return n; +} + +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; +} + +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 + size_t len = 0; + if (dl < n / 4 * 3 + 1) goto fail; + while (src != NULL && src + 3 < end) { + 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)); + if (src[3] != '=') dst[len++] = (char) ((c << 6) | d); + } + src += 4; + } + dst[len] = '\0'; + return len; +fail: + if (dl > 0) dst[0] = '\0'; + return 0; +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/bsd.c" +#endif + + +#if MG_ENABLE_BSD_SOCKETS + +struct mg_bsd_sock { + void *t; // opaque transport handle + int fd; + int domain, type, proto; + bool nonblock; + struct sockaddr_in addr; // bind address + struct sockaddr_in peer; // peer address (after accept/connect) + struct mg_bsd_sock *next; +}; + +#define MG_BSD_FD_BASE 17777 + +// static struct mg_mgr *s_mgr; +static struct mg_bsd_sock *s_socks; + +static struct mg_bsd_sock *get(int fd) { + if (fd < MG_BSD_FD_BASE) return NULL; + for (struct mg_bsd_sock *s = s_socks; s; s = s->next) + if (s->fd == fd) return s; + return NULL; +} + +static int alloc_sock(struct mg_bsd_sock *s) { + for (int fd = MG_BSD_FD_BASE; ; fd++) { + if (get(fd) == NULL) { s->fd = fd; break; } + } + s->next = s_socks; + s_socks = s; + return s->fd; +} + +static void release_sock(int fd) { + struct mg_bsd_sock **p = &s_socks; + while (*p && (*p)->fd != fd) p = &(*p)->next; + if (*p) *p = (*p)->next; +} + + +int socket(int domain, int type, int proto) { + struct mg_bsd_sock *s = (struct mg_bsd_sock *) calloc(1, sizeof(*s)); + if (!s) { errno = ENOMEM; return -1; } + s->t = mg_bsd_transport_new(domain, type, proto); + if (!s->t || alloc_sock(s) < 0) { free(s); errno = ENOMEM; return -1; } + s->domain = domain; s->type = type; s->proto = proto; + return s->fd; +} + +int bind(int fd, const struct sockaddr *addr, socklen_t len) { + struct mg_bsd_sock *s = get(fd); + if (!s) return -1; + memcpy(&s->addr, addr, len < sizeof(s->addr) ? len : sizeof(s->addr)); + return 0; +} + +int listen(int fd, int backlog) { + struct mg_bsd_sock *s = get(fd); + if (!s) return -1; + (void) backlog; + return mg_bsd_transport_listen(s->t, &s->addr); +} + +int accept(int fd, struct sockaddr *addr, socklen_t *addrlen) { + struct mg_bsd_sock *ls = get(fd); + if (!ls) return -1; + struct sockaddr_in peer = {0}; + void *t = mg_bsd_transport_accept(ls->t, &peer, ls->nonblock); + if (!t) { if (ls->nonblock) errno = EAGAIN; return -1; } + struct mg_bsd_sock *ns = (struct mg_bsd_sock *) calloc(1, sizeof(*ns)); + if (!ns || alloc_sock(ns) < 0) { mg_bsd_transport_free(t); free(ns); errno = ENOMEM; return -1; } + ns->t = t; ns->domain = ls->domain; ns->type = ls->type; ns->peer = peer; + if (addr && addrlen) { + size_t sz = sizeof(peer) < *addrlen ? sizeof(peer) : *addrlen; + memcpy(addr, &peer, sz); + *addrlen = (socklen_t) sizeof(peer); + } + return ns->fd; +} + +int connect(int fd, const struct sockaddr *addr, socklen_t len) { + struct mg_bsd_sock *s = get(fd); + if (!s) return -1; + (void) len; + return mg_bsd_transport_connect(s->t, (const struct sockaddr_in *) addr, s->nonblock); +} + +ssize_t send(int fd, const void *buf, size_t len, int flags) { + struct mg_bsd_sock *s = get(fd); + if (!s) return -1; + return mg_bsd_transport_send(s->t, buf, len, s->nonblock || (flags & MSG_DONTWAIT)); +} + +ssize_t recv(int fd, void *buf, size_t len, int flags) { + struct mg_bsd_sock *s = get(fd); + if (!s) return -1; + return mg_bsd_transport_recv(s->t, buf, len, s->nonblock || (flags & MSG_DONTWAIT)); +} + +ssize_t sendto(int fd, const void *buf, size_t len, int flags, + const struct sockaddr *dest, socklen_t addrlen) { + (void) dest; (void) addrlen; + return send(fd, buf, len, flags); +} + +ssize_t recvfrom(int fd, void *buf, size_t len, int flags, + struct sockaddr *src, socklen_t *addrlen) { + ssize_t n = recv(fd, buf, len, flags); + if (n > 0 && src && addrlen) { + struct mg_bsd_sock *s = get(fd); + if (s) { + size_t sz = sizeof(s->peer) < *addrlen ? sizeof(s->peer) : *addrlen; + memcpy(src, &s->peer, sz); + *addrlen = (socklen_t) sizeof(s->peer); + } + } + return n; +} + +ssize_t write(int fd, const void *buf, size_t len) { return send(fd, buf, len, 0); } +ssize_t read(int fd, void *buf, size_t len) { return recv(fd, buf, len, 0); } + +int close(int fd) { + struct mg_bsd_sock *s = get(fd); + if (!s) return -1; + mg_bsd_transport_close(s->t); + release_sock(fd); + free(s); + return 0; +} + +int shutdown(int fd, int how) { (void) how; return close(fd); } + +int fcntl(int fd, int cmd, int arg) { + struct mg_bsd_sock *s = get(fd); + if (!s) return -1; + if (cmd == F_GETFL) return s->nonblock ? O_NONBLOCK : 0; + if (cmd == F_SETFL) { s->nonblock = (arg & O_NONBLOCK) != 0; return 0; } + return -1; +} + +int setsockopt(int fd, int level, int optname, const void *optval, socklen_t optlen) { + (void) fd; (void) level; (void) optname; (void) optval; (void) optlen; + return 0; +} + +int getsockopt(int fd, int level, int optname, void *optval, socklen_t *optlen) { + (void) fd; (void) level; (void) optname; + if (optval && optlen && *optlen >= sizeof(int)) { *(int *) optval = 0; *optlen = sizeof(int); } + return 0; +} + +int getsockname(int fd, struct sockaddr *addr, socklen_t *addrlen) { + struct mg_bsd_sock *s = get(fd); + if (!s) return -1; + size_t sz = sizeof(s->addr) < *addrlen ? sizeof(s->addr) : *addrlen; + memcpy(addr, &s->addr, sz); + *addrlen = (socklen_t) sz; + return 0; +} + +int getpeername(int fd, struct sockaddr *addr, socklen_t *addrlen) { + struct mg_bsd_sock *s = get(fd); + if (!s) { errno = ENOTCONN; return -1; } + size_t sz = sizeof(s->peer) < *addrlen ? sizeof(s->peer) : *addrlen; + memcpy(addr, &s->peer, sz); + *addrlen = (socklen_t) sz; + return 0; +} + +// select/poll: not implemented for queue-based backend +int select(int nfds, fd_set *r, fd_set *w, fd_set *e, struct timeval *tv) { + (void) nfds; (void) r; (void) w; (void) e; (void) tv; + return 0; +} + +int poll(struct pollfd *fds, unsigned int nfds, int timeout) { + (void) fds; (void) nfds; (void) timeout; + return 0; +} + +// DNS stubs (overridden in the FreeRTOS backend below) +#if !MG_ENABLE_FREERTOS +struct hostent *gethostbyname(const char *name) { (void) name; return NULL; } +int getaddrinfo(const char *node, const char *service, + const struct addrinfo *hints, struct addrinfo **res) { + (void) node; (void) service; (void) hints; (void) res; + return -1; +} +void freeaddrinfo(struct addrinfo *res) { (void) res; } +#endif + +int inet_pton(int af, const char *src, void *dst) { + struct mg_addr a = {0}; + if (af == AF_INET && mg_aton(mg_str_s(src), &a)) { memcpy(dst, &a.addr.ip4, 4); return 1; } + return 0; +} + +const char *inet_ntop(int af, const void *src, char *dst, socklen_t size) { + if (af == AF_INET && size >= 16) { + const uint8_t *ip = (const uint8_t *) src; + snprintf(dst, size, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + return dst; + } + return NULL; +} + +in_addr_t inet_addr(const char *cp) { + struct mg_addr a = {0}; + return mg_aton(mg_str_s(cp), &a) ? a.addr.ip4 : (in_addr_t) -1; +} + +static char s_ntoa_buf[16]; +char *inet_ntoa(struct in_addr in) { + const uint8_t *ip = (const uint8_t *) &in.s_addr; + snprintf(s_ntoa_buf, sizeof(s_ntoa_buf), "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + return s_ntoa_buf; +} + +#ifdef MG_ENABLE_BSD_PROTOTYPES +uint16_t htons(uint16_t n) { return mg_htons(n); } +uint16_t ntohs(uint16_t n) { return mg_htons(n); } +uint32_t htonl(uint32_t n) { return mg_htonl(n); } +uint32_t ntohl(uint32_t n) { return mg_htonl(n); } +#endif + +// ============================================================ +// FreeRTOS + Mongoose transport backend +// ============================================================ +#if MG_ENABLE_FREERTOS + +#include + +#ifndef MG_BSD_CHUNK_SIZE +#define MG_BSD_CHUNK_SIZE 256 +#endif +#ifndef MG_BSD_Q_DEPTH +#define MG_BSD_Q_DEPTH 4 +#endif + +struct mg_bsd_chunk { uint8_t data[MG_BSD_CHUNK_SIZE]; uint16_t len; }; + +struct mg_xport { + struct mg_connection *c; // Mongoose connection, task1-only + QueueHandle_t recv_q; // task1 writes on MG_EV_READ, task2 reads in recv() + QueueHandle_t send_q; // task2 writes in send(), task1 drains on MG_EV_POLL + QueueHandle_t accept_q; // task1 writes on MG_EV_ACCEPT, task2 reads in accept() + struct sockaddr_in peer; + bool closed; + TaskHandle_t connect_waiter; // task blocked in connect(), woken by MG_EV_CONNECT + int *connect_result; // where to store 0/−1 connect outcome +}; + +enum mg_bsd_cmd_op { BSD_CMD_LISTEN, BSD_CMD_CLOSE, BSD_CMD_CONNECT, BSD_CMD_RESOLVE }; +struct mg_bsd_cmd { + enum mg_bsd_cmd_op type; + struct mg_xport *x; + char url[64]; + TaskHandle_t caller; + int *result; +}; + +static QueueHandle_t s_cmd_q; + +// Single-slot DNS resolve state (not reentrant, sufficient for demos) +static struct { struct mg_addr addr; bool done, error; TaskHandle_t caller; } s_resolve; + +static void resolve_cb(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_RESOLVE) { s_resolve.addr = c->rem; s_resolve.done = true; c->is_closing = 1; } + else if ((ev == MG_EV_ERROR || ev == MG_EV_CLOSE) && !s_resolve.done) s_resolve.error = true; + if ((s_resolve.done || s_resolve.error) && s_resolve.caller) { + TaskHandle_t h = s_resolve.caller; + s_resolve.caller = NULL; // prevent double-notify on subsequent MG_EV_CLOSE + xTaskNotifyGive(h); + } + (void) ev_data; +} + +// Allocate transport for an accepted connection (recv+send queues only) +static struct mg_xport *xport_alloc(void) { + struct mg_xport *x = (struct mg_xport *) calloc(1, sizeof(*x)); + if (!x) return NULL; + x->recv_q = xQueueCreate(MG_BSD_Q_DEPTH, sizeof(struct mg_bsd_chunk)); + x->send_q = xQueueCreate(MG_BSD_Q_DEPTH, sizeof(struct mg_bsd_chunk)); + if (!x->recv_q || !x->send_q) { mg_bsd_transport_free(x); return NULL; } + return x; +} + +static void xport_ev(struct mg_connection *c, int ev, void *ev_data) { + struct mg_xport *x = (struct mg_xport *) c->fn_data; + if (!x) return; + + if (ev == MG_EV_ACCEPT) { + // c is the new accepted connection; x is the listening transport + struct mg_xport *nx = xport_alloc(); + if (!nx) { c->is_closing = 1; return; } + nx->c = c; + nx->peer.sin_family = AF_INET; + nx->peer.sin_port = c->rem.port; + memcpy(&nx->peer.sin_addr, &c->rem.addr.ip4, 4); + c->fn_data = nx; + xQueueSend(x->accept_q, &nx, 0); + } else if (ev == MG_EV_READ && x->recv_q) { + // Drain c->recv into recv_q in fixed-size chunks; task1 owns c->recv + size_t off = 0; + while (off < c->recv.len) { + struct mg_bsd_chunk chunk; + size_t n = c->recv.len - off; + if (n > MG_BSD_CHUNK_SIZE) n = MG_BSD_CHUNK_SIZE; + memcpy(chunk.data, c->recv.buf + off, n); + chunk.len = (uint16_t) n; + xQueueSend(x->recv_q, &chunk, portMAX_DELAY); + off += n; + } + mg_iobuf_del(&c->recv, 0, c->recv.len); + } else if (ev == MG_EV_POLL && x->send_q) { + // Drain send_q → mg_send(); task1 owns c + struct mg_bsd_chunk chunk; + while (xQueueReceive(x->send_q, &chunk, 0) == pdTRUE) + mg_send(c, chunk.data, chunk.len); + } else if (ev == MG_EV_CONNECT) { + // Outgoing connection established: wake the task blocked in connect() + if (x->connect_waiter) { + if (x->connect_result) *x->connect_result = 0; + TaskHandle_t h = x->connect_waiter; + x->connect_waiter = NULL; x->connect_result = NULL; + xTaskNotifyGive(h); + } + } else if (ev == MG_EV_CLOSE) { + x->c = NULL; x->closed = true; c->fn_data = NULL; + // If connect() is still waiting, signal failure + if (x->connect_waiter) { + if (x->connect_result) *x->connect_result = -1; + TaskHandle_t h = x->connect_waiter; + x->connect_waiter = NULL; x->connect_result = NULL; + xTaskNotifyGive(h); + } + if (x->recv_q) { struct mg_bsd_chunk eof = {.len = 0}; xQueueSend(x->recv_q, &eof, 0); } + if (x->accept_q) { struct mg_xport *nil = NULL; xQueueSend(x->accept_q, &nil, 0); } + } + (void) ev_data; +} + +void mg_bsd_init(void) { + s_cmd_q = xQueueCreate(8, sizeof(struct mg_bsd_cmd)); +} + +void mg_bsd_poll(struct mg_mgr *mgr) { + struct mg_bsd_cmd cmd; + if (s_cmd_q == NULL) return; + while (xQueueReceive(s_cmd_q, &cmd, 0) == pdTRUE) { + bool notify = true; + if (cmd.type == BSD_CMD_LISTEN) { + struct mg_connection *c = mg_listen(mgr, cmd.url, xport_ev, cmd.x); + cmd.x->c = c; + *cmd.result = c ? 0 : -1; + } else if (cmd.type == BSD_CMD_CLOSE) { + if (cmd.x->c) { + cmd.x->c->fn_data = NULL; + cmd.x->c->is_draining = 1; + } + *cmd.result = 0; + } else if (cmd.type == BSD_CMD_CONNECT) { + cmd.x->connect_waiter = cmd.caller; + cmd.x->connect_result = cmd.result; + struct mg_connection *c = mg_connect(mgr, cmd.url, xport_ev, cmd.x); + cmd.x->c = c; + if (!c) { *cmd.result = -1; cmd.x->connect_waiter = NULL; cmd.x->connect_result = NULL; } + else notify = false; // xport_ev notifies when connected or on error + } else if (cmd.type == BSD_CMD_RESOLVE) { + s_resolve.done = s_resolve.error = false; + s_resolve.caller = cmd.caller; + char url[80]; + snprintf(url, sizeof(url), "tcp://%s:0", cmd.url); + if (!mg_connect(mgr, url, resolve_cb, NULL)) s_resolve.error = true; + else notify = false; // resolve_cb notifies when done + } + if (notify) xTaskNotifyGive(cmd.caller); + } +} + +void *mg_bsd_transport_new(int domain, int type, int proto) { + (void) domain; (void) type; (void) proto; + // For socket() calls: allocate accept_q only; recv/send added when needed + struct mg_xport *x = (struct mg_xport *) calloc(1, sizeof(*x)); + if (!x) return NULL; + x->accept_q = xQueueCreate(MG_BSD_BACKLOG, sizeof(struct mg_xport *)); + if (!x->accept_q) { free(x); return NULL; } + return x; +} + +void mg_bsd_transport_free(void *t) { + struct mg_xport *x = (struct mg_xport *) t; + if (!x) return; + if (x->recv_q) vQueueDelete(x->recv_q); + if (x->send_q) vQueueDelete(x->send_q); + if (x->accept_q) vQueueDelete(x->accept_q); + free(x); +} + +int mg_bsd_transport_listen(void *t, const struct sockaddr_in *addr) { + struct mg_xport *x = (struct mg_xport *) t; + int result = -1; + struct mg_bsd_cmd cmd = {BSD_CMD_LISTEN, x, {0}, xTaskGetCurrentTaskHandle(), &result}; + snprintf(cmd.url, sizeof(cmd.url), "tcp://0.0.0.0:%d", mg_ntohs(addr->sin_port)); + xQueueSend(s_cmd_q, &cmd, portMAX_DELAY); + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + return result; +} + +void *mg_bsd_transport_accept(void *t, struct sockaddr_in *peer, bool nonblock) { + struct mg_xport *x = (struct mg_xport *) t; + struct mg_xport *nx = NULL; + TickType_t ticks = nonblock ? 0 : portMAX_DELAY; + if (xQueueReceive(x->accept_q, &nx, ticks) != pdTRUE || !nx) return NULL; + if (peer) *peer = nx->peer; + return nx; +} + +ssize_t mg_bsd_transport_recv(void *t, void *buf, size_t len, bool nonblock) { + struct mg_xport *x = (struct mg_xport *) t; + struct mg_bsd_chunk chunk; + TickType_t ticks = nonblock ? 0 : portMAX_DELAY; + if (xQueueReceive(x->recv_q, &chunk, ticks) != pdTRUE) { + errno = EAGAIN; + return -1; + } + if (chunk.len == 0) return 0; // EOF + size_t n = chunk.len < len ? chunk.len : len; + memcpy(buf, chunk.data, n); + return (ssize_t) n; +} + +ssize_t mg_bsd_transport_send(void *t, const void *buf, size_t len, bool nonblock) { + struct mg_xport *x = (struct mg_xport *) t; + if (x->closed) return -1; + size_t sent = 0; + TickType_t ticks = nonblock ? 0 : portMAX_DELAY; + while (sent < len) { + struct mg_bsd_chunk chunk; + size_t n = len - sent; + if (n > MG_BSD_CHUNK_SIZE) n = MG_BSD_CHUNK_SIZE; + memcpy(chunk.data, (const uint8_t *) buf + sent, n); + chunk.len = (uint16_t) n; + if (xQueueSend(x->send_q, &chunk, ticks) != pdTRUE) break; + sent += n; + } + return sent > 0 ? (ssize_t) sent : (errno = EAGAIN, -1); +} + +int mg_bsd_transport_connect(void *t, const struct sockaddr_in *addr, bool nonblock) { + struct mg_xport *x = (struct mg_xport *) t; + (void) nonblock; + if (!x->recv_q) x->recv_q = xQueueCreate(MG_BSD_Q_DEPTH, sizeof(struct mg_bsd_chunk)); + if (!x->send_q) x->send_q = xQueueCreate(MG_BSD_Q_DEPTH, sizeof(struct mg_bsd_chunk)); + if (!x->recv_q || !x->send_q) { errno = ENOMEM; return -1; } + int result = -1; + struct mg_bsd_cmd cmd = {BSD_CMD_CONNECT, x, {0}, xTaskGetCurrentTaskHandle(), &result}; + uint8_t *ip = (uint8_t *) &addr->sin_addr.s_addr; + snprintf(cmd.url, sizeof(cmd.url), "tcp://%d.%d.%d.%d:%d", + ip[0], ip[1], ip[2], ip[3], mg_ntohs(addr->sin_port)); + xQueueSend(s_cmd_q, &cmd, portMAX_DELAY); + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + return result; +} + +// gethostbyname: resolve via Mongoose DNS (not reentrant) +static struct hostent s_hostent; +static char *s_h_aliases[1]; +static char *s_h_addr_list[2]; +static uint32_t s_h_addr; +static char s_h_name[64]; + +struct hostent *gethostbyname(const char *name) { + struct mg_bsd_cmd cmd = {BSD_CMD_RESOLVE, NULL, {0}, xTaskGetCurrentTaskHandle(), NULL}; + snprintf(cmd.url, sizeof(cmd.url), "%s", name); + xQueueSend(s_cmd_q, &cmd, portMAX_DELAY); + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + if (s_resolve.error) return NULL; + s_h_addr = s_resolve.addr.addr.ip4; + s_h_addr_list[0] = (char *) &s_h_addr; + s_h_addr_list[1] = NULL; + s_h_aliases[0] = NULL; + snprintf(s_h_name, sizeof(s_h_name), "%s", name); + s_hostent.h_name = s_h_name; + s_hostent.h_aliases = s_h_aliases; + s_hostent.h_addrtype = AF_INET; + s_hostent.h_length = 4; + s_hostent.h_addr_list = s_h_addr_list; + return &s_hostent; +} + +int getaddrinfo(const char *node, const char *service, + const struct addrinfo *hints, struct addrinfo **res) { + struct hostent *h = gethostbyname(node); + if (!h) return -1; + struct addrinfo *ai = (struct addrinfo *) calloc(1, sizeof(*ai)); + struct sockaddr_in *sa = (struct sockaddr_in *) calloc(1, sizeof(*sa)); + if (!ai || !sa) { free(ai); free(sa); return -1; } + sa->sin_family = AF_INET; + memcpy(&sa->sin_addr, h->h_addr, 4); + if (service) sa->sin_port = htons((uint16_t) atoi(service)); + ai->ai_family = AF_INET; + ai->ai_socktype = hints ? hints->ai_socktype : SOCK_STREAM; + ai->ai_addrlen = sizeof(*sa); + ai->ai_addr = (struct sockaddr *) sa; + *res = ai; + return 0; +} + +void freeaddrinfo(struct addrinfo *res) { + while (res) { + struct addrinfo *next = res->ai_next; + free(res->ai_addr); + free(res); + res = next; + } +} + +void mg_bsd_transport_close(void *t) { + struct mg_xport *x = (struct mg_xport *) t; + if (!x->closed && x->c) { + int result = 0; + struct mg_bsd_cmd cmd = {BSD_CMD_CLOSE, x, {0}, xTaskGetCurrentTaskHandle(), &result}; + xQueueSend(s_cmd_q, &cmd, portMAX_DELAY); + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + } + mg_bsd_transport_free(x); +} + +#ifndef MG_WAKEUP_QUEUE_DEPTH +#define MG_WAKEUP_QUEUE_DEPTH 4 +#endif + + +struct wumsg { + unsigned long id; + size_t len; + uint8_t data[]; +}; + +static void wufn(struct mg_connection *c, int ev, void *ev_data) { + QueueHandle_t q = (QueueHandle_t) c->mgr->pipe.q; + if (ev == MG_EV_POLL) { + struct wumsg *m; + if (xQueueReceive(q, &m, 0) == pdTRUE) { + struct mg_connection *t; + for (t = c->mgr->conns; t != NULL; t = t->next) { + if (t->id == m->id) { + struct mg_str data = mg_str_n((char *) m->data, m->len); + mg_call(t, MG_EV_WAKEUP, &data); + break; + } + } + free(m); + } + } else if (ev == MG_EV_CLOSE) { + struct wumsg *m; + while (xQueueReceive(q, &m, 0) == pdTRUE) free(m); + vQueueDelete(q); + c->mgr->pipe.q = NULL; + } + (void) ev_data; +} + +bool mg_wakeup_init(struct mg_mgr *mgr) { + struct mg_connection *c; + if (mgr->pipe.q != NULL) return true; + mgr->pipe.q = xQueueCreate(MG_WAKEUP_QUEUE_DEPTH, sizeof(void *)); + if (mgr->pipe.q == NULL) { + MG_ERROR(("Cannot create queue")); + return false; + } + c = mg_alloc_conn(mgr); + if (c == NULL) { + vQueueDelete((QueueHandle_t) mgr->pipe.q); + mgr->pipe.q = NULL; + return false; + } + c->fd = (void *) (size_t) MG_INVALID_SOCKET; + c->fn = wufn; + LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); + MG_DEBUG(("%lu queue %p", c->id, mgr->pipe.q)); + mg_call(c, MG_EV_OPEN, NULL); + return true; +} + +bool mg_wakeup(struct mg_mgr *mgr, unsigned long conn_id, const void *buf, + size_t len) { + struct wumsg *m; + if (mgr->pipe.q == NULL || conn_id == 0) return false; + m = (struct wumsg *) calloc(1, sizeof(*m) + len); + if (m == NULL) { + MG_ERROR(("OOM")); + return false; + } + m->id = conn_id; + m->len = len; + memcpy(m->data, buf, len); + if (xQueueSend((QueueHandle_t) mgr->pipe.q, &m, 0) != pdTRUE) { + free(m); + MG_ERROR(("xQueueSend")); + return false; + } + return true; +} + +#endif // MG_ENABLE_FREERTOS +#endif // MG_ENABLE_BSD_SOCKETS + +#ifdef MG_ENABLE_LINES +#line 1 "src/dash.c" +#endif + + + + +#define MG_NO_CACHE_HEADERS "Cache-Control: no-cache\r\n" +#define MG_JSON_HEADERS "Content-Type: application/json\r\n" MG_NO_CACHE_HEADERS + +#define CONN_HANDLED 'Z' + +struct mg_dash_cdata { + char marker; + struct mg_dash_user *u; + struct mg_dash *dash; +}; + +static struct mg_dash_user s_guest; +static struct mg_dash_user *s_users; // List of authenticated users + +static struct mg_str trimq(struct mg_str s) { // Trim double quotes + if (s.len > 1 && s.buf[0] == '"') s.len -= 2, s.buf++; + return s; +} + +static struct mg_field_set *mg_dash_find_field_set(struct mg_dash *dash, + struct mg_str name) { + struct mg_field_set *fs; + for (fs = dash->sets; fs != NULL; fs = fs->next) { + if (mg_strcmp(name, mg_str(fs->name)) == 0) return fs; + } + return NULL; +} + +// static struct mg_field *mg_dash_find_field(struct mg_field *fields, +// struct mg_str name) { +// size_t i; +// for (i = 0; fields != NULL && fields[i].name != NULL; i++) { +// if (mg_strcmp(name, mg_str(fields[i].name)) == 0) return &fields[i]; +// } +// return NULL; +// } + +static size_t mg_print_field(mg_pfn_t fn, void *arg, va_list *ap) { + struct mg_field *f = va_arg(*ap, struct mg_field *); + size_t n = 0; + n += mg_xprintf(fn, arg, "%m:", MG_ESC(f->name)); + if (f->type == MG_VAL_BOOL) { + n += mg_xprintf(fn, arg, "%s", *(bool *) f->value ? "true" : "false"); + } else if (f->type == MG_VAL_INT) { + n += mg_xprintf(fn, arg, "%d", *(int *) f->value); + } else if (f->type == MG_VAL_UINT64) { + n += mg_xprintf(fn, arg, "%llu", (uint64_t) *(uint64_t *) f->value); + } else if (f->type == MG_VAL_DBL) { + n += mg_xprintf(fn, arg, "%.2f", *(double *) f->value); + } else if (f->type == MG_VAL_STR) { + n += mg_xprintf(fn, arg, "%m", MG_ESC(f->value)); + } else if (f->type == MG_VAL_RAW) { + n += mg_xprintf(fn, arg, "%s", f->value); + } else { + n += mg_xprintf(fn, arg, "null"); + } + return n; +} + +static size_t mg_print_field_set(mg_pfn_t fn, void *arg, va_list *ap) { + struct mg_field_set *set = va_arg(*ap, struct mg_field_set *); + size_t i, n = 0; + n += mg_xprintf(fn, arg, "{"); + for (i = 0; set != NULL && set->fields[i].name != NULL; i++) { + if (i > 0) n += mg_xprintf(fn, arg, ","); + n += mg_xprintf(fn, arg, "%M", mg_print_field, &set->fields[i]); + } + n += mg_xprintf(fn, arg, "}"); + return n; +} + +static int mg_dash_array_size(struct mg_field_set *set, + struct mg_dash_user *u) { + int saved = *set->index, sz = -1; + *set->index = -1; + if (set->fn) { + if (set->fn(MG_DASH_READ, u)) sz = *set->index; + } else if (set->get_dir) { + mg_dash_dir_read(set, u); + sz = *set->index; + } + *set->index = saved; + return sz; +} + +static size_t mg_dash_print_array(mg_pfn_t fn, void *arg, va_list *ap) { + struct mg_field_set *set = va_arg(*ap, struct mg_field_set *); + int from = va_arg(*ap, int); + int to = va_arg(*ap, int); + struct mg_dash_user *u = va_arg(*ap, struct mg_dash_user *); + bool started = false; + int saved = *set->index; + size_t n = 0; + *set->index = from; + n += mg_xprintf(fn, arg, "["); + for (;;) { + bool done = to >= 0 && *set->index > to; + if (!done) { + if (set->fn) + set->fn(MG_DASH_READ, u); + else if (set->get_dir) + mg_dash_dir_read(set, u); + done = *set->index < 0; + } + if (done) break; + n += mg_xprintf(fn, arg, "%s%M", started ? "," : "", mg_print_field_set, + set); + started = true; + (*set->index)++; + } + n += mg_xprintf(fn, arg, "]"); + *set->index = saved; + return n; +} + +static size_t mg_dash_print_endpoint(mg_pfn_t fn, void *arg, va_list *ap) { + struct mg_dash *dash = va_arg(*ap, struct mg_dash *); + struct mg_dash_user *u = va_arg(*ap, struct mg_dash_user *); + struct mg_str *name = va_arg(*ap, struct mg_str *); + struct mg_str *from_str = va_arg(*ap, struct mg_str *); + struct mg_str *to_str = va_arg(*ap, struct mg_str *); + struct mg_field_set *set = mg_dash_find_field_set(dash, *name); + size_t n = 0; + if (name->len == 0) { + struct mg_field_set *fs; + const char *comma = ""; + n += mg_xprintf(fn, arg, "{"); + for (fs = dash->sets; fs != NULL; fs = fs->next) { + if (fs->index != NULL) { + int sz = mg_dash_array_size(fs, u); + if (sz < 0) continue; + n += mg_xprintf(fn, arg, comma); + n += mg_xprintf(fn, arg, "%m:%d", MG_ESC(fs->name), sz); + } else { + if (fs->fn && !fs->fn(MG_DASH_READ, u)) continue; + n += mg_xprintf(fn, arg, comma); + n += mg_xprintf(fn, arg, "%m:%M", MG_ESC(fs->name), mg_print_field_set, + fs); + } + comma = ","; + } + n += mg_xprintf(fn, arg, "}"); + } else if (set != NULL && (set->fn == NULL || set->fn(MG_DASH_READ, u))) { + if (set->index != NULL && from_str != NULL && from_str->len > 0) { + int from = 0, to = 0; + mg_str_to_num(*from_str, 10, &from, sizeof(from)); + to = from; + if (to_str != NULL && to_str->len > 0) { + mg_str_to_num(*to_str, 10, &to, sizeof(to)); + } + n += mg_xprintf(fn, arg, "%M", mg_dash_print_array, set, from, to, u); + } else if (set->index != NULL) { + n += mg_xprintf(fn, arg, "%d", mg_dash_array_size(set, u)); + } else { + n += mg_xprintf(fn, arg, "%M", mg_print_field_set, set); + } + } else { + n += mg_xprintf(fn, arg, "null"); + } + return n; +} + +void mg_dash_send_change(struct mg_mgr *mgr, struct mg_field_set *set) { + struct mg_connection *c; + for (c = mgr->conns; c != NULL; c = c->next) { + struct mg_dash_cdata *d = (struct mg_dash_cdata *) c->data; + struct mg_dash_user *u = d->u; + if (!c->is_websocket) continue; + if (u == NULL) continue; + if (set->index != NULL && *set->index < 0) { + int sz = mg_dash_array_size(set, u); + if (sz < 0) continue; + mg_ws_printf(c, WEBSOCKET_OP_TEXT, "{%m:%m,%m:{%m:%d}}", MG_ESC("method"), + MG_ESC("change"), MG_ESC("params"), MG_ESC(set->name), sz); + } else { + int saved_idx = set->index != NULL ? *set->index : 0; + bool ok = set->fn ? set->fn(MG_DASH_READ, u) + : (set->get_dir ? mg_dash_dir_read(set, u) : true); + if (!ok) { + if (set->index != NULL) *set->index = saved_idx; + continue; + } + if (set->index != NULL) { + char key[64]; + mg_snprintf(key, sizeof(key), "%s/%d", set->name, *set->index); + mg_ws_printf(c, WEBSOCKET_OP_TEXT, "{%m:%m,%m:{%m:%M}}", + MG_ESC("method"), MG_ESC("change"), MG_ESC("params"), + MG_ESC(key), mg_print_field_set, set); + *set->index = saved_idx; + } else { + mg_ws_printf(c, WEBSOCKET_OP_TEXT, "{%m:%m,%m:{%m:%M}}", + MG_ESC("method"), MG_ESC("change"), MG_ESC("params"), + MG_ESC(set->name), mg_print_field_set, set); + } + } + } +} + +static int mg_dash_parse_field(struct mg_str json, struct mg_field *f) { + char json_path[128]; + bool ok = false; + mg_snprintf(json_path, sizeof(json_path), "$.%s", f->name); + if (f->type == MG_VAL_BOOL) { + ok = f->value_size == sizeof(bool) && + mg_json_get_bool(json, json_path, (bool *) f->value); + } else if (f->type == MG_VAL_INT) { + double d; + if (f->value_size == sizeof(int) && mg_json_get_num(json, json_path, &d) && + d == (int) d) { + *(int *) f->value = (int) d; + ok = true; + } + } else if (f->type == MG_VAL_UINT64) { + double d; + if (f->value_size == sizeof(uint64_t) && + mg_json_get_num(json, json_path, &d) && d == (double) (int64_t) d) { + *(uint64_t *) f->value = (uint64_t) d; + ok = true; + } + } else if (f->type == MG_VAL_DBL) { + ok = f->value_size == sizeof(double) && + mg_json_get_num(json, json_path, (double *) f->value); + } else if (f->type == MG_VAL_STR && f->value_size > 0) { + struct mg_str tok = mg_json_get_tok(json, json_path); + ok = tok.len >= 2 && tok.buf[0] == '"' && + tok.buf[tok.len - 1] == '"'; + if (ok) mg_json_unescape(json, json_path, (char *) f->value, f->value_size); + } else if (f->type == MG_VAL_RAW && f->value_size > 0) { + ok = mg_snprintf((char *) f->value, f->value_size, "%.*s", json.len, + json.buf) == json.len; + } + return ok; +} + +static int mg_dash_apply(struct mg_connection *c, struct mg_dash *dash, + struct mg_str json, struct mg_dash_user *u) { + struct mg_str key, val; + size_t ofs = 0; + int total_count = 0; + while ((ofs = mg_json_next(json, ofs, &key, &val)) > 0) { + struct mg_field_set *set = mg_dash_find_field_set(dash, trimq(key)); + int count = 0; + if (set == NULL) { + MG_ERROR(("UNKNOWN SET: [%.*s]", key.len, key.buf)); + continue; + } + if (set->fn != NULL && !set->fn(MG_DASH_WRITE, u)) continue; // auth check + { + size_t i; + for (i = 0; set->fields[i].name != NULL; i++) { + if (mg_dash_parse_field(val, &set->fields[i])) count++; + } + } + if (count) { + if (set->fn) set->fn(MG_DASH_WRITE, u); // apply side effects + mg_dash_send_change(c->mgr, set); + total_count += count; + } + } + return total_count; +} + +bool mg_dash_dir_read(struct mg_field_set *set, struct mg_dash_user *u) { + char dir[256], fname[128] = ""; + struct mg_fs *fs = u->dash->upload_fs ? u->dash->upload_fs : &mg_fs_posix; + struct mg_field *name_field = NULL, *size_field = NULL; + size_t i; + + if (!set->get_dir(u, dir, sizeof(dir))) return false; + + for (i = 0; set->fields[i].name != NULL; i++) { + if (name_field == NULL && set->fields[i].type == MG_VAL_STR && + strcmp(set->fields[i].name, "name") == 0) + name_field = &set->fields[i]; + if (size_field == NULL && strcmp(set->fields[i].name, "size") == 0) + size_field = &set->fields[i]; + } + if (name_field == NULL) return false; + + if (*set->index == -1) { // Size query: count all files + int count = 0; + while (mg_fs_ls(fs, dir, fname, sizeof(fname))) count++; + *set->index = count; + return true; + } + + { // Regular read: scan to *set->index + int target = *set->index, cur = 0; + while (mg_fs_ls(fs, dir, fname, sizeof(fname))) { + if (cur++ == target) { + mg_snprintf((char *) name_field->value, name_field->value_size, "%s", + fname); + if (size_field != NULL) { + char path[512]; + size_t sz = 0; + mg_snprintf(path, sizeof(path), "%s/%s", dir, fname); + fs->st(path, &sz, NULL); + if (size_field->type == MG_VAL_UINT64) + *(uint64_t *) size_field->value = (uint64_t) sz; + else if (size_field->type == MG_VAL_INT) + *(int *) size_field->value = (int) sz; + } + return true; + } + } + *set->index = -1; // No more entries + return true; + } +} + +static bool mg_dash_set_file_name(struct mg_field_set *set, + struct mg_str name) { + size_t i; + for (i = 0; set->fields[i].name != NULL; i++) { + struct mg_field *f = &set->fields[i]; + if (f->type == MG_VAL_STR && strcmp(f->name, "name") == 0) { + mg_snprintf((char *) f->value, f->value_size, "%.*s", (int) name.len, + name.buf); + return true; + } + } + return false; +} + +static inline void mg_log_http_req(struct mg_connection *c, + struct mg_http_message *hm) { + int len = 0; + size_t n, spaces = 0, body_n = hm->body.len; + struct mg_http_message tmp; + memset(&tmp, 0, sizeof(tmp)); + len = mg_http_parse((char *) c->send.buf, c->send.len, &tmp); + n = (len < 0 || (size_t) len > c->send.len) ? c->send.len : (size_t) len; + while ((n + spaces) < c->send.len && spaces < c->send.len && + (c->send.buf[c->send.len - spaces - 1] == '\r' || + c->send.buf[c->send.len - spaces - 1] == '\n')) + spaces++; + // hm->body.len comes from Content-Length and can be larger than the bytes + // actually buffered so far (e.g. mid-stream uploads); cap the preview to + // what's actually present in c->recv, or we'd read past its end + { + char *recv_end = (char *) c->recv.buf + c->recv.len; + if (hm->body.buf >= (char *) c->recv.buf && hm->body.buf <= recv_end) { + size_t avail = (size_t) (recv_end - hm->body.buf); + if (body_n > avail) body_n = avail; + } else { + body_n = 0; + } + } + MG_DEBUG(("%lu %.*s %.*s%s%.*s %.*s: %lu %.*s -> %lu %.*s", c->id, + hm->method.len, hm->method.buf, hm->uri.len, hm->uri.buf, + hm->query.len > 0 ? "?" : "", hm->query.len, hm->query.buf, + c->send.len > 15 ? 3 : 0, &c->send.buf[9], hm->body.len, body_n, + hm->body.buf, c->send.len - n, c->send.len - n - spaces, + c->send.buf + n)); +} + +static void mg_dash_ota_cb(struct mg_connection *c, const char *errmsg) { + mg_http_reply(c, errmsg ? 500 : 200, NULL, errmsg ? errmsg : "ok\n"); + c->is_draining = 1; +} + +static void mg_dash_upload_cb(struct mg_connection *c, const char *errmsg) { + if (errmsg) { + mg_http_reply(c, 500, NULL, "%s\n", errmsg); + } else { + // mg_http_start_upload() repurposes c->data for its own bookkeeping, + // so the field set can't be cached there. Re-derive the dashboard from + // c->fn_data instead, and notify every file-backed array: the upload + // could belong to any of them, and re-querying get_dir() per recipient + // is what mg_dash_send_change() does anyway (directories can be + // user-specific) + struct mg_dash *dash = (struct mg_dash *) c->fn_data; + struct mg_field_set *fs; + mg_http_reply(c, 200, NULL, "ok\n"); + for (fs = dash->sets; fs != NULL; fs = fs->next) { + if (fs->get_dir == NULL) continue; + *fs->index = -1; // Signal mg_dash_send_change() to broadcast new size + mg_dash_send_change(c->mgr, fs); + } + } + c->is_draining = 1; +} + +static uint64_t mg_dash_make_expiration_time(struct mg_dash *dash) { + unsigned t = (unsigned) dash->session_auto_expiration_seconds; + if (t == 0) t = 3600; // Default expiration time in seconds + return mg_millis() + t * 1000; +} + +static struct mg_dash_user *mg_dash_add_user(struct mg_dash_user **users, + struct mg_dash *dash, + const char *name, + const char *token, int level) { + struct mg_dash_user *u = (struct mg_dash_user *) mg_calloc(1, sizeof(*u)); + if (u != NULL) { + mg_snprintf(u->name, sizeof(u->name), "%s", name); + if (token == NULL) { + mg_random_str(u->token, sizeof(u->token) - 1); + } else { + mg_snprintf(u->token, sizeof(u->token), "%s", token); + } + u->level = level; + u->expire = mg_dash_make_expiration_time(dash); + u->dash = dash; + u->next = *users; + *users = u; + } + return u; +} + +static struct mg_dash_user *mg_dash_find_user(struct mg_dash_user *users, + struct mg_dash *dash, + const char *name) { + struct mg_dash_user *u; + for (u = users; u != NULL; u = u->next) { + if (u->dash == dash && strcmp(u->name, name) == 0) return u; + } + return NULL; +} + +static struct mg_dash_user *mg_dash_find_token(struct mg_dash_user *users, + struct mg_dash *dash, + const char *token) { + struct mg_dash_user *u; + for (u = users; u != NULL; u = u->next) { + if (u->dash == dash && strcmp(u->token, token) == 0) return u; + } + return NULL; +} + +static void mg_dash_refresh_user(struct mg_dash *dash, + struct mg_dash_user *user) { + user->expire = mg_dash_make_expiration_time(dash); +} + +static void mg_dash_delete_user(struct mg_mgr *mgr, struct mg_dash_user *u) { + struct mg_connection *conn; + for (conn = mgr->conns; conn != NULL; conn = conn->next) { + struct mg_dash_cdata *d = (struct mg_dash_cdata *) conn->data; + if (conn->is_websocket && d->dash == u->dash && d->u == u) { + d->u = NULL; + conn->is_closing = 1; + } + } + LIST_DELETE(struct mg_dash_user, &s_users, u); + mg_free(u); +} + +static void mg_dash_delete_users(struct mg_mgr *mgr, struct mg_dash *dash) { + struct mg_dash_user *u, *tmp; + for (u = s_users; u != NULL; u = tmp) { + tmp = u->next; + if (u->dash == dash) mg_dash_delete_user(mgr, u); + } +} + +// Parse HTTP requests, return authenticated user or NULL +static struct mg_dash_user *mg_dash_authenticate(struct mg_connection *c, + struct mg_http_message *hm, + struct mg_dash *dash) { + char user[100], pass[100]; + struct mg_dash_user *u, *tmp; + struct mg_str *ah; + int level = 0, num_users = 0; + + if (dash->authenticate == NULL) { + mg_snprintf(s_guest.name, sizeof(s_guest.name), "%s", "guest"); + s_guest.level = 9; + s_guest.dash = dash; + dash->guest = &s_guest; + return dash->guest; + } + mg_http_creds(hm, user, sizeof(user), pass, sizeof(pass)); + ah = mg_http_get_header(hm, "Authorization"); + // MG_DEBUG(("user [%s], pass: [%s], h: %.*s", user, pass, hm->head.len, + // hm->head.buf)); + + // Remove expired users + for (u = s_users; u != NULL; u = tmp) { + tmp = u->next; + if (u->expire < mg_millis()) { + MG_DEBUG(("Deleting expired auth %s/%d %llu %u", u->name, u->level, + u->expire, mg_millis() - u->expire)); + mg_dash_delete_user(c->mgr, u); + } + } + + if (pass[0] == '\0') return NULL; + + for (u = s_users; u != NULL; u = u->next) { + if (u->dash == dash) num_users++; + } + if (ah == NULL) { + u = mg_dash_find_token(s_users, dash, pass); + if (u != NULL) { + mg_dash_refresh_user(dash, u); + return u; + } + } + + level = dash->authenticate(user, sizeof(user), pass); + MG_DEBUG(("user %s, level: %d", user, level)); + if (level <= 0) return NULL; + + u = mg_dash_find_user(s_users, dash, user); + if (u != NULL) { + if (ah == NULL) mg_snprintf(u->token, sizeof(u->token), "%s", pass); + mg_dash_refresh_user(dash, u); + return u; + } + + if (num_users < 10) + return mg_dash_add_user(&s_users, dash, user, ah == NULL ? pass : NULL, + level); + // MG_DEBUG(("[%s/%s] -> %s", user, pass, result ? "OK" : "FAIL")); + return NULL; +} + +static void mg_handle_login(struct mg_connection *c, struct mg_dash_user *u) { + char cookie[256]; + mg_snprintf(cookie, sizeof(cookie), + "Set-Cookie: access_token=%s; Path=/; " + "%sHttpOnly; SameSite=Lax; Max-Age=%d\r\n%s", + u->token, c->is_tls ? "Secure; " : "", 3600 * 24, + MG_JSON_HEADERS); + mg_http_reply(c, 200, cookie, "{%m:%m,%m:%d}\n", // + MG_ESC("user"), MG_ESC(u->name), // + MG_ESC("level"), u->level); +} + +static void mg_handle_logout(struct mg_connection *c) { + char cookie[256]; + mg_snprintf(cookie, sizeof(cookie), + "Set-Cookie: access_token=; Path=/; " + "Expires=Thu, 01 Jan 1970 00:00:00 UTC; " + "%sHttpOnly; Max-Age=0; \r\n", + c->is_tls ? "Secure; " : ""); + mg_http_reply(c, 401, cookie, "Unauthorized\n"); +} + +static void mg_dash_handle_del(struct mg_connection *c, struct mg_dash *dash, + struct mg_dash_user *u, struct mg_str *parts) { + struct mg_field_set *set = mg_dash_find_field_set(dash, parts[0]); + if (set == NULL || set->index == NULL) { + mg_http_reply(c, 404, MG_JSON_HEADERS, "null\n"); + } else { + int from = 0, to = 0, count = 0; + if (parts[1].len) mg_str_to_num(parts[1], 10, &from, sizeof(from)); + to = parts[2].len ? 0 : from; + if (parts[2].len) mg_str_to_num(parts[2], 10, &to, sizeof(to)); + for (*set->index = from; *set->index <= to; (*set->index)++) { + if (set->fn && set->fn(MG_DASH_DELETE, u)) + count++; + else + break; + } + if (count) { + *set->index = -1; + mg_dash_send_change(c->mgr, set); + mg_http_reply(c, 200, MG_JSON_HEADERS, "%d\n", count); + } else { + mg_http_reply(c, 403, MG_JSON_HEADERS, "false\n"); + } + } +} + +// Handle "POST /api/get//": modify one array element. Loads the +// element at first - that doubles as a read-access check and as a +// pre-fill, so that JSON keys absent from the body keep their old values - +// then overlays the values from the body and asks fn to persist them +static void mg_dash_handle_mod(struct mg_connection *c, struct mg_dash *dash, + struct mg_dash_user *u, struct mg_str *parts, + struct mg_str body) { + struct mg_field_set *set = mg_dash_find_field_set(dash, parts[0]); + int index = 0, count = 0; + size_t i; + if (set == NULL || set->index == NULL || set->fn == NULL) { + mg_http_reply(c, 404, MG_JSON_HEADERS, "null\n"); + return; + } + mg_str_to_num(parts[1], 10, &index, sizeof(index)); + *set->index = index; + if (!set->fn(MG_DASH_READ, u) || *set->index != index) { + mg_http_reply(c, 404, MG_JSON_HEADERS, "null\n"); + return; + } + for (i = 0; set->fields[i].name != NULL; i++) { + if (mg_dash_parse_field(body, &set->fields[i])) count++; + } + if (count && set->fn(MG_DASH_WRITE, u)) { + mg_dash_send_change(c->mgr, set); + mg_http_reply(c, 200, MG_JSON_HEADERS, "%d\n", count); + } else { + mg_http_reply(c, 403, MG_JSON_HEADERS, "false\n"); + } +} + +// Handle "POST /api/add/": append a new array element. Parses the body +// straight into the bound fields, then asks fn to accept and persist them as +// a new element - fn returns false to reject, e.g. when a cap is reached +static void mg_dash_handle_add(struct mg_connection *c, struct mg_dash *dash, + struct mg_dash_user *u, struct mg_str *parts, + struct mg_str body) { + struct mg_field_set *set = mg_dash_find_field_set(dash, parts[0]); + int count = 0; + size_t i; + if (set == NULL || set->index == NULL || set->fn == NULL) { + mg_http_reply(c, 404, MG_JSON_HEADERS, "null\n"); + return; + } + for (i = 0; set->fields[i].name != NULL; i++) { + if (mg_dash_parse_field(body, &set->fields[i])) count++; + } + if (count && set->fn(MG_DASH_ADD, u)) { + *set->index = -1; // Signal mg_dash_send_change() to send new size + mg_dash_send_change(c->mgr, set); + mg_http_reply(c, 200, MG_JSON_HEADERS, "true\n"); + } else { + mg_http_reply(c, 403, MG_JSON_HEADERS, "false\n"); + } +} + +void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data) { + struct mg_dash *dash = (struct mg_dash *) c->fn_data; + struct mg_dash_cdata *d = (struct mg_dash_cdata *) c->data; + + if (ev == MG_EV_OPEN) { + d->dash = dash; + // c->is_hexdumping = 1; + } else if (ev == MG_EV_CLOSE && c->is_listening) { + mg_dash_delete_users(c->mgr, dash); + } else if (ev == MG_EV_HTTP_HDRS && d->marker == 0) { + // Received headers - check authentication and possibly start uploads/ota + struct mg_http_message *hm = (struct mg_http_message *) ev_data; + struct mg_dash_user *u = mg_dash_authenticate(c, hm, dash); + struct mg_str parts[3]; + memset(parts, 0, sizeof(parts)); + + if (mg_match(hm->uri, mg_str("/api/hi"), NULL) || + mg_match(hm->uri, mg_str("/api/logout"), NULL)) { + // Do nothing, handle them MG_EV_HTTP_MSG. We bypass auth for those + } else if (u == NULL && mg_match(hm->uri, mg_str("/api/#"), NULL)) { + mg_http_reply(c, 403, MG_JSON_HEADERS, "Not Authorised\n"); + d->marker = CONN_HANDLED; + } else if (mg_match(hm->uri, mg_str("/api/login"), NULL) && u != NULL) { + mg_handle_login(c, u); + d->marker = CONN_HANDLED; + } else if (mg_match(hm->uri, mg_str("/api/ota"), NULL)) { + mg_http_start_ota(c, hm, mg_dash_ota_cb); + } else if (mg_match(hm->uri, mg_str("/fs/*/*"), parts) && + (mg_strcasecmp(hm->method, mg_str("POST")) == 0 || + mg_strcasecmp(hm->method, mg_str("PUT")) == 0)) { + struct mg_field_set *set = mg_dash_find_field_set(dash, parts[0]); + struct mg_str name = parts[1]; + int len = + mg_url_decode(name.buf, name.len, (char *) name.buf, name.len + 1, 0); + if (len > 0 && (size_t) len <= name.len) name.len = (size_t) len; + if (set == NULL || set->get_dir == NULL) { + mg_http_reply(c, 404, MG_JSON_HEADERS, "Not Found\n"); + d->marker = CONN_HANDLED; + } else if (u == NULL) { + mg_http_reply(c, 403, MG_JSON_HEADERS, "Not Authorised\n"); + d->marker = CONN_HANDLED; + } else if (!mg_path_is_sane(name)) { + mg_http_reply(c, 400, MG_JSON_HEADERS, "Bad file name\n"); + d->marker = CONN_HANDLED; + } else { + mg_dash_set_file_name(set, name); + if (set->fn != NULL && !set->fn(MG_DASH_WRITE, u)) { + mg_http_reply(c, 403, MG_JSON_HEADERS, "Not Authorised\n"); + d->marker = CONN_HANDLED; + } else { + char dir[256]; + struct mg_fs *fs = dash->upload_fs ? dash->upload_fs : &mg_fs_posix; + if (!set->get_dir(u, dir, sizeof(dir))) { + mg_http_reply(c, 500, MG_JSON_HEADERS, "Upload dir error\n"); + d->marker = CONN_HANDLED; + } else { + mg_http_start_upload(c, hm, name, mg_str(dir), fs, + mg_dash_upload_cb); + } + } + } + } + if (d->marker != '\0') mg_log_http_req(c, hm); + } else if (ev == MG_EV_HTTP_MSG && d->marker != '\0') { + // The response has been send in EV_HDRS path, so we're not reponding + // anything but clearing the marker for the next request. + d->marker = 0; + c->is_resp = 0; + } else if (ev == MG_EV_HTTP_MSG && d->marker == '\0') { + struct mg_http_message *hm = (struct mg_http_message *) ev_data; + struct mg_dash_user *u = mg_dash_authenticate(c, hm, dash); + struct mg_str parts[5]; + memset(parts, 0, sizeof(parts)); + + if (mg_match(hm->uri, mg_str("/api/hi"), NULL)) { + mg_http_reply(c, 200, MG_JSON_HEADERS, "hi\n"); + } else if (mg_match(hm->uri, mg_str("/api/logout"), NULL)) { + mg_handle_logout(c); + mg_ws_printf(c, WEBSOCKET_OP_TEXT, "{%m:%m}", MG_ESC("method"), + MG_ESC("logout")); + } else if (mg_match(hm->uri, mg_str("/api/websocket"), NULL)) { + d->u = u; + mg_ws_upgrade(c, hm, NULL); + } else if (mg_match(hm->uri, mg_str("/fs/*/*"), parts)) { + struct mg_field_set *set = mg_dash_find_field_set(dash, parts[0]); + if (set == NULL || set->get_dir == NULL) { + mg_http_reply(c, 404, MG_JSON_HEADERS, "Not Found"); + } else if (u == NULL) { + mg_http_reply(c, 403, MG_JSON_HEADERS, "Not Authorised\n"); + } else { + char dir[256], path[512]; + struct mg_fs *fs = dash->upload_fs ? dash->upload_fs : &mg_fs_posix; + struct mg_str name = parts[1]; + int len = mg_url_decode(name.buf, name.len, (char *) name.buf, + name.len + 1, 0); + if (len > 0 && (size_t) len <= name.len) name.len = (size_t) len; + if (!mg_path_is_sane(name)) { + mg_http_reply(c, 400, MG_JSON_HEADERS, "Bad file name\n"); + return; + } + if (!set->get_dir(u, dir, sizeof(dir))) { + mg_http_reply(c, 500, MG_JSON_HEADERS, "Upload dir error\n"); + return; + } + mg_snprintf(path, sizeof(path), "%s/%.*s", dir, name.len, name.buf); + if (mg_strcasecmp(hm->method, mg_str("DELETE")) == 0) { + mg_dash_set_file_name(set, name); + if (set->fn != NULL && !set->fn(MG_DASH_DELETE, u)) { + mg_http_reply(c, 403, MG_JSON_HEADERS, "Not Authorised\n"); + } else { + fs->rm(path); + *set->index = -1; // Signal mg_dash_send_change() to send new size + mg_dash_send_change(c->mgr, set); + mg_http_reply(c, 200, NULL, "true"); + } + } else { + mg_http_serve_file(c, hm, path, NULL); + } + } + } else if (mg_match(hm->uri, mg_str("/api/del/*/*/*"), parts) || + mg_match(hm->uri, mg_str("/api/del/*/*"), parts)) { + mg_dash_handle_del(c, dash, u, parts); + } else if (mg_match(hm->uri, mg_str("/api/add/*"), parts)) { + mg_dash_handle_add(c, dash, u, parts, hm->body); + } else if (mg_match(hm->uri, mg_str("/api/get/*/*"), parts) && + mg_strcasecmp(hm->method, mg_str("POST")) == 0) { + mg_dash_handle_mod(c, dash, u, parts, hm->body); + } else if (mg_match(hm->uri, mg_str("/api/get/*/*/*"), parts) || + mg_match(hm->uri, mg_str("/api/get/*/*"), parts) || + mg_match(hm->uri, mg_str("/api/get/*"), parts) || + mg_match(hm->uri, mg_str("/api/get"), NULL)) { + mg_http_reply(c, 200, MG_JSON_HEADERS, "%M\n", mg_dash_print_endpoint, + dash, u, &parts[0], &parts[1], &parts[2]); + } else if (mg_match(hm->uri, mg_str("/api/set"), NULL)) { + int count = mg_dash_apply(c, dash, hm->body, u); + mg_http_reply(c, 200, MG_JSON_HEADERS, "%d\n", count); + } else if (mg_match(hm->uri, mg_str("/"), NULL)) { + struct mg_http_serve_opts opts; + memset(&opts, 0, sizeof(opts)); + opts.fs = &mg_fs_packed; + mg_http_serve_file(c, hm, "/dashboard.html", &opts); + } else { + struct mg_dash_custom_handler *ch = dash->custom_handlers; + for (ch = dash->custom_handlers; ch != NULL; ch = ch->next) { + if (mg_match(hm->uri, ch->uri_pattern, NULL)) { + ch->handler(c, ev, ev_data); + break; + } + } + if (ch == NULL) mg_http_reply(c, 404, MG_JSON_HEADERS, "Not Found"); + mg_log_http_req(c, hm); + } + } +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/dns.c" +#endif + + + + + + + + +struct dns_data { + struct dns_data *next; + struct mg_connection *c; + uint64_t expire; + uint16_t txnid; +}; + +static void sendnsreq(struct mg_connection *, struct mg_str *, int, + struct mg_dns *, bool); + +struct mdns_data { + struct mdns_data *next; + struct mg_connection *c; + uint64_t expire; + struct mg_str name; +}; + +static void sendmdnsreq(struct mg_connection *, struct mg_str *, int, + struct mg_connection *, bool); + +static void dns_free(struct dns_data **head, struct dns_data *d) { + LIST_DELETE(struct dns_data, head, d); + mg_free(d); +} + +static void mdns_free(struct mdns_data **head, struct mdns_data *d) { + LIST_DELETE(struct mdns_data, head, d); + mg_free((void *) d->name.buf); + mg_free(d); +} + +void mg_resolve_cancel(struct mg_connection *c) { + struct dns_data *tmp, *d; + struct mdns_data *mtmp, *md; + struct dns_data **head = (struct dns_data **) &c->mgr->active_dns_requests; + struct mdns_data **mhead = + (struct mdns_data **) &c->mgr->active_mdns_requests; + for (d = *head; d != NULL; d = tmp) { + tmp = d->next; + if (d->c == c) dns_free(head, d); + } + for (md = *mhead; md != NULL; md = mtmp) { + mtmp = md->next; + if (md->c == c) mdns_free(mhead, md); + } +} + +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 it is nul-term + return i; +} + +static size_t mg_dns_parse_name(const uint8_t *s, size_t n, size_t ofs, + char *dst, size_t dstlen) { + return mg_dns_parse_name_depth(s, n, ofs, dst, dstlen, 0, 0); +} + +size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs, + bool is_question, struct mg_dns_rr *rr) { + const uint8_t *s = buf + ofs, *e = &buf[len]; + + memset(rr, 0, sizeof(*rr)); + if (len < sizeof(struct mg_dns_header)) return 0; // Too small + if (len > 512) return 0; // Too large, we don't expect that + if (s >= e) return 0; // Overflow + + if ((rr->nlen = (uint16_t) mg_dns_parse_name(buf, len, ofs, NULL, 0)) == 0) + return 0; + s += rr->nlen + 4; + if (s > e) return 0; + rr->atype = (uint16_t) (((uint16_t) s[-4] << 8) | s[-3]); + rr->aclass = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]); + if (is_question) return (size_t) (rr->nlen + 4); + + s += 6; + if (s > e) return 0; + rr->alen = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]); + if (s + rr->alen > e) return 0; + return (size_t) (rr->nlen + rr->alen + 10); +} + +bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *dm) { + const struct mg_dns_header *h = (struct mg_dns_header *) buf; + struct mg_dns_rr rr; + size_t i, n, num_answers, ofs = sizeof(*h); + bool is_response; + 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); + is_response = mg_ntohs(h->flags) & 0x8000; + + 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)); + mg_dns_parse_name(buf, len, ofs, dm->name, sizeof(dm->name)); + ofs += n; + } + + if (!is_response) { + // For queries, there is no need to parse the answers. In this way, + // we also ensure the domain name (dm->name) is parsed from + // the question field. + return true; + } + + 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 == MG_DNS_RTYPE_A && rr.aclass == 1) { + dm->addr.is_ip6 = false; + memcpy(&dm->addr.addr.ip, &buf[ofs - 4], 4); + dm->resolved = true; + break; // Return success + } else if (rr.alen == 16 && rr.atype == MG_DNS_RTYPE_AAAA && + rr.aclass == 1) { + dm->addr.is_ip6 = true; + memcpy(&dm->addr.addr.ip, &buf[ofs - 16], 16); + dm->resolved = true; + break; // Return success + } + } + return true; +} + +static void dns_cb(struct mg_connection *c, int ev, void *ev_data) { + 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"); // will remove entry + } + } 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); + 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)); + } + 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"); // will remove entry + } + } +} + +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); +} + +bool mg_dnsc_init(struct mg_mgr *mgr, struct mg_dns *dnsc); +bool mg_dnsc_init(struct mg_mgr *mgr, struct mg_dns *dnsc) { + if (dnsc->url == NULL) { + mg_error(0, "DNS server URL is NULL. Call mg_mgr_init()"); + return false; + } + if (dnsc->c == NULL) { + dnsc->c = mg_connect(mgr, dnsc->url, NULL, NULL); + if (dnsc->c == NULL) return false; + dnsc->c->pfn = dns_cb; + } + return true; +} + +static void sendnsreq(struct mg_connection *c, struct mg_str *name, int ms, + struct mg_dns *dnsc, bool ipv6) { + struct dns_data *d = NULL; + if (!mg_dnsc_init(c->mgr, dnsc)) { + mg_error(c, "resolver"); + } else if ((d = (struct dns_data *) mg_calloc(1, sizeof(*d))) == NULL) { + mg_error(c, "resolve OOM"); + } else { + struct dns_data *reqs = (struct dns_data *) c->mgr->active_dns_requests; + uint16_t id; + mg_random(&id, sizeof(uint16_t)); + if (reqs != NULL) // no seq, no collision for upto 256 in-flight requests + id = (uint16_t) (reqs->txnid + (id & 0xFF) + 1); + d->txnid = id; + d->next = reqs; + 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 if (host.len > 6 && + strncmp(".local", &host.buf[host.len - 6], 6) == 0) { + // this is a request for a .local name (mDNS) + sendmdnsreq(c, &host, 500, c->mgr->mdns, c->mgr->use_dns6); // 500ms tmout + } else { + // host is not an IP nor a .local, send DNS resolution request + struct mg_dns *dns = c->mgr->use_dns6 ? &c->mgr->dns6 : &c->mgr->dns4; + sendnsreq(c, &host, c->mgr->dnstimeout, dns, c->mgr->use_dns6); + } +} + +// Response header length is 10 bytes +static const uint8_t mdns_answer[] = { + 0, 1, // 2 bytes - record type, A + 0, 1, // 2 bytes - address class, INET + 0, 0, 0, 120, // 4 bytes - TTL + 0, 4 // 2 bytes - address length +}; + +// A name length is name->len + '.local' + 2 = name->len + 8 +static uint8_t *build_name(struct mg_str *name, uint8_t *p) { + *p++ = (uint8_t) name->len; // label 1 + memcpy(p, name->buf, name->len), p += name->len; + *p++ = 5; // label 2 + memcpy(p, "local", 5), p += 5; + *p++ = 0; // no more labels + return p; +} + +void mg_getlocaddr(struct mg_connection *, struct mg_addr *, struct mg_addr *); + +// An A record length is 10 + 4 = 14 bytes +static uint8_t *build_a_record(struct mg_connection *c, uint8_t *p, + struct mg_addr *addr) { + memcpy(p, mdns_answer, sizeof(mdns_answer)), p += sizeof(mdns_answer); + if (addr != NULL && !addr->is_ip6) { + memcpy(p, &addr->addr.ip4, 4), p += 4; + } else { +#if MG_ENABLE_TCPIP + memcpy(p, &c->mgr->ifp->ip, 4), p += 4; +#else + struct mg_addr loc, to; + memset(&loc, 0, sizeof(loc)); + to.is_ip6 = false; + to.port = mg_htons(5353); + to.addr.ip4 = MG_IPV4(224, 0, 0, 51); + mg_getlocaddr(c, &to, &loc); + memcpy(p, &loc.addr.ip4, 4), p += 4; +#endif + } + return p; +} + +// A srv name length is r->srvcproto.len + '.local' + 2 = r->srvcproto.len + 8 +static uint8_t *build_srv_name(uint8_t *p, struct mg_dnssd_record *r) { + *p++ = (uint8_t) r->srvcproto.len - 5; // label 1, up to '._tcp' + memcpy(p, r->srvcproto.buf, r->srvcproto.len), p += r->srvcproto.len; + p[-5] = 4; // label 2, '_tcp', overwrite '.' + *p++ = 5; // label 3 + memcpy(p, "local", 5), p += 5; + *p++ = 0; // no more labels + return p; +} + +#if 0 +// TODO(): for listing +static uint8_t *build_mysrv_name(struct mg_str *name, uint8_t *p, + struct mg_dnssd_record *r) { + *p++ = name->len; // label 1 + memcpy(p, name->buf, name->len), p += name->len; + return build_srv_name(p, r); +} +#endif + +// A PTR record length is 10 + name->len + 3 = name->len + 13 +static uint8_t *build_ptr_record(struct mg_str *name, uint8_t *p, uint16_t o) { + uint16_t offset = mg_htons(o); + memcpy(p, mdns_answer, sizeof(mdns_answer)); + p[1] = MG_DNS_RTYPE_PTR; // overwrite record type + p += sizeof(mdns_answer); + p[-1] = (uint8_t) name->len + + 3; // overwrite response length, label length + label + offset + *p++ = (uint8_t) name->len; // response: label 1 + memcpy(p, name->buf, name->len), p += name->len; // copy label + memcpy(p, &offset, 2); + *p |= 0xC0, p += 2; + return p; +} + +// An SRV record length is 10 + name->len + 9 = name->len + 19 +static uint8_t *build_srv_record(struct mg_str *name, uint8_t *p, + struct mg_dnssd_record *r, uint16_t o) { + uint16_t port = mg_htons(r->port); + uint16_t offset = mg_htons(o); + memcpy(p, mdns_answer, sizeof(mdns_answer)); + p[1] = MG_DNS_RTYPE_SRV; // overwrite record type + p += sizeof(mdns_answer); + p[-1] = (uint8_t) name->len + 9; // overwrite response length (4+2+1+2) + *p++ = 0; // priority + *p++ = 0; + *p++ = 0; // weight + *p++ = 0; + memcpy(p, &port, 2), p += 2; // port + *p++ = (uint8_t) name->len; // label 1 + memcpy(p, name->buf, name->len), p += name->len; + memcpy(p, &offset, 2); + *p |= 0xC0, p += 2; + return p; +} + +// A TXT record length is r->txt.len (txt contents) + 10 +static uint8_t *build_txt_record(uint8_t *p, struct mg_dnssd_record *r) { + uint16_t len = mg_htons((uint16_t) r->txt.len); + memcpy(p, mdns_answer, sizeof(mdns_answer)); + p[1] = MG_DNS_RTYPE_TXT; // overwrite record type + p += sizeof(mdns_answer); + memcpy(p - 2, &len, 2); // overwrite response length + memcpy(p, r->txt.buf, r->txt.len), p += r->txt.len; // copy record verbatim + return p; +} + +// Each additional record has a 2-byte field pointing to the name label + +// RFC-6762 16: case-insensitivity --> RFC-1034, 1035 + +static void handle_mdns_query(struct mg_connection *c) { + struct mg_dns_header *qh = (struct mg_dns_header *) c->recv.buf; + struct mg_dns_rr rr; + size_t n; + // Parse first question, offset 12 is header size + n = mg_dns_parse_rr(c->recv.buf, c->recv.len, 12, true, &rr); + MG_VERBOSE(("mDNS request parsed, result=%d", (int) n)); + if (n > 0) { + // RFC-6762 Appendix C, RFC2181 11: m(n + 1-63), max 255 + 0x0 + uint8_t buf[sizeof(struct mg_dns_header) + 256 + sizeof(mdns_answer) + 4]; + struct mg_dns_header *h = (struct mg_dns_header *) buf; + uint8_t *p = &buf[sizeof(*h)]; + char name[256]; + uint8_t name_len; + // uint16_t q = mg_ntohs(qh->num_questions); + struct mg_str defname = mg_str((const char *) c->fn_data); + struct mg_str *respname; + struct mg_mdns_req req; + memset(&req, 0, sizeof(req)); + req.is_unicast = (rr.aclass & MG_BIT(15)) != 0; // QU + rr.aclass &= (uint16_t) ~MG_BIT(15); // remove "QU" (unicast response) + qh->num_questions = mg_htons(1); // parser sanity + mg_dns_parse_name(c->recv.buf, c->recv.len, 12, name, sizeof(name)); + name_len = (uint8_t) strlen(name); // verify it ends in .local + if (name_len <= 6 || strcmp(".local", &name[name_len - 6]) != 0 || + (rr.aclass != 1 && rr.aclass != 0xff)) + return; + name[name_len -= 6] = '\0'; // remove .local + MG_VERBOSE(("RR %u %u %s", (unsigned int) rr.atype, + (unsigned int) rr.aclass, name)); + if (rr.atype == MG_DNS_RTYPE_A) { + // TODO(): ensure c->fn_data ends in \0 + // if we have a name to match, go; otherwise users will match and fill + // req.r.name and set req.is_resp + if (c->fn_data != NULL && mg_casecmp((char *) c->fn_data, name) != 0) + return; + req.is_resp = (c->fn_data != NULL); + req.reqname = mg_str_n(name, name_len); + } else // users have to match the request to something in their db, then + // fill req.r and set req.is_resp + if (rr.atype == MG_DNS_RTYPE_PTR) { + if (strcmp("_services._dns-sd._udp", name) == 0) req.is_listing = true; + MG_DEBUG( + ("PTR request for %s", req.is_listing ? "services listing" : name)); + req.reqname = mg_str_n(name, name_len); + } else if (rr.atype == MG_DNS_RTYPE_SRV || rr.atype == MG_DNS_RTYPE_TXT) { + MG_DEBUG(("%s request for %s", + rr.atype == MG_DNS_RTYPE_SRV ? "SRV" : "TXT", name)); + // if possible, check it starts with our name, users will check it ends + // in a service name they handle + if (c->fn_data != NULL) { + if (mg_strcasecmp(defname, mg_str_n(name, defname.len)) != 0 || + name[defname.len] != '.') + return; + req.reqname = + mg_str_n(name + defname.len + 1, name_len - defname.len - 1); + MG_DEBUG( + ("That's us, handing %.*s", req.reqname.len, req.reqname.buf)); + } else { + req.reqname = mg_str_n(name, name_len); + } + } else { // unhandled record + return; + } + req.rr = &rr; + mg_call(c, MG_EV_MDNS_REQ, &req); + if (!req.is_resp) return; + respname = req.respname.buf != NULL ? &req.respname : &defname; + + memset(h, 0, sizeof(*h)); // clear header + h->txnid = req.is_unicast ? qh->txnid : 0; // RFC-6762 18.1 + h->num_answers = mg_htons(1); // RFC-6762 6: 0 questions, 1 Answer + h->flags = mg_htons(0x8400); // Authoritative response + if (req.is_listing) { + // TODO(): RFC-6762 6: each responder SHOULD delay its response by a + // random amount of time selected with uniform random distribution in the + // range 20-120 ms. + // TODO(): + return; + } else if (rr.atype == MG_DNS_RTYPE_PTR) { // serve PTR + SRV + TXT + A + // TODO(): RFC-6762 6: each responder SHOULD delay its response by a + // random amount of time selected with uniform random distribution in the + // range 20-120 ms. Response to PTR is local_name._myservice._tcp.local + uint8_t *o = p, *aux; + uint16_t offset; + if (respname->buf == NULL || respname->len == 0) return; + if ((sizeof(*h) + req.r->srvcproto.len + 8 + respname->len + 13 + 2 + + respname->len + 19 + 2 + req.r->txt.len + 10 + 2 + 14) > + sizeof(buf)) // srv name + PTR + 2 + SRV + 2 + TXT + 2 + A + return; + h->num_other_prs = mg_htons(3); // 3 additional records + p = build_srv_name(p, req.r); + aux = build_ptr_record(respname, p, (uint16_t) (o - buf)); + o = p + sizeof(mdns_answer); // point to PTR response (full srvc name) + offset = mg_htons((uint16_t) (o - buf)); + o = p - 7; // point to '.local' label (\x05local\x00) + p = aux; + memcpy(p, &offset, 2); // point to full srvc name, in record + *p |= 0xC0, p += 2; + aux = p; + p = build_srv_record(respname, p, req.r, (uint16_t) (o - buf)); + o = aux + sizeof(mdns_answer) + 6; // point to target in SRV + memcpy(p, &offset, 2); // point to full srvc name, in record + *p |= 0xC0, p += 2; + p = build_txt_record(p, req.r); + offset = mg_htons((uint16_t) (o - buf)); + memcpy(p, &offset, 2); // point to target name, in record + *p |= 0xC0, p += 2; + p = build_a_record(c, p, req.addr); + } else if (rr.atype == MG_DNS_RTYPE_TXT) { + if ((sizeof(*h) + req.r->srvcproto.len + 8 + req.r->txt.len + 10) > + sizeof(buf)) // srv name + TXT + return; + p = build_srv_name(p, req.r); + p = build_txt_record(p, req.r); + } else if (rr.atype == MG_DNS_RTYPE_SRV) { // serve SRV + A + uint8_t *o, *aux; + uint16_t offset; + if (respname->buf == NULL || respname->len == 0) return; + if ((sizeof(*h) + req.r->srvcproto.len + 8 + respname->len + 19 + 2 + + 14) > sizeof(buf)) // srv name + SRV + 2 + A + return; + h->num_other_prs = mg_htons(1); // 1 additional record + p = build_srv_name(p, req.r); + o = p - 7; // point to '.local' label (\x05local\x00) + aux = p; + p = build_srv_record(respname, p, req.r, (uint16_t) (o - buf)); + o = aux + sizeof(mdns_answer) + 6; // point to target in SRV + offset = mg_htons((uint16_t) (o - buf)); + memcpy(p, &offset, 2); // point to target name, in record + *p |= 0xC0, p += 2; + p = build_a_record(c, p, req.addr); + } else { // A requested + // RFC-6762 6: 0 Auth, 0 Additional RRs + if (respname->buf == NULL || respname->len == 0) return; + if ((sizeof(*h) + respname->len + 8 + 14) > sizeof(buf)) // name + A + return; + p = build_name(respname, p); + p = build_a_record(c, p, req.addr); + } + if (!req.is_unicast) mg_multicast_restore(c, (uint8_t *) &c->loc); + mg_send(c, buf, (size_t) (p - buf)); // And send it! + MG_DEBUG(("%M > %M", mg_print_ip_port, &c->loc, mg_print_ip_port, &c->rem)); + MG_DEBUG(("mDNS %s response sent", req.is_unicast ? "unicast" : "mcast")); + } +} + +static void handle_mdns_response(struct mg_connection *c) { + struct mg_dns_header *rh = (struct mg_dns_header *) c->recv.buf; + struct mg_dns_rr rr; + size_t n; + // Parse first response, offset 12 is header size + n = mg_dns_parse_rr(c->recv.buf, c->recv.len, 12, false, &rr); + MG_VERBOSE(("mDNS response parsed, result=%d", (int) n)); + if (n > 0) { + // RFC-6762 Appendix C, RFC2181 11: m(n + 1-63), max 255 + 0x0 + char name[256]; + uint8_t name_len; + struct mg_mdns_resp resp; + memset(&resp, 0, sizeof(resp)); + if (rh->num_answers > mg_htons(1)) MG_DEBUG(("ignoring > 1 answers")); + mg_dns_parse_name(c->recv.buf, c->recv.len, 12, name, sizeof(name)); + name_len = (uint8_t) strlen(name); + MG_VERBOSE(("RR %u %u %s", (unsigned int) rr.atype, + (unsigned int) rr.aclass, name)); + if (rr.alen == 4 && rr.atype == MG_DNS_RTYPE_A && + (rr.aclass & 0x7FFF) == 1) { + resp.addr.is_ip6 = false; + memcpy(resp.addr.addr.ip, (char *) (rh + 1) + n - 4, 4); + MG_DEBUG(("A response from %.*s = %M", name_len, name, mg_print_ip, + &resp.addr)); + // } else if (rr.alen == 16 && rr.atype == MG_DNS_RTYPE_AAAA && + // (rr.aclass & 0x7FFF) == 1) { + // resp.addr.is_ip6 = true; + // memcpy(resp.addr.addr.ip, (char *)(rh + 1) + n - 16], 16); + // MG_DEBUG(("AAAA response from %.*s = %M", name_len, name, + // mg_print_ip, &resp.addr)); + } else { + return; + } + resp.name = mg_str_n(name, name_len); + resp.rr = &rr; + mg_call(c, MG_EV_MDNS_RESP, &resp); + } +} + +static void handle_mdns_record(struct mg_connection *c) { + struct mg_dns_header *h = (struct mg_dns_header *) c->recv.buf; + if (c->recv.len <= 12) return; + if ((h->flags & mg_htons(0xF800)) == 0) { + // flags -> !resp, opcode=0 => query; ignore other opcodes + handle_mdns_query(c); + } else if ((h->flags & mg_htons(0xF800)) == mg_htons(0x8000)) { + // flags -> resp, opcode=0 => response; ignore other opcodes + handle_mdns_response(c); + } +} + +static void mdns_cb(struct mg_connection *c, int ev, void *ev_data) { + struct mdns_data *d, *tmp; + struct mdns_data **head = (struct mdns_data **) &c->mgr->active_mdns_requests; + // mDNS resolver + 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 mdns poll", d->expire, now)); + if (now > d->expire) mg_error(d->c, "mDNS timeout"); // will remove entry + } + } else if (ev == MG_EV_CLOSE) { + for (d = *head; d != NULL; d = tmp) { + tmp = d->next; + mg_error(d->c, "mDNS listener error"); // this will remove entry + } + } else if (ev == MG_EV_MDNS_RESP) { + struct mg_mdns_resp *resp = (struct mg_mdns_resp *) ev_data; + if (resp->rr->atype == MG_DNS_RTYPE_A) { + for (d = *head; d != NULL; d = tmp) { + tmp = d->next; + if (mg_strcasecmp(d->name, resp->name) != 0) continue; + if (d->c->is_resolving) { + resp->addr.port = d->c->rem.port; // Save port + d->c->rem = resp->addr; // Copy resolved address + MG_DEBUG(("%lu %.*s is %M", d->c->id, resp->name.len, resp->name.buf, + mg_print_ip, &d->c->rem)); + mg_connect_resolved(d->c); + } else { + // this should not happen, unless above does not clear c->is_resolving + MG_ERROR(("%lu already resolved", d->c->id)); + } + mdns_free(head, d); + } + } + } else if (ev == MG_EV_READ) { + // generic mDNS[-SD] handling + handle_mdns_record(c); // this will call us back with MG_EV_MDNS_RESP + mg_iobuf_del(&c->recv, 0, c->recv.len); + } + + (void) ev_data; +} + +void mg_multicast_add(struct mg_connection *c, char *ip); +struct mg_connection *mg_mdns_listen(struct mg_mgr *mgr, mg_event_handler_t fn, + void *fn_data) { + struct mg_connection *c = + mg_listen(mgr, "udp://224.0.0.251:5353", fn, fn_data); + if (c == NULL) return NULL; + c->mgr->mdns = c; // Add mDNS entry to enable resolver to use it + c->pfn = mdns_cb, c->pfn_data = fn_data; + mg_multicast_add(c, (char *) "224.0.0.251"); + return c; +} + +static bool mdns_query(struct mg_connection *c, struct mg_str *name, + unsigned int rtype) { + mg_multicast_restore(c, (uint8_t *) &c->loc); + (void) rtype; + return mg_dns_send(c, name, 0, false); +} + +bool mg_mdns_query(struct mg_connection *c, const char *name, + unsigned int rtype) { + struct mg_str name_; + name_.buf = (char *) name, name_.len = strlen(name); + return mdns_query(c, &name_, rtype); +} + +static void sendmdnsreq(struct mg_connection *c, struct mg_str *name, int ms, + struct mg_connection *mdnsc, bool ipv6) { + struct mdns_data *d = NULL; + if (mdnsc == NULL) { + mg_error(c, "no mDNS listener, see mg_mdns_listen()"); + } else if ((d = (struct mdns_data *) mg_calloc(1, sizeof(*d))) == NULL) { + mg_error(c, "resolve OOM"); + } else { + struct mdns_data *reqs = (struct mdns_data *) c->mgr->active_mdns_requests; + d->next = reqs; + c->mgr->active_mdns_requests = d; + d->expire = mg_millis() + (uint64_t) ms; + d->name = mg_strdup(*name); + d->c = c; + c->is_resolving = 1; + MG_VERBOSE( + ("%lu resolving %.*s via mDNS", c->id, (int) name->len, name->buf)); + if (!mdns_query(mdnsc, name, MG_DNS_RTYPE_A)) { + mg_error(c, "mDNS send"); // will remove newly created entry + } + } + (void) ipv6; +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/event.c" +#endif + + + + + + +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 + // 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); +} + +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 +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/flash.c" +#endif + + + + +#ifdef MG_OTA_PUBLIC_KEY + + +#endif + +#if MG_OTA != MG_OTA_NONE && MG_OTA != MG_OTA_CUSTOM + +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 + +bool mg_ota_flash_begin(size_t new_firmware_size, struct mg_flash *flash) { + bool ok = false; + if (s_size) { + MG_ERROR(("OTA already in progress. Call mg_ota_end()")); + } else { + size_t half = flash->size / 2; + s_crc32 = 0; + s_addr = (char *) flash->start + half; + MG_DEBUG(("FW %lu bytes, max %lu", new_firmware_size, half)); + if (new_firmware_size < half) { + 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, half - flash->align)); + } + } + return ok; +} + +bool mg_ota_flash_write(const void *buf, size_t len, struct mg_flash *flash) { + bool ok = false; + if (s_size == 0) { + MG_ERROR(("OTA is not started, call mg_ota_begin()")); + } else { + size_t len_aligned_down = MG_ROUND_DOWN(len, flash->align); + if (len_aligned_down) ok = flash->write_fn(s_addr, buf, len_aligned_down); + if (len_aligned_down < len) { + size_t left = len - len_aligned_down; + char tmp[flash->align]; + memset(tmp, 0xff, sizeof(tmp)); + memcpy(tmp, (char *) buf + len_aligned_down, left); + ok = flash->write_fn(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; +} + +bool mg_ota_flash_end(struct mg_flash *flash) { + char *base = (char *) flash->start + flash->size / 2; + bool ok = false; + int prev_state = MG_OTA_STATE_GET(); + if (s_size) { + size_t size = (size_t) (s_addr - base); + uint32_t crc32 = mg_crc32(0, base, s_size); + if (size == s_size && crc32 == s_crc32) ok = true; + MG_DEBUG(("CRC: %x/%x, size: %lu/%lu, status: %s", s_crc32, crc32, s_size, + size, ok ? "ok" : "fail")); +#ifdef MG_OTA_PUBLIC_KEY + if (ok) { + bool signed_fw = s_size > 68 && + memcmp((uint8_t *) base + s_size - 4, "MGSG", 4) == 0; + if (signed_fw) { + static const uint8_t s_pubkey[] = MG_OTA_PUBLIC_KEY; + uint8_t hash[32]; + size_t fw_size = s_size - 68; // strip 64-byte sig + 4-byte magic + mg_sha256(hash, (uint8_t *) base, fw_size); + ok = mg_uecc_verify(s_pubkey, hash, sizeof(hash), + (uint8_t *) base + fw_size, + mg_uecc_secp256r1()) == 1; + MG_INFO(("Signature: %s", ok ? "ok" : "fail")); + } else { + ok = false; + MG_ERROR(("Unsigned firmware rejected")); + } + } +#endif + s_size = 0; + if (ok) MG_OTA_STATE_SET(MG_OTA_TESTING); + if (ok) ok = flash->swap_fn(); + if (!ok) MG_OTA_STATE_SET(prev_state); // undo state in case of failure + } + MG_INFO(("Finishing OTA: %s", ok ? "ok" : "fail")); + return ok; +} + +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/fmt.c" +#endif + + + + +static bool is_digit(int c) { + return c >= '0' && c <= '9'; +} + +static int addexp(char *buf, int e, int sign) { + int n = 0; + buf[n++] = 'e'; + buf[n++] = (char) sign; + if (e > 400) return 0; + if (e < 10) buf[n++] = '0'; + if (e >= 100) buf[n++] = (char) (e / 100 + '0'), e -= 100 * (e / 100); + if (e >= 10) buf[n++] = (char) (e / 10 + '0'), e -= 10 * (e / 10); + buf[n++] = (char) (e + '0'); + return n; +} + +static int xisinf(double x) { + union { + double f; + uint64_t u; + } ieee754; + ieee754.f = x; + return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) == 0x7ff00000 && + ((unsigned) ieee754.u == 0); +} + +static int xisnan(double x) { + union { + double f; + uint64_t u; + } ieee754; + ieee754.f = x; + return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) + + ((unsigned) ieee754.u != 0) > + 0x7ff00000; +} + +static size_t mg_dtoa(char *dst, size_t dstlen, double d, int width, bool tz) { + char buf[40]; + int i, s = 0, n = 0, e = 0; + double t, mul, saved; + if (d == 0.0) return mg_snprintf(dst, dstlen, "%s", "0"); + if (xisinf(d)) return mg_snprintf(dst, dstlen, "%s", d > 0 ? "inf" : "-inf"); + if (xisnan(d)) return mg_snprintf(dst, dstlen, "%s", "nan"); + if (d < 0.0) d = -d, buf[s++] = '-'; + + // Round + saved = d; + if (tz) { + mul = 1.0; + while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0; + } else { + mul = 0.1; + } + + 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 (tz && (e >= width || e <= -width) && width > 1) { + char exp[6]; + int ne; + n = (int) mg_dtoa(buf + s, sizeof(buf) - (size_t) s, saved / mul, width, tz); + // printf(" --> %.*g %d [%.*s]\n", 10, d / t, e, n, buf); + ne = addexp(exp, e < 0 ? -e : e, e < 0 ? '-' : '+'); + if (s + n + ne >= (int) sizeof(buf)) + n = (int) sizeof(buf) - s - ne - 1; + memcpy(buf + s + n, exp, (size_t) ne); + n += ne; + return mg_snprintf(dst, dstlen, "%.*s", s + n, buf); + } else { + int targ_width = width; + 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); + if (!tz && n > 0) targ_width = width + n; + for (i = 0, t = 0.1; s + n < (int) sizeof(buf) && n < targ_width; i++) { + int ch = (int) (d / t); + buf[s + n++] = (char) (ch + '0'); + d -= ch * t; + t /= 10.0; + } + } + + while (tz && n > 0 && buf[s + n - 1] == '0') n--; // Trim trailing zeroes + if (tz && n > 0 && buf[s + n - 1] == '.') n--; // Trim trailing dot + n += s; + if (n >= (int) sizeof(buf)) n = (int) sizeof(buf) - 1; + buf[n] = '\0'; + return mg_snprintf(dst, dstlen, "%s", buf); +} + +static size_t mg_lld(char *buf, int64_t val, bool is_signed, bool is_hex) { + const char *letters = "0123456789abcdef"; + uint64_t v = (uint64_t) val; + size_t s = 0, n, i; + if (is_signed && val < 0) buf[s++] = '-', v = (uint64_t) (-val); + // This loop prints a number in reverse order. I guess this is because we + // write numbers from right to left: least significant digit comes last. + // Maybe because we use Arabic numbers, and Arabs write RTL? + if (is_hex) { + for (n = 0; v; v >>= 4) buf[s + n++] = letters[v & 15]; + } else { + for (n = 0; v; v /= 10) buf[s + n++] = letters[v % 10]; + } + // Reverse a string + for (i = 0; i < n / 2; i++) { + char t = buf[s + i]; + buf[s + i] = buf[s + n - i - 1], buf[s + n - i - 1] = t; + } + if (val == 0) buf[n++] = '0'; // Handle special case + return n + s; +} + +static size_t scpy(void (*out)(char, void *), void *ptr, char *buf, + size_t len) { + size_t i = 0; + while (i < len && buf[i] != '\0') out(buf[i++], ptr); + return i; +} + +size_t mg_xprintf(void (*out)(char, void *), void *ptr, const char *fmt, ...) { + size_t len = 0; + va_list ap; + va_start(ap, fmt); + len = mg_vxprintf(out, ptr, fmt, &ap); + va_end(ap); + return len; +} + +size_t mg_vxprintf(void (*out)(char, void *), void *param, const char *fmt, + va_list *ap) { + size_t i = 0, n = 0; + while (fmt[i] != '\0') { + if (fmt[i] == '%') { + size_t j, k, x = 0, is_long = 0, w = 0 /* width */, pr = ~0U /* prec */; + char pad = ' ', minus = 0, c = fmt[++i]; + if (c == '#') x++, c = fmt[++i]; + if (c == '-') minus++, c = fmt[++i]; + if (c == '0') pad = '0', c = fmt[++i]; + while (is_digit(c)) w *= 10, w += (size_t) (c - '0'), c = fmt[++i]; + if (c == '.') { + c = fmt[++i]; + if (c == '*') { + pr = (size_t) va_arg(*ap, int); + c = fmt[++i]; + } else { + pr = 0; + while (is_digit(c)) pr *= 10, pr += (size_t) (c - '0'), c = fmt[++i]; + } + } + while (c == 'h') c = fmt[++i]; // Treat h and hh as int + if (c == 'l') { + is_long++, c = fmt[++i]; + if (c == 'l') is_long++, c = fmt[++i]; + } + if (c == 'p') x = 1, is_long = 1; + if (c == 'd' || c == 'u' || c == 'x' || c == 'X' || c == 'p' || + c == 'g' || c == 'f') { + bool s = (c == 'd'), h = (c == 'x' || c == 'X' || c == 'p'); + char tmp[40]; + size_t xl = x ? 2 : 0; + if (c == 'g' || c == 'f') { + double v = va_arg(*ap, double); + if (pr == ~0U) pr = 6; + k = mg_dtoa(tmp, sizeof(tmp), v, (int) pr, c == 'g'); + } else if (is_long == 2) { + int64_t v = va_arg(*ap, int64_t); + k = mg_lld(tmp, v, s, h); + } else if (is_long == 1) { + long v = va_arg(*ap, long); + k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned long) v, s, h); + } else { + int v = va_arg(*ap, int); + k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned) v, s, h); + } + for (j = 0; j < xl && w > 0; j++) w--; + for (j = 0; pad == ' ' && !minus && k < w && j + k < w; j++) + n += scpy(out, param, &pad, 1); + n += scpy(out, param, (char *) "0x", xl); + for (j = 0; pad == '0' && k < w && j + k < w; j++) + n += scpy(out, param, &pad, 1); + n += scpy(out, param, tmp, k); + for (j = 0; pad == ' ' && minus && k < w && j + k < w; j++) + n += scpy(out, param, &pad, 1); + } else if (c == 'm' || c == 'M') { + mg_pm_t f = va_arg(*ap, mg_pm_t); + if (c == 'm') out('"', param); + n += f(out, param, ap); + if (c == 'm') n += 2, out('"', param); + } else if (c == 'c') { + int ch = va_arg(*ap, int); + out((char) ch, param); + n++; + } else if (c == 's') { + char *p = va_arg(*ap, char *); + if (pr == ~0U) pr = p == NULL ? 0 : strlen(p); + for (j = 0; !minus && pr < w && j + pr < w; j++) + n += scpy(out, param, &pad, 1); + n += scpy(out, param, p, pr); + for (j = 0; minus && pr < w && j + pr < w; j++) + n += scpy(out, param, &pad, 1); + } else if (c == '%') { + out('%', param); + n++; + } else { + out('%', param); + out(c, param); + n += 2; + } + i++; + } else { + out(fmt[i], param), n++, i++; + } + } + return n; +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/fs.c" +#endif + + + + + +struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int flags) { + struct mg_fd *fd = (struct mg_fd *) mg_calloc(1, sizeof(*fd)); + if (fd != NULL) { + fd->fd = fs->op(path, flags); + fd->fs = fs; + if (fd->fd == NULL) { + mg_free(fd); + fd = NULL; + } + } + return fd; +} + +void mg_fs_close(struct mg_fd *fd) { + if (fd != NULL) { + fd->fs->cl(fd->fd); + mg_free(fd); + } +} + +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 *) mg_calloc(1, result.len + 1); + if (result.buf != NULL && + fs->rd(fp, (void *) result.buf, result.len) != result.len) { + mg_free((void *) result.buf); + result.buf = NULL; + } + fs->cl(fp); + } + if (result.buf == NULL) result.len = 0; + return result; +} + +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], rnd[10]; + size_t path_len = mg_snprintf(tmp, sizeof(tmp), "%s..%s", path, + mg_random_str(rnd, sizeof(rnd))); + if (path_len < sizeof(tmp) && + (fd = mg_fs_open(fs, tmp, MG_FS_WRITE | MG_FS_EXCL)) != NULL) { + result = fs->wr(fd->fd, buf, len) == len; + mg_fs_close(fd); + if (result) { + fs->rm(path); + result = fs->mv(tmp, path); + } + fs->rm(tmp); + } + return result; +} + +bool mg_file_printf(struct mg_fs *fs, const char *path, const char *fmt, ...) { + va_list ap; + char *data; + bool result = false; + va_start(ap, fmt); + data = mg_vmprintf(fmt, &ap); + va_end(ap); + result = mg_file_write(fs, path, data, strlen(data)); + mg_free(data); + return result; +} + +// 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 + } +} + +bool mg_fs_ls(struct mg_fs *fs, const char *path, char *buf, size_t len) { + struct mg_str s; + s.buf = buf, s.len = len; + fs->ls(path, mg_fs_ls_fn, &s); + return buf[0] != '\0'; +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/fs_fat.c" +#endif + + + + +#if MG_ENABLE_FATFS +#include + +static int mg_days_from_epoch(int y, int m, int d) { + y -= m <= 2; + int era = y / 400; + int yoe = y - era * 400; + int doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; + int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; + return era * 146097 + doe - 719468; +} + +static time_t mg_timegm(const struct tm *t) { + int year = t->tm_year + 1900; + int month = t->tm_mon; // 0-11 + if (month > 11) { + year += month / 12; + month %= 12; + } else if (month < 0) { + int years_diff = (11 - month) / 12; + year -= years_diff; + month += 12 * years_diff; + } + int x = mg_days_from_epoch(year, month + 1, t->tm_mday); + return 60 * (60 * (24L * x + t->tm_hour) + t->tm_min) + t->tm_sec; +} + +static time_t ff_time_to_epoch(uint16_t fdate, uint16_t ftime) { + struct tm tm; + memset(&tm, 0, sizeof(struct tm)); + tm.tm_sec = (ftime << 1) & 0x3e; + tm.tm_min = ((ftime >> 5) & 0x3f); + tm.tm_hour = ((ftime >> 11) & 0x1f); + tm.tm_mday = (fdate & 0x1f); + tm.tm_mon = ((fdate >> 5) & 0x0f) - 1; + tm.tm_year = ((fdate >> 9) & 0x7f) + 80; + return mg_timegm(&tm); +} + +static int ff_stat(const char *path, size_t *size, time_t *mtime) { + FILINFO fi; + if (path[0] == '\0') { + if (size) *size = 0; + if (mtime) *mtime = 0; + return MG_FS_DIR; + } else if (f_stat(path, &fi) == 0) { + if (size) *size = (size_t) fi.fsize; + if (mtime) *mtime = ff_time_to_epoch(fi.fdate, fi.ftime); + return MG_FS_READ | MG_FS_WRITE | ((fi.fattrib & AM_DIR) ? MG_FS_DIR : 0); + } else { + return 0; + } +} + +static void ff_list(const char *dir, void (*fn)(const char *, void *), + void *userdata) { + DIR d; + FILINFO fi; + if (f_opendir(&d, dir) == FR_OK) { + while (f_readdir(&d, &fi) == FR_OK && fi.fname[0] != '\0') { + if (!strcmp(fi.fname, ".") || !strcmp(fi.fname, "..")) continue; + fn(fi.fname, userdata); + } + f_closedir(&d); + } +} + +static void *ff_open(const char *path, int flags) { + FIL *fp = NULL; + unsigned char mode = FA_READ; + if (flags & MG_FS_WRITE) { + mode |= FA_WRITE; + if (flags & MG_FS_EXCL) { + mode |= FA_OPEN_ALWAYS | FA_OPEN_APPEND; + } else { + mode |= FA_CREATE_NEW; + } + } + if ((fp = mg_calloc(1, sizeof(*fp))) != NULL && + f_open(fp, path, mode) != FR_OK) { + mg_free(fp); + fp = NULL; + } + return fp; +} + +static void ff_close(void *fp) { + if (fp != NULL) { + f_close((FIL *) fp); + mg_free(fp); + } +} + +static size_t ff_read(void *fp, void *buf, size_t len) { + UINT n = 0, misalign = ((size_t) buf) & 3; + if (misalign) { + char aligned[4]; + f_read((FIL *) fp, aligned, len > misalign ? misalign : len, &n); + memcpy(buf, aligned, n); + } else { + f_read((FIL *) fp, buf, len, &n); + } + return n; +} + +static size_t ff_write(void *fp, const void *buf, size_t len) { + UINT n = 0; + return f_write((FIL *) fp, (char *) buf, len, &n) == FR_OK ? n : 0; +} + +static size_t ff_seek(void *fp, size_t offset) { + f_lseek((FIL *) fp, offset); + return offset; +} + +static bool ff_rename(const char *from, const char *to) { + return f_rename(from, to) == FR_OK; +} + +static bool ff_remove(const char *path) { + return f_unlink(path) == FR_OK; +} + +static bool ff_mkdir(const char *path) { + return f_mkdir(path) == FR_OK; +} + +struct mg_fs mg_fs_fat = {ff_stat, ff_list, ff_open, ff_close, ff_read, + ff_write, ff_seek, ff_rename, ff_remove, ff_mkdir}; +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/fs_packed.c" +#endif + + + + + +struct packed_file { + const char *data; + size_t size; + size_t pos; +}; + +const struct mg_mem_file *mg_mem_files; + +static int mg_scmp(const char *a, const char *b) { + while (*a && (*a == *b)) a++, b++; + return *(const unsigned char *) a - *(const unsigned char *) b; +} +static const char *mg_unlist(size_t no) { + return mg_mem_files == NULL ? NULL : mg_mem_files[no].path; +} +static const char *mg_unpack(const char *path, size_t *size, time_t *mtime) { + const struct mg_mem_file *p; + for (p = mg_mem_files; p != NULL && p->path != NULL; p++) { + if (mg_scmp(p->path, path) != 0) continue; + if (size != NULL) *size = p->size; + if (mtime != NULL) *mtime = p->mtime; + return (const char *) p->data; + } + return NULL; +} + +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); +} + +static int is_dir_prefix(const char *prefix, size_t n, const char *path) { + // MG_INFO(("[%.*s] [%s] %c", (int) n, prefix, path, path[n])); + return n < strlen(path) && strncmp(prefix, path, n) == 0 && + (n == 0 || path[n] == '/' || path[n - 1] == '/'); +} + +static int packed_stat(const char *path, size_t *size, time_t *mtime) { + const char *p; + size_t i, n = strlen(path); + if (mg_unpack(path, size, mtime)) return MG_FS_READ; // Regular file + // Scan all files. If `path` is a dir prefix for any of them, it's a dir + for (i = 0; (p = mg_unlist(i)) != NULL; i++) { + if (is_dir_prefix(path, n, p)) return MG_FS_DIR; + } + return 0; +} + +static void packed_list(const char *dir, void (*fn)(const char *, void *), + void *userdata) { + char buf[MG_PATH_MAX], tmp[sizeof(buf)]; + const char *path, *begin, *end; + size_t i, n = strlen(dir); + tmp[0] = '\0'; // Previously listed entry + for (i = 0; (path = mg_unlist(i)) != NULL; i++) { + if (!is_dir_prefix(dir, n, path)) continue; + begin = &path[n + 1]; + end = strchr(begin, '/'); + if (end == NULL) end = begin + strlen(begin); + mg_snprintf(buf, sizeof(buf), "%.*s", (int) (end - begin), begin); + buf[sizeof(buf) - 1] = '\0'; + // If this entry has been already listed, skip + // NOTE: we're assuming that file list is sorted alphabetically + if (strcmp(buf, tmp) == 0) continue; + fn(buf, userdata); // Not yet listed, call user function + strcpy(tmp, buf); // And save this entry as listed + } +} + +static void *packed_open(const char *path, int flags) { + size_t size = 0; + const char *data = mg_unpack(path, &size, NULL); + struct packed_file *fp = NULL; + if (data == NULL) return NULL; + if (flags & MG_FS_WRITE) return NULL; + if ((fp = (struct packed_file *) mg_calloc(1, sizeof(*fp))) != NULL) { + fp->size = size; + fp->data = data; + } + return (void *) fp; +} + +static void packed_close(void *fp) { + if (fp != NULL) mg_free(fp); +} + +static size_t packed_read(void *fd, void *buf, size_t len) { + struct packed_file *fp = (struct packed_file *) fd; + if (fp->pos + len > fp->size) len = fp->size - fp->pos; + memcpy(buf, &fp->data[fp->pos], len); + fp->pos += len; + return len; +} + +static size_t packed_write(void *fd, const void *buf, size_t len) { + (void) fd, (void) buf, (void) len; + return 0; +} + +static size_t packed_seek(void *fd, size_t offset) { + struct packed_file *fp = (struct packed_file *) fd; + fp->pos = offset; + if (fp->pos > fp->size) fp->pos = fp->size; + return fp->pos; +} + +static bool packed_rename(const char *from, const char *to) { + (void) from, (void) to; + return false; +} + +static bool packed_remove(const char *path) { + (void) path; + return false; +} + +static bool packed_mkdir(const char *path) { + (void) path; + return false; +} + +struct mg_fs mg_fs_packed = { + packed_stat, packed_list, packed_open, packed_close, packed_read, + packed_write, packed_seek, packed_rename, packed_remove, packed_mkdir}; + +#ifdef MG_ENABLE_LINES +#line 1 "src/fs_posix.c" +#endif + + +#if MG_ENABLE_POSIX_FS + +#ifndef MG_STAT_STRUCT +#define MG_STAT_STRUCT stat +#endif + +#ifndef MG_STAT_FUNC +#define MG_STAT_FUNC stat +#endif + +static int p_stat(const char *path, size_t *size, time_t *mtime) { +#if !defined(S_ISDIR) + MG_ERROR(("stat() API is not supported. %p %p %p", path, size, mtime)); + return 0; +#else +#if MG_ARCH == MG_ARCH_WIN32 + struct _stati64 st; + wchar_t tmp[MG_PATH_MAX]; + MultiByteToWideChar(CP_UTF8, 0, path, -1, tmp, sizeof(tmp) / sizeof(tmp[0])); + if (_wstati64(tmp, &st) != 0) return 0; + // If path is a symlink, windows reports 0 in st.st_size. + // Get a real file size by opening it and jumping to the end + if (st.st_size == 0 && (st.st_mode & _S_IFREG)) { + FILE *fp = _wfopen(tmp, L"rb"); + if (fp != NULL) { + fseek(fp, 0, SEEK_END); + if (ftell(fp) > 0) st.st_size = ftell(fp); // Use _ftelli64 on win10+ + fclose(fp); + } + } +#else + struct MG_STAT_STRUCT st; + if (MG_STAT_FUNC(path, &st) != 0) return 0; +#endif + if (size) *size = (size_t) st.st_size; + if (mtime) *mtime = st.st_mtime; + return MG_FS_READ | MG_FS_WRITE | (S_ISDIR(st.st_mode) ? MG_FS_DIR : 0); +#endif +} + +#if MG_ARCH == MG_ARCH_WIN32 +struct dirent { + char d_name[MAX_PATH]; +}; + +typedef struct win32_dir { + HANDLE handle; + WIN32_FIND_DATAW info; + struct dirent result; +} DIR; + +#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 + +static int to_wchar(const char *path, wchar_t *wbuf, size_t wbuf_len) { + int ret; + char buf[MAX_PATH * 2], buf2[MAX_PATH * 2], *p; + strncpy(buf, path, sizeof(buf)); + buf[sizeof(buf) - 1] = '\0'; + // Trim trailing slashes. Leave backslash for paths like "X:\" + p = buf + strlen(buf) - 1; + while (p > buf && p[-1] != ':' && (p[0] == '\\' || p[0] == '/')) *p-- = '\0'; + memset(wbuf, 0, wbuf_len * sizeof(wchar_t)); + ret = MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len); + // Convert back to Unicode. If doubly-converted string does not match the + // original, something is fishy, reject. + WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2), + NULL, NULL); + if (strcmp(buf, buf2) != 0) { + wbuf[0] = L'\0'; + ret = 0; + } + return ret; +} + +DIR *opendir(const char *name) { + DIR *d = NULL; + wchar_t wpath[MAX_PATH]; + DWORD attrs; + size_t n; + + if (name == NULL) { + SetLastError(ERROR_BAD_ARGUMENTS); + } else if ((d = (DIR *) mg_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)) { + n = wcslen(wpath); + if (n <= (sizeof(wpath) / sizeof(wpath[0])) - 3) { + (void) wcscat(wpath, L"\\*"); + d->handle = FindFirstFileW(wpath, &d->info); + d->result.d_name[0] = '\0'; + } else { + mg_free(d); + d = NULL; + SetLastError(ERROR_BUFFER_OVERFLOW); + } + } else { + mg_free(d); + d = NULL; + } + } + return d; +} + +int closedir(DIR *d) { + int result = 0; + if (d != NULL) { + if (d->handle != INVALID_HANDLE_VALUE) + result = FindClose(d->handle) ? 0 : -1; + mg_free(d); + } else { + result = -1; + SetLastError(ERROR_BAD_ARGUMENTS); + } + return result; +} + +struct dirent *readdir(DIR *d) { + struct dirent *result = NULL; + if (d != NULL) { + memset(&d->result, 0, sizeof(d->result)); + if (d->handle != INVALID_HANDLE_VALUE) { + result = &d->result; + WideCharToMultiByte(CP_UTF8, 0, d->info.cFileName, -1, result->d_name, + sizeof(result->d_name), NULL, NULL); + if (!FindNextFileW(d->handle, &d->info)) { + FindClose(d->handle); + d->handle = INVALID_HANDLE_VALUE; + } + } else { + SetLastError(ERROR_FILE_NOT_FOUND); + } + } else { + SetLastError(ERROR_BAD_ARGUMENTS); + } + return result; +} +#endif + +static void p_list(const char *dir, void (*fn)(const char *, void *), + void *userdata) { +#if MG_ENABLE_DIRLIST + struct dirent *dp; + DIR *dirp; + if ((dirp = (opendir(dir))) == NULL) return; + while ((dp = readdir(dirp)) != NULL) { + if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue; + fn(dp->d_name, userdata); + } + closedir(dirp); +#else + (void) dir, (void) fn, (void) userdata; +#endif +} + +static void *p_open(const char *path, int flags) { +#if MG_ARCH == MG_ARCH_WIN32 + const char *mode = flags == MG_FS_READ ? "rb" + : (flags & MG_FS_EXCL) ? "wxb" + : "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" + : (flags & MG_FS_EXCL) ? "wxbe" + : "a+be"; // e for CLOSEXEC + 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 == '\t' || 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; + int cl_count = 0, te_count = 0, auth_count = 0; + int conn_count = 0, cookie_count = 0; + 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[0] == '\t')) s++; // Skip spaces + if ((s = skiptorn(s, end, &v)) == NULL) return false; + while (v.len > 0 && (v.buf[v.len - 1] == ' ' || v.buf[v.len - 1] == '\t')) { + v.len--; // Trim spaces + } + // detect duplicated headers -> discard + if (((mg_strcasecmp(k, mg_str("Content-Length")) == 0) && + (++cl_count > 1)) || + ((mg_strcasecmp(k, mg_str("Transfer-Encoding")) == 0) && + (++te_count > 1)) || + ((mg_strcasecmp(k, mg_str("Authorization")) == 0) && + (++auth_count > 1)) || + ((mg_strcasecmp(k, mg_str("Cookie")) == 0) && (++cookie_count > 1)) || + ((mg_strcasecmp(k, mg_str("Connection")) == 0) && (++conn_count > 1))) + return false; + // 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; + bool version_prefix_valid; + + 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 + is_response = + hm->method.len > 5 && (mg_ncasecmp(hm->method.buf, "HTTP/", 5) == 0); + if ((s = skiptorn(s, end, &hm->proto)) == NULL) return false; + // If we're given a version, check that it is HTTP/x.x + version_prefix_valid = + hm->proto.len > 5 && (mg_ncasecmp(hm->proto.buf, "HTTP/", 5) == 0); + if (!is_response && !version_prefix_valid) + return -1; // no version detected in request + if (!is_response && hm->proto.len > 0 && + (!version_prefix_valid || hm->proto.len != 8 || + (hm->proto.buf[5] < '0' || hm->proto.buf[5] > '9') || + (hm->proto.buf[6] != '.') || + (hm->proto.buf[7] < '0' || hm->proto.buf[7] > '9'))) { + return -1; + } + + // 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 + cl = mg_http_get_header(hm, "Content-Length"); + if (cl != NULL && mg_http_get_header(hm, "Transfer-Encoding") != NULL) + return -1; // cannot contain both CL and TE + if (cl != 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, if Content-Length is not specified + // set body length to 0. + if (hm->body.len == (size_t) ~0 && !is_response) { + 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; + if (!mg_send(c, " \r\n", 10)) mg_error(c, "OOM"); + 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 + } + if (!mg_send(c, "\r\n", 2)) mg_error(c, "OOM"); +} + +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); + if (!mg_send(c, buf, len) || !mg_send(c, "\r\n", 2)) mg_error(c, "OOM"); + 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("mjs"), 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), asterisk = mg_str_n("*", 1); + 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(asterisk, k) == 0 || 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 && opts->fs ? opts->fs : &mg_fs_posix; + struct mg_fd *fd = NULL; + size_t size = 0; + time_t mtime = 0; + const char *mime_types = opts && opts->mime_types ? opts->mime_types : NULL; + const char *hdrs = opts && opts->extra_headers ? opts->extra_headers : ""; + struct mg_str *inm = NULL; + struct mg_str mime = guess_content_type(mg_str(path), 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) { + if (mg_match(*ae, mg_str("*gzip*"), NULL)) { + mg_snprintf(tmp, sizeof(tmp), "%s.gz", path); + fd = mg_fs_open(fs, tmp, MG_FS_READ); + if (fd != NULL) gzip = true, path = tmp; + } + } + // No luck opening .gz? Open what we've told to open + if (fd == NULL) fd = mg_fs_open(fs, path, MG_FS_READ); + } + + // Failed to open, and page404 is configured? Open it, then + if (fd == NULL && opts && opts->page404) { + fd = mg_fs_open(fs, opts->page404, MG_FS_READ); + path = opts->page404; + mime = guess_content_type(mg_str(path), mime_types); + } + + if (fd == NULL || fs->st(path, &size, &mtime) == 0) { + mg_http_reply(c, 404, hdrs, "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, hdrs, ""); + } 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, hdrs); + if (mg_strcasecmp(hm->method, mg_str("HEAD")) == 0 || c->is_closing) { + c->is_resp = 0; + mg_fs_close(fd); + } else { // start serving static content only if not closing, see #3354 + // 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 +// Print file name, escaping HTML chars +static size_t html_esc(void (*fn)(char, void *), void *arg, va_list *ap) { + const char *s = va_arg(*ap, const char *); + size_t i, len = 0; + for (i = 0; s[i] != '\0'; i++) { + if (s[i] == '<') { + len += mg_xprintf(fn, arg, "%s", "<"); + } else if (s[i] == '>') { + len += mg_xprintf(fn, arg, "%s", ">"); + } else if (s[i] == '&') { + len += mg_xprintf(fn, arg, "%s", "&"); + } else { + len += mg_xprintf(fn, arg, "%c", s[i]); + } + } + return len; +} + +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->c->id, path)); + } 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, + " %M%s" + "%s%s\n", + n, path, slash, html_esc, 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 = + ""; + 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, + "Index of %M%s%s" + "" + "

Index of %M

" + "" + "" + "" + "" + "\n", + mg_print_html_esc, (int) uri.len, uri.buf, sort_js_code, sort_js_code2, + mg_print_html_esc, (int) uri.len, uri.buf); + mg_printf(c, "%s", + " " + "\n"); + fs->ls(dir, printdirentry, &d); + mg_printf(c, + "" + "
Name" + "ModifiedSize

..[DIR]

Mongoose v.%s
\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 + n = strlen(path); + if (!mg_path_is_sane(mg_str_n(path, n))) { + mg_http_reply(c, 400, "", "Invalid path"); + return -1; + } + 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.buf); + c->is_resp = 0; + flags = -1; + } else if (flags & MG_FS_DIR) { + if (((mg_snprintf(path + n, path_size - n, "/" MG_HTTP_INDEX) > 0 && + (tmp = fs->st(path, NULL, NULL)) != 0) || + (mg_snprintf(path + n, path_size - n, "/index.shtml") > 0 && + (tmp = fs->st(path, NULL, NULL)) != 0))) { + flags = tmp; + } else if ((mg_snprintf(path + n, path_size - n, "/" MG_HTTP_INDEX ".gz") > + 0 && + (tmp = fs->st(path, NULL, NULL)) != + 0)) { // check for gzipped index + flags = tmp; + path[n + 1 + strlen(MG_HTTP_INDEX)] = + '\0'; // Remove appended .gz in index file name + } else { + path[n] = '\0'; // Remove appended index file name + } + } + return flags; +} + +static int uri_to_path(struct mg_connection *c, struct mg_http_message *hm, + const struct mg_http_serve_opts *opts, char *path, + size_t path_size) { + struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs; + struct mg_str k, v, 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 && p < x && *p == '"' ? 1 : 0)))); + } + } + 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, ¤t_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)", path); + 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); +} + +struct mg_upload_priv { + size_t expected; + size_t received; + struct mg_fd *fd; // non-NULL: file upload; NULL: OTA + void (*fn)(struct mg_connection *, const char *); +}; + +static void mg_upload_handler(struct mg_connection *c, int ev, void *ev_data) { + struct mg_upload_priv *p = (struct mg_upload_priv *) c->data; + if (p->fn == NULL) return; + if (ev == MG_EV_READ && c->recv.len > 0) { + size_t alignment = 512; + size_t left = p->expected > p->received ? p->expected - p->received : 0; + size_t aligned = c->recv.len < left ? MG_ROUND_DOWN(c->recv.len, alignment) + : c->recv.len; + bool ok = true; + if (aligned > 0) { + if (p->fd != NULL) { + ok = p->fd->fs->wr(p->fd->fd, c->recv.buf, aligned) == aligned; + } else { + ok = mg_ota_write(c->recv.buf, aligned); + } + } + p->received += aligned; + mg_iobuf_del(&c->recv, 0, aligned); + if (!ok) { + if (p->fd != NULL) { mg_fs_close(p->fd); p->fd = NULL; } + else mg_ota_end(); + p->fn(c, "write error"); + mg_free(c->pfn_data); c->pfn_data = NULL; p->fn = NULL; + } else if (p->received >= p->expected) { + const char *errmsg = NULL; + if (p->fd != NULL) { mg_fs_close(p->fd); p->fd = NULL; } + else if (!mg_ota_end()) errmsg = "OTA finalize failed"; + p->fn(c, errmsg); + mg_free(c->pfn_data); c->pfn_data = NULL; p->fn = NULL; + } + } else if (ev == MG_EV_ERROR || ev == MG_EV_CLOSE) { + if (p->fd != NULL) { mg_fs_close(p->fd); p->fd = NULL; } + else mg_ota_end(); + p->fn(c, ev == MG_EV_ERROR ? (const char *) ev_data : "connection closed"); + mg_free(c->pfn_data); c->pfn_data = NULL; p->fn = NULL; + } + (void) ev_data; +} + +static void mg_upload_default_cb(struct mg_connection *c, const char *status) { + MG_INFO(("%lu %s", c->id, status ? status : "ok")); + mg_http_reply(c, status ? 500 : 200, "", "%s\n", status ? status : "ok"); +} + +void mg_http_start_upload(struct mg_connection *c, struct mg_http_message *hm, + struct mg_str name, struct mg_str dir, + struct mg_fs *fs, + void (*fn)(struct mg_connection *, const char *)) { + struct mg_upload_priv *p = (struct mg_upload_priv *) c->data; + char path[MG_PATH_MAX]; + struct mg_fd *fd; + if (fn == NULL) fn = mg_upload_default_cb; + if (sizeof(*p) > sizeof(c->data)) { fn(c, "data too small"); return; } + if (!mg_path_is_sane(name)) { fn(c, "bad name"); return; } + mg_snprintf(path, sizeof(path), "%.*s%c%.*s", (int) dir.len, dir.buf, + MG_DIRSEP, (int) name.len, name.buf); + fd = mg_fs_open(fs, path, MG_FS_WRITE); + if (fd == NULL) { fn(c, "open failed"); return; } + p->expected = hm->body.len; + p->received = 0; + p->fd = fd; + p->fn = fn; + c->fn = mg_upload_handler; + c->pfn_data = strdup(path); + c->pfn = NULL; + mg_iobuf_del(&c->recv, 0, hm->head.len); + mg_call(c, MG_EV_READ, &c->recv.len); +} + +void mg_http_start_ota(struct mg_connection *c, struct mg_http_message *hm, + void (*fn)(struct mg_connection *, const char *)) { + struct mg_upload_priv *p = (struct mg_upload_priv *) c->data; + if (fn == NULL) fn = mg_upload_default_cb; + if (sizeof(*p) > sizeof(c->data)) { fn(c, "data too small"); return; } + if (!mg_ota_begin(hm->body.len)) { fn(c, "ota begin failed"); return; } + p->expected = hm->body.len; + p->received = 0; + p->fd = NULL; + p->fn = fn; + c->fn = mg_upload_handler; + c->pfn = NULL; + mg_iobuf_del(&c->recv, 0, hm->head.len); + mg_call(c, MG_EV_READ, &c->recv.len); +} + +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 + 2 || 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 || + (ev == MG_EV_POLL && c->is_accepted && !c->is_draining && + c->recv.len > 0)) { // see #2796 + 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, is_http_1_0 = false; + size_t old_len = c->recv.len; + 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 (c->recv.len != old_len) { + // User manipulated received data. Wash our hands + MG_DEBUG(("%lu detaching HTTP handler", c->id)); + c->pfn = NULL; + return; + } + 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); + } + is_http_1_0 = + hm.proto.len == 8 && mg_ncasecmp(hm.proto.buf, "HTTP/1.0", 8) == 0; + // HTTP/1.0 does not use "Transfer-Encoding: chunked" + if (!is_http_1_0 && + (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 (unless the body length is 0, in + // which case it can be omitted). Other requests can also contain a + // body, but their content has no defined semantics (RFC 7231) + if (hm.body.len != 0) require_content_len = true; + ofs += (size_t) n; // this request has been processed + } 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) { + if (!c->is_client) mg_http_reply(c, 411, "", ""); + MG_ERROR(("Content length missing from %s", + is_response ? "response" : "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 (c->is_accepted && !c->is_resp) { + struct mg_str *cc = mg_http_get_header(&hm, "Connection"); + if (cc != NULL && mg_strcasecmp(*cc, mg_str("close")) == 0) { + c->is_draining = 1; // honor "Connection: close" + break; + } + } + } + if (ofs > 0) mg_iobuf_del(&c->recv, 0, ofs); // Delete processed data + } + (void) ev_data; +} + +struct mg_connection *mg_http_connect(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data) { + return mg_connect_svc(mgr, url, fn, fn_data, http_cb, NULL); +} + +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; +} + +bool mg_iobuf_resize(struct mg_iobuf *io, size_t new_size) { + bool ok = true; + new_size = roundup(new_size, io->align); + if (new_size == 0) { + mg_bzero(io->buf, io->size); + mg_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 mg_calloc/mg_free only + void *p = mg_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); + mg_free(io->buf); + io->buf = (unsigned char *) p; + io->size = new_size; + io->len = len; + } else { + ok = false; + MG_ERROR(("%lld->%lld", (uint64_t) io->size, (uint64_t) new_size)); + } + } + return ok; +} + +bool 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 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'); + // use fast exponentiation + // https://en.wikipedia.org/wiki/Exponentiation_by_squaring + if (exp != 0) { + double x = 10, y = 1; + if (exp > 308) exp = 308; + if (minus) x = 0.1; + while (exp > 1) { + if (exp & 1) { + y *= x; + --exp; + } + x *= x; + exp >>= 1; + } + d *= x * y; + } + } + + 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; +} + +size_t mg_json_unescape(struct mg_str json, const char *path, char *to, + size_t n) { + struct mg_str s = mg_json_get_tok(json, path); + size_t res = 0, i, j; + if (s.len > 1 && s.buf[0] == '"') { // Is is a string? + s.len -= 2, s.buf++; // Trim surrounding double-quotes + 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) + break; + 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) res = j; + if (n > 0) to[j < n ? j : n - 1] = '\0'; + } + return res; +} + +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 >= 2 && json.buf[off] == '"') { + if ((result = (char *) mg_calloc(1, (size_t) len)) != NULL && + len > 2 && mg_json_unescape(json, path, result, (size_t) len) == 0) { + mg_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 *) mg_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 *) mg_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/l2.c" +#endif + + + + +#if MG_ENABLE_TCPIP + +// L2 API +void mg_l2_init(struct mg_tcpip_if *ifp); +bool mg_l2_poll(struct mg_tcpip_if *ifp, bool expired_1000ms); +uint8_t *mg_l2_header(struct mg_tcpip_if *ifp, enum mg_l2proto proto, + uint8_t *src, uint8_t *dst, uint8_t *frame); +size_t mg_l2_trailer(struct mg_tcpip_if *ifp, size_t len, uint8_t *cur); +bool mg_l2_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto, + struct mg_str *pay, struct mg_str *raw); +uint8_t *mg_l2_getaddr(struct mg_tcpip_if *ifp, uint8_t *frame); +uint8_t *mg_l2_mapip(enum mg_l2type type, enum mg_l2addrtype addrtype, + struct mg_addr *ip); +#if MG_ENABLE_IPV6 +bool mg_l2_genip6(enum mg_l2type type, uint64_t *ip6, uint8_t prefix_len, + uint8_t *addr); +bool mg_l2_ip6get(enum mg_l2type type, uint8_t *addr, uint8_t *opts, + uint8_t len); +uint8_t mg_l2_ip6put(enum mg_l2type type, uint8_t *addr, uint8_t *opts); +#endif +size_t mg_l2_driver_output(struct mg_tcpip_if *ifp, size_t len); + +// clang-format off +extern void mg_l2_eth_init(struct mg_tcpip_if *); +extern bool mg_l2_eth_poll(struct mg_tcpip_if *, bool); +extern uint8_t *mg_l2_eth_header(struct mg_tcpip_if *, enum mg_l2proto, struct mg_l2addr *, struct mg_l2addr *, uint8_t *); +extern size_t mg_l2_eth_trailer(struct mg_tcpip_if *, size_t, uint8_t *); +extern bool mg_l2_eth_rx(struct mg_tcpip_if *, enum mg_l2proto *, struct mg_str *, struct mg_str *); +extern struct mg_l2addr *mg_l2_eth_getaddr(struct mg_tcpip_if *, uint8_t *); +extern struct mg_l2addr *mg_l2_eth_mapip(enum mg_l2addrtype, struct mg_addr *); +#if MG_ENABLE_IPV6 +extern bool mg_l2_eth_genip6(uint64_t *, uint8_t, struct mg_l2addr *); +extern bool mg_l2_eth_ip6get(struct mg_l2addr *, uint8_t *, uint8_t); +extern uint8_t mg_l2_eth_ip6put(struct mg_l2addr *, uint8_t *); +#endif + +extern void mg_l2_ppp_init(struct mg_tcpip_if *); +extern bool mg_l2_ppp_poll(struct mg_tcpip_if *, bool); +extern uint8_t *mg_l2_ppp_header(struct mg_tcpip_if *, enum mg_l2proto, struct mg_l2addr *, struct mg_l2addr *, uint8_t *); +extern size_t mg_l2_ppp_trailer(struct mg_tcpip_if *, size_t, uint8_t *); +extern bool mg_l2_ppp_rx(struct mg_tcpip_if *, enum mg_l2proto *, struct mg_str *, struct mg_str *); +extern struct mg_l2addr *mg_l2_ppp_getaddr(struct mg_tcpip_if *, uint8_t *); +extern struct mg_l2addr *mg_l2_ppp_mapip(enum mg_l2addrtype, struct mg_addr *); +#if MG_ENABLE_IPV6 +extern bool mg_l2_ppp_genip6(uint64_t *, uint8_t, struct mg_l2addr *); +extern bool mg_l2_ppp_ip6get(struct mg_l2addr *, uint8_t *, uint8_t); +extern uint8_t mg_l2_ppp_ip6put(struct mg_l2addr *, uint8_t *); +#endif +extern void mg_l2_pppoe_init(struct mg_tcpip_if *); +extern bool mg_l2_pppoe_poll(struct mg_tcpip_if *, bool); +extern uint8_t *mg_l2_pppoe_header(struct mg_tcpip_if *, enum mg_l2proto, struct mg_l2addr *, struct mg_l2addr *, uint8_t *); +extern size_t mg_l2_pppoe_trailer(struct mg_tcpip_if *, size_t, uint8_t *); +extern bool mg_l2_pppoe_rx(struct mg_tcpip_if *, enum mg_l2proto *, struct mg_str *, struct mg_str *); + +typedef void (*l2_init_fn)(struct mg_tcpip_if *); +typedef bool (*l2_poll_fn)(struct mg_tcpip_if *, bool); +typedef uint8_t *((*l2_header_fn)(struct mg_tcpip_if *, enum mg_l2proto, struct mg_l2addr *, struct mg_l2addr *, uint8_t *)); +typedef size_t (*l2_trailer_fn)(struct mg_tcpip_if *, size_t, uint8_t *); +typedef bool (*l2_rx_fn)(struct mg_tcpip_if *, enum mg_l2proto *, struct mg_str *, struct mg_str *); +typedef struct mg_l2addr (*(*l2_getaddr_fn)(struct mg_tcpip_if *, uint8_t *)); +typedef struct mg_l2addr (*(*l2_mapip_fn)(enum mg_l2addrtype, struct mg_addr *)); +#if MG_ENABLE_IPV6 +typedef bool (*l2_genip6_fn)(uint64_t *, uint8_t, struct mg_l2addr *); +typedef bool (*l2_ip6get_fn)(struct mg_l2addr *, uint8_t *, uint8_t); +typedef uint8_t (*l2_ip6put_fn)(struct mg_l2addr *, uint8_t *); +#endif +// clang-format on + +static const l2_init_fn l2_init[] = {mg_l2_eth_init, mg_l2_ppp_init, + mg_l2_pppoe_init}; +static const l2_poll_fn l2_poll[] = {mg_l2_eth_poll, mg_l2_ppp_poll, + mg_l2_pppoe_poll}; +static const l2_header_fn l2_header[] = {mg_l2_eth_header, mg_l2_ppp_header, + mg_l2_pppoe_header}; +static const l2_trailer_fn l2_trailer[] = {mg_l2_eth_trailer, mg_l2_ppp_trailer, + mg_l2_pppoe_trailer}; +static const l2_rx_fn l2_rx[] = {mg_l2_eth_rx, mg_l2_ppp_rx, mg_l2_pppoe_rx}; +static const l2_getaddr_fn l2_getaddr[] = {mg_l2_eth_getaddr, mg_l2_ppp_getaddr, + mg_l2_ppp_getaddr}; +static const l2_mapip_fn l2_mapip[] = {mg_l2_eth_mapip, mg_l2_ppp_mapip, + mg_l2_ppp_mapip}; +#if MG_ENABLE_IPV6 +static const l2_genip6_fn l2_genip6[] = {mg_l2_eth_genip6, mg_l2_ppp_genip6, + mg_l2_ppp_genip6}; +static const l2_ip6get_fn l2_ip6get[] = {mg_l2_eth_ip6get, mg_l2_ppp_ip6get, + mg_l2_ppp_ip6get}; +static const l2_ip6put_fn l2_ip6put[] = {mg_l2_eth_ip6put, mg_l2_ppp_ip6put, + mg_l2_ppp_ip6put}; +#endif + +void mg_l2_init(struct mg_tcpip_if *ifp) { + l2_init[ifp->l2type](ifp); +} + +bool mg_l2_poll(struct mg_tcpip_if *ifp, bool expired_1000ms) { + return l2_poll[ifp->l2type](ifp, expired_1000ms); +} + +uint8_t *mg_l2_header(struct mg_tcpip_if *ifp, enum mg_l2proto proto, + uint8_t *src, uint8_t *dst, uint8_t *frame) { + return l2_header[ifp->l2type](ifp, proto, (struct mg_l2addr *) src, + (struct mg_l2addr *) dst, frame); +} + +size_t mg_l2_trailer(struct mg_tcpip_if *ifp, size_t len, uint8_t *frame) { + return l2_trailer[ifp->l2type](ifp, len, frame); +} + +bool mg_l2_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto, + struct mg_str *pay, struct mg_str *raw) { + return l2_rx[ifp->l2type](ifp, proto, pay, raw); +} + +uint8_t *mg_l2_getaddr(struct mg_tcpip_if *ifp, uint8_t *frame) { + return (uint8_t *) l2_getaddr[ifp->l2type](ifp, frame); +} + +struct mg_l2addr s_mapip; + +uint8_t *mg_l2_mapip(enum mg_l2type type, enum mg_l2addrtype addrtype, + struct mg_addr *ip) { + return (uint8_t *) l2_mapip[type](addrtype, ip); +} + +#if MG_ENABLE_IPV6 +bool mg_l2_genip6(enum mg_l2type type, uint64_t *ip6, uint8_t prefix_len, + uint8_t *addr) { + return l2_genip6[type](ip6, prefix_len, (struct mg_l2addr *) addr); +} + +bool mg_l2_ip6get(enum mg_l2type type, uint8_t *addr, uint8_t *opts, + uint8_t len) { + return l2_ip6get[type]((struct mg_l2addr *) addr, opts, len); +} +uint8_t mg_l2_ip6put(enum mg_l2type type, uint8_t *addr, uint8_t *opts) { + return l2_ip6put[type]((struct mg_l2addr *) addr, opts); +} +#endif + +size_t mg_l2_driver_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; +} + +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/l2_eth.c" +#endif + + + + + + + +#if MG_ENABLE_TCPIP + +#if defined(__DCC__) +#pragma pack(1) +#else +#pragma pack(push, 1) +#endif + +struct eth { + uint8_t dst[6]; // Destination MAC address + uint8_t src[6]; // Source MAC address + uint16_t type; // Ethernet type +}; + +struct qtag { + uint16_t tpid; // Ethernet 802.1Q type + uint16_t tci; // PCP, DEI, VLAN id +#define VLAN_ID(x) ((uint16_t) ((x) &0x0FFF)) +}; + +#if defined(__DCC__) +#pragma pack(0) +#else +#pragma pack(pop) +#endif + +static const uint16_t eth_types[] = { + // order is vital, see l2.h + 0x800, // IPv4 + 0x86dd, // IPv6 + 0x806, // ARP + 0x8863, // PPPoE Discovery Stage + 0x8864 // PPPoE Session Stage +}; + +void mg_l2_eth_init(struct mg_tcpip_if *ifp) { + struct mg_l2addr *l2addr = (struct mg_l2addr *) ifp->mac; + // If MAC is not set, make a random one + if (l2addr->addr.mac[0] == 0 && l2addr->addr.mac[1] == 0 && + l2addr->addr.mac[2] == 0 && l2addr->addr.mac[3] == 0 && + l2addr->addr.mac[4] == 0 && l2addr->addr.mac[5] == 0) { + l2addr->addr.mac[0] = 0x02; // Locally administered, unicast + mg_random(&l2addr->addr.mac[1], sizeof(l2addr->addr.mac) - 1); + MG_INFO( + ("MAC not set. Generated random: %M", mg_print_mac, l2addr->addr.mac)); + } + ifp->l2mtu = 1500; + ifp->framesize = 1540; +} + +bool mg_l2_eth_poll(struct mg_tcpip_if *ifp, bool expired_1000ms) { + (void) ifp; + (void) expired_1000ms; + return true; +} + +uint8_t *mg_l2_eth_header(struct mg_tcpip_if *ifp, enum mg_l2proto proto, + struct mg_l2addr *src, struct mg_l2addr *dst, + uint8_t *frame) { + struct eth_data *d = &ifp->l2data.eth; + struct eth *eth = (struct eth *) frame; + uint8_t *hlp; + hlp = (uint8_t *) (eth + 1); + memcpy(eth->src, src->addr.mac, sizeof(eth->dst)); + memcpy(eth->dst, dst->addr.mac, sizeof(eth->dst)); + if (d->vlan_id == 0) { // Traditional plain frame + eth->type = mg_htons(eth_types[(unsigned int) proto]); + } else { // Add 802.1Q tag + struct qtag *qtag = (struct qtag *) ð->type; + qtag->tpid = mg_htons(0x8100); + qtag->tci = mg_htons(d->vlan_id); // PCP = default (best-effort) + hlp += sizeof(*qtag); + MG_STORE_BE16(qtag + 1, eth_types[(unsigned int) proto]); + } + return hlp; +} + +size_t mg_l2_eth_trailer(struct mg_tcpip_if *ifp, size_t len, uint8_t *cur) { + struct eth_data *d = &ifp->l2data.eth; + // there is no len field in Ethernet, CRC is hw-calculated; pad to 64 - CRC + size_t ethlen = + len + sizeof(struct eth) + (d->vlan_id != 0 ? sizeof(struct qtag) : 0); + (void) cur; + return ethlen >= 60 ? ethlen : 60; +} + +struct mg_l2addr *mg_l2_eth_mapip(enum mg_l2addrtype addrtype, + struct mg_addr *addr); + +bool mg_l2_eth_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto, + struct mg_str *pay, struct mg_str *raw) { + struct eth *eth = (struct eth *) raw->buf; + struct eth_data *d = &ifp->l2data.eth; + uint16_t type, len; + unsigned int i; + size_t hdrlen = + sizeof(struct eth) + (d->vlan_id != 0 ? sizeof(struct qtag) : 0); + if (raw->len < hdrlen) return false; // Truncated - runt? + len = (uint16_t) raw->len; + if (d->vlan_id == 0) { // We don't handle VLANs + type = mg_ntohs(eth->type); + } else { // We do, check 802.1Q tag + struct qtag *qtag = (struct qtag *) ð->type; + if (qtag->tpid != mg_htons(0x8100)) return false; // Untagged frame + if (VLAN_ID(mg_ntohs(qtag->tci)) != VLAN_ID(d->vlan_id)) + return false; // Not our VLAN + type = MG_LOAD_BE16(qtag + 1); + } + if (ifp->enable_mac_check && + memcmp(eth->dst, ifp->mac, sizeof(eth->dst)) != 0 && + memcmp(eth->dst, mg_l2_eth_mapip(MG_TCPIP_L2ADDR_BCAST, NULL), + sizeof(eth->dst)) != 0) + return false; // TODO(): add multicast addresses + if (ifp->enable_fcs_check && len > hdrlen + 4) { + uint32_t crc; + len -= 4; // TODO(scaprile): check on bigendian + crc = mg_crc32(0, (const char *) raw->buf, len); + if (memcmp((void *) ((size_t) raw->buf + len), &crc, sizeof(crc))) + return false; + } + pay->buf = ((char *) eth) + hdrlen; + pay->len = len - hdrlen; + for (i = 0; i < sizeof(eth_types) / sizeof(uint16_t); i++) { + if (type == eth_types[i]) break; + } + if (i == sizeof(eth_types) / sizeof(eth_types[0])) { + MG_DEBUG(("Unknown eth type %x", type)); + if (mg_log_level >= MG_LL_VERBOSE) + mg_hexdump(raw->buf, raw->len >= 32 ? 32 : raw->len); + return false; + } + *proto = (enum mg_l2proto) i; + return true; +} + +struct mg_l2addr *mg_l2_eth_getaddr(struct mg_tcpip_if *ifp, uint8_t *frame) { + struct eth *eth = (struct eth *) frame; + (void) ifp; // address field is before a possible 802.1Q tag + return (struct mg_l2addr *) ð->src; +} + +extern struct mg_l2addr s_mapip; + +struct mg_l2addr *mg_l2_eth_mapip(enum mg_l2addrtype addrtype, + struct mg_addr *addr) { + switch (addrtype) { + case MG_TCPIP_L2ADDR_BCAST: + memset(s_mapip.addr.mac, 0xff, sizeof(s_mapip.addr.mac)); + break; + case MG_TCPIP_L2ADDR_MCAST: { + uint8_t *ip = (uint8_t *) &addr->addr.ip4; + // IP multicast group MAC, RFC-1112 6.4 + s_mapip.addr.mac[0] = 0x01, s_mapip.addr.mac[1] = 0x00, + s_mapip.addr.mac[2] = 0x5E; + s_mapip.addr.mac[3] = ip[1] & 0x7F; // 23 LSb + s_mapip.addr.mac[4] = ip[2]; + s_mapip.addr.mac[5] = ip[3]; + break; + } + case MG_TCPIP_L2ADDR_MCAST6: { + // IPv6 multicast address mapping, RFC-2464 7 + uint8_t *ip = (uint8_t *) &addr->addr.ip6; + s_mapip.addr.mac[0] = 0x33, s_mapip.addr.mac[1] = 0x33; + s_mapip.addr.mac[2] = ip[12], s_mapip.addr.mac[3] = ip[13], + s_mapip.addr.mac[4] = ip[14], s_mapip.addr.mac[5] = ip[15]; + break; + } + } + return &s_mapip; +} + +#if MG_ENABLE_IPV6 +static void meui64(uint8_t *addr, uint8_t *mac) { + *addr++ = *mac++ ^ (uint8_t) 0x02, *addr++ = *mac++, *addr++ = *mac++; + *addr++ = 0xff, *addr++ = 0xfe; + *addr++ = *mac++, *addr++ = *mac++, *addr = *mac; +} + +bool mg_l2_eth_genip6(uint64_t *ip6, uint8_t prefix_len, + struct mg_l2addr *l2addr) { + if (prefix_len > 64) { + MG_ERROR(("Prefix length > 64, UNSUPPORTED")); + return false; + } + ip6[0] = 0; + meui64(((uint8_t *) &ip6[1]), l2addr->addr.mac); // RFC-4291 2.5.4, 2.5.1 + return true; +} + +bool mg_l2_eth_ip6get(struct mg_l2addr *l2addr, uint8_t *opts, uint8_t len) { + if (len != 1) return false; + memcpy(l2addr->addr.mac, opts, 6); + return true; +} + +uint8_t mg_l2_eth_ip6put(struct mg_l2addr *l2addr, uint8_t *opts) { + memcpy(opts, l2addr->addr.mac, 6); + return 1; +} +#endif + +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/l2_ppp.c" +#endif + + + + + + + +#if MG_ENABLE_TCPIP + +#if defined(__DCC__) +#pragma pack(1) +#else +#pragma pack(push, 1) +#endif +// all in network order + +struct ppp { // RFC-1661 + uint16_t proto; +}; + +struct lcp { // RFC-1661 + uint8_t code, id; + uint16_t len; +}; + +struct ipcp { // RFC-1332 + uint8_t code, id; + uint16_t len; +}; + +struct ipv6cp { // RFC-5072 + uint8_t code, id; + uint16_t len; +}; + +struct hdlc_ { // RFC-1662, "PPP in HDLC-like Framing" + uint8_t addr, ctrl; +}; + +struct pppoe { // RFC-2516, "A Method for Transmitting PPP Over Ethernet + // (PPPoE)" + uint8_t vertype, code; + uint16_t id, len; +}; + +#if defined(__DCC__) +#pragma pack(0) +#else +#pragma pack(pop) +#endif + +#define MG_PPP_ADDR 0xff +#define MG_PPP_CTRL 0x03 + +#define MG_PPP_PROTO_IP 0x0021 +#define MG_PPP_PROTO_IPV6 0x0057 +#define MG_PPP_PROTO_LCP 0xc021 +#define MG_PPP_PROTO_IPCP 0x8021 +#define MG_PPP_PROTO_IPV6CP 0x8057 +#define MG_PPP_PROTO_PAP 0xc023 +#define MG_PPP_PROTO_CHAP 0xc223 + +#define MG_PPP_LCP_CFG_REQ 1 +#define MG_PPP_LCP_CFG_ACK 2 +#define MG_PPP_LCP_CFG_NACK 3 +#define MG_PPP_LCP_CFG_REJECT 4 +#define MG_PPP_LCP_CFG_TERM_REQ 5 +#define MG_PPP_LCP_CFG_TERM_ACK 6 +#define MG_PPP_LCP_REJECT 8 +#define MG_PPP_LCP_ECHO_REQ 9 +#define MG_PPP_LCP_ECHO_REPLY 10 + +#define MG_PPP_IPCP_CFG_REQ 1 +#define MG_PPP_IPCP_CFG_ACK 2 +#define MG_PPP_IPCP_CFG_NACK 3 +#define MG_PPP_IPCP_CFG_REJECT 4 +#define MG_PPP_IPCP_OPT_IPADDR 3 + +#define MG_PPP_IPV6CP_CFG_REQ 1 +#define MG_PPP_IPV6CP_CFG_ACK 2 +#define MG_PPP_IPV6CP_CFG_NACK 3 +#define MG_PPP_IPV6CP_CFG_REJECT 4 +#define MG_PPP_IPV6CP_OPT_IFCID 1 + +#define MG_PPPoE_PADI 0x09 +#define MG_PPPoE_PADO 0x07 +#define MG_PPPoE_PADR 0x19 +#define MG_PPPoE_PADS 0x65 +#define MG_PPPoE_PADT 0xa7 + +#define MG_PPPoE_ST_DISC 0 // Discovery phase, see what servers are out there +#define MG_PPPoE_ST_REQ 1 // Chose a server, request a session and wait +#define MG_PPPoE_ST_SESS 2 // Session established, PPP traffic is exchanged + +#define PDIFF(a, b) ((size_t) (((char *) (b)) - ((char *) (a)))) + +static bool s_lcpup = false; // ************ THESE SHOULD MOVE TO A struct + // mg_l2data ******************************* +static uint8_t s_state = MG_PPPoE_ST_DISC; +static uint16_t s_id; + +void mg_l2_ppp_init(struct mg_tcpip_if *ifp) { + ifp->l2mtu = 1500; + ifp->framesize = 1500 + sizeof(struct ppp) + sizeof(struct hdlc_); +} + +extern void mg_l2_eth_init(struct mg_tcpip_if *); + +void mg_l2_pppoe_init(struct mg_tcpip_if *ifp) { + mg_l2_eth_init(ifp); + ifp->l2mtu = ifp->l2mtu - (uint16_t) (sizeof(struct pppoe) + + sizeof(struct ppp)); // 1500 --> 1492 +} + +bool mg_l2_ppp_poll(struct mg_tcpip_if *ifp, bool expired_1000ms) { + if (expired_1000ms && ifp->state == MG_TCPIP_STATE_DOWN) s_lcpup = false; + return s_lcpup; +} + +static uint8_t *hdlc_header(uint8_t *p) { + struct hdlc_ *hdlc = (struct hdlc_ *) p; + hdlc->addr = MG_PPP_ADDR; + hdlc->ctrl = MG_PPP_CTRL; + return (uint8_t *) (hdlc + 1); +} + +static uint8_t *ppp_header(uint16_t proto, uint8_t *p) { + struct ppp *ppp = (struct ppp *) p; + ppp->proto = mg_htons(proto); + return (uint8_t *) (ppp + 1); +} + +static uint8_t *l2_ppp_header(enum mg_l2proto proto, uint8_t *p) { + uint16_t ppp_proto = proto == MG_TCPIP_L2PROTO_IPV4 ? MG_PPP_PROTO_IP + : proto == MG_TCPIP_L2PROTO_IPV4 ? MG_PPP_PROTO_IPV6 + : 0; + return ppp_header(ppp_proto, p); +} + +uint8_t *mg_l2_ppp_header(struct mg_tcpip_if *ifp, enum mg_l2proto proto, + struct mg_l2addr *src, struct mg_l2addr *dst, + uint8_t *frame) { + (void) ifp; + (void) src; + (void) dst; + return l2_ppp_header(proto, hdlc_header(frame)); +} + +extern uint8_t *mg_l2_eth_header(struct mg_tcpip_if *ifp, enum mg_l2proto proto, + struct mg_l2addr *src, struct mg_l2addr *dst, + uint8_t *frame); + +static uint8_t *pppoe_header(struct mg_tcpip_if *ifp, enum mg_l2proto proto, + uint8_t code, uint16_t id, struct mg_l2addr *src, + struct mg_l2addr *dst, uint8_t *frame) { + struct pppoe *pppoe = + (struct pppoe *) mg_l2_eth_header(ifp, proto, src, dst, frame); + pppoe->vertype = 0x11; + pppoe->code = code; + pppoe->id = id; + return (uint8_t *) (pppoe + 1); +} + +uint8_t *mg_l2_pppoe_header(struct mg_tcpip_if *ifp, enum mg_l2proto proto, + struct mg_l2addr *src, struct mg_l2addr *dst, + uint8_t *frame) { + (void) dst; + return l2_ppp_header(proto, pppoe_header(ifp, MG_TCPIP_L2PROTO_PPPoE_SESS, 0, + s_id, src, dst, frame)); +} + +// Calculate FCS/CRC for PPP frames. Could be implemented faster using lookup +// tables. +static uint16_t fcs_do(uint8_t *frame, size_t len) { + uint32_t fcs = 0xffff; + unsigned int j; + for (j = 0; j < len; j++) { + unsigned int i; + uint8_t x = frame[j]; + for (i = 0; i < 8; i++) { + fcs = ((fcs ^ x) & 1) ? (fcs >> 1) ^ 0x8408 : fcs >> 1; + x >>= 1; + } + } + return (uint16_t) (fcs & 0xffff); +} + +size_t mg_l2_ppp_trailer(struct mg_tcpip_if *ifp, size_t len, uint8_t *cur) { + uint16_t crc; + uint8_t *frame; + len += sizeof(struct ppp) + sizeof(struct hdlc_); + frame = cur - len; + crc = fcs_do(frame, len); + *cur++ = (uint8_t) ~crc; // add CRC, note the byte order + *cur++ = (uint8_t) (~crc >> 8); + // there is no len field in PPP + (void) ifp; + return len + 2; +} + +extern size_t mg_l2_eth_trailer(struct mg_tcpip_if *, size_t, uint8_t *); + +static size_t pppoe_trailer(struct mg_tcpip_if *ifp, size_t len, uint8_t *cur) { + struct pppoe *pppoe = (struct pppoe *) (cur - len - sizeof(struct pppoe)); + pppoe->len = mg_htons((uint16_t) len); + return mg_l2_eth_trailer(ifp, PDIFF(pppoe, cur), cur); +} + +size_t mg_l2_pppoe_trailer(struct mg_tcpip_if *ifp, size_t len, uint8_t *cur) { + return pppoe_trailer(ifp, len + sizeof(struct ppp), cur); +} + +size_t mg_l2_driver_output(struct mg_tcpip_if *ifp, size_t len); + +static uint8_t *ppp_tx_frame_header(struct mg_tcpip_if *ifp, uint16_t proto) { + uint8_t *l2p = (uint8_t *) ifp->tx.buf; + if (ifp->l2type == MG_TCPIP_L2_PPP) + return ppp_header(proto, hdlc_header(l2p)); + return ppp_header(proto, pppoe_header(ifp, MG_TCPIP_L2PROTO_PPPoE_SESS, 0, + s_id, (struct mg_l2addr *) ifp->mac, + (struct mg_l2addr *) ifp->gwmac, l2p)); +} +static size_t ppp_tx_frame_trailer(struct mg_tcpip_if *ifp, size_t len, + uint8_t *cur) { + return mg_l2_driver_output(ifp, ifp->l2type == MG_TCPIP_L2_PPPoE + ? mg_l2_pppoe_trailer(ifp, len, cur) + : mg_l2_ppp_trailer(ifp, len, cur)); +} + +// Transmit a single PPP frame for the given protocol +static size_t ppp_tx_frame(struct mg_tcpip_if *ifp, uint16_t proto, + uint8_t *data, size_t datasz) { + uint8_t *pay = ppp_tx_frame_header(ifp, proto); + memcpy(pay, data, datasz); + return ppp_tx_frame_trailer(ifp, datasz, pay + datasz); +} + +static void ppp_handle_lcp(struct mg_tcpip_if *ifp, uint8_t *lcpp, + size_t lcpsz) { + uint8_t id; + uint16_t len; + struct lcp *lcp = (struct lcp *) lcpp; + if (lcpsz < sizeof(*lcp)) return; + id = lcp->id; + len = mg_ntohs(lcp->len); + switch (lcp->code) { + case MG_PPP_LCP_CFG_REQ: { + if (len == sizeof(*lcp)) { + MG_DEBUG(("LCP config request of %d bytes, acknowledging...", len)); + lcp->code = MG_PPP_LCP_CFG_ACK; + ppp_tx_frame(ifp, MG_PPP_PROTO_LCP, lcpp, len); + lcp->code = MG_PPP_LCP_CFG_REQ; + ppp_tx_frame(ifp, MG_PPP_PROTO_LCP, lcpp, len); + } else { + MG_DEBUG(("LCP config request of %d bytes, rejecting...", len)); + lcp->code = MG_PPP_LCP_CFG_REJECT; + ppp_tx_frame(ifp, MG_PPP_PROTO_LCP, lcpp, len); + } + } break; + case MG_PPP_LCP_CFG_ACK: + s_lcpup = true; + break; + case MG_PPP_LCP_CFG_TERM_REQ: { + uint8_t ack[4] = {MG_PPP_LCP_CFG_TERM_ACK, id, 0, 4}; + MG_DEBUG(("LCP termination request, acknowledging...")); + ppp_tx_frame(ifp, MG_PPP_PROTO_LCP, ack, sizeof(ack)); + s_lcpup = false; + } break; + case MG_PPP_LCP_ECHO_REQ: // RFC-1661 5.8: must respond + MG_DEBUG(("LCP echo request of %d bytes, replying...", len)); + lcp->code = MG_PPP_LCP_ECHO_REPLY; + ppp_tx_frame(ifp, MG_PPP_PROTO_LCP, lcpp, len); + break; + } +} + +static bool find_opt(const uint8_t opt, const uint8_t optlen, + const uint8_t *opts, size_t optslen, uint8_t *dest) { + uint8_t *p = (uint8_t *) opts; + while (optslen >= 2) { // parse options for requested one + if (p[1] > optslen || p[1] < 2) return false; // truncated / malformed + if (p[0] == opt && p[1] == optlen) { + memcpy(dest, p + 2, optlen - 2); + return true; + } + optslen -= p[1]; + p += p[1]; + } + return false; +} + +static void ppp_handle_ipcp(struct mg_tcpip_if *ifp, uint8_t *ipcpp, + size_t ipcpsz) { + uint16_t len; + uint8_t id; + struct ipcp *ipcp = (struct ipcp *) ipcpp; + uint8_t req[] = { + MG_PPP_IPCP_CFG_REQ, 0, 0, 10, MG_PPP_IPCP_OPT_IPADDR, 6, 0, 0, 0, 0}; + if (ipcpsz < sizeof(*ipcp)) return; + id = ipcp->id; + len = mg_ntohs(ipcp->len); + if (len > ipcpsz) return; + switch (ipcp->code) { + case MG_PPP_IPCP_CFG_REQ: + MG_VERBOSE(("got IPCP config request, acknowledging...")); + if (len >= 10 && + find_opt(MG_PPP_IPCP_OPT_IPADDR, 6, (const uint8_t *) (ipcp + 1), + len - sizeof(*ipcp), (uint8_t *) &ifp->gw)) { + MG_DEBUG(("IPCP cfg, GW IP: %M", mg_print_ip4, &ifp->gw)); + ipcp->code = MG_PPP_IPCP_CFG_ACK; + } else if (ifp->gw == 0) { + MG_ERROR(("Peer did not provide its IP address")); + // NOTE: We should NACK with an added option, probably we can just store + // the incoming address and offer a statically configured ifp->gw when + // theirs == 0, but it is unlikely to find such a dumb PPP server and it + // will complicate our config and this protocol state machine + ipcp->code = MG_PPP_IPCP_CFG_REJECT; + ppp_tx_frame(ifp, MG_PPP_PROTO_IPCP, ipcpp, len); + } + ppp_tx_frame(ifp, MG_PPP_PROTO_IPCP, ipcpp, len); + req[1] = id; + memcpy(req + 6, &ifp->ip, 4); // Request config IP address or 0.0.0.0 + ppp_tx_frame(ifp, MG_PPP_PROTO_IPCP, req, sizeof(req)); + break; + case MG_PPP_IPCP_CFG_ACK: + // Our peer accepted our IP address + MG_VERBOSE(("got IPCP config ack")); + ifp->mask = 0xffffffff; // send to gw + ifp->state = MG_TCPIP_STATE_IP; + ifp->gw_ready = true; + break; + case MG_PPP_IPCP_CFG_NACK: + MG_VERBOSE(("got IPCP config nack")); + // NACK contains our "suggested" IP address, use it + if (len >= 10 && + find_opt(MG_PPP_IPCP_OPT_IPADDR, 6, (const uint8_t *) (ipcp + 1), + len - sizeof(*ipcp), (uint8_t *) &ifp->ip)) { + MG_DEBUG(("IPCP cfg, IP: %M", mg_print_ip4, &ifp->ip)); + ipcp->code = MG_PPP_IPCP_CFG_REQ; + ppp_tx_frame(ifp, MG_PPP_PROTO_IPCP, ipcpp, len); + } + break; + case MG_PPP_IPCP_CFG_REJECT: + MG_ERROR(("Peer rejected our IP address, need to properly set ifp->ip")); + break; + } +} + +#if MG_ENABLE_IPV6 +static void ppp_handle_ipv6cp(struct mg_tcpip_if *ifp, uint8_t *ipv6cpp, + size_t ipv6cpsz) { + uint16_t len; + uint8_t id; + struct ipv6cp *ipv6cp = (struct ipv6cp *) ipv6cpp; + uint8_t req[14]; + memset(req, 0, sizeof(req)); + req[0] = MG_PPP_IPV6CP_CFG_REQ, req[3] = 14, req[4] = MG_PPP_IPV6CP_OPT_IFCID, + req[5] = 10; + if (ipv6cpsz < sizeof(*ipv6cp)) return; + id = ipv6cp->id; + len = mg_ntohs(ipv6cp->len); + switch (ipv6cp->code) { + case MG_PPP_IPV6CP_CFG_REQ: + MG_VERBOSE(("got IPV6CP config request...")); + if (len >= 10 && + find_opt( + MG_PPP_IPV6CP_OPT_IFCID, 10, (const uint8_t *) (ipv6cp + 1), + len - sizeof(*ipv6cp), + (uint8_t *) &((struct mg_l2addr *) (ifp->gwmac))->addr.ieee64)) { + if (((struct mg_l2addr *) (ifp->gwmac))->addr.ieee64 != 0) { + MG_DEBUG(("IPV6CP cfg, GW IFCID: %M", mg_print_ieee64, + &((struct mg_l2addr *) (ifp->gwmac))->addr.ieee64)); + ipv6cp->code = MG_PPP_IPV6CP_CFG_ACK; + ppp_tx_frame(ifp, MG_PPP_PROTO_IPV6CP, ipv6cpp, len); + req[1] = id; + ifp->ip6ll[0] = 0, ifp->ip6ll[1] = 0; // clear any former ll address + memset(req + 6, 0, 8); // Inform ifc id 0 + ppp_tx_frame(ifp, MG_PPP_PROTO_IPV6CP, req, sizeof(req)); + } else { + MG_ERROR(("Peer is not able to provide its interface id")); + ipv6cp->code = MG_PPP_IPV6CP_CFG_REJECT; + ppp_tx_frame(ifp, MG_PPP_PROTO_IPV6CP, ipv6cpp, len); + } + } else { + MG_ERROR(("Peer did not provide its interface id")); + // We should NACK with an added option, but we can't provide one for + // them + ipv6cp->code = MG_PPP_IPV6CP_CFG_REJECT; + ppp_tx_frame(ifp, MG_PPP_PROTO_IPV6CP, ipv6cpp, len); + } + break; + case MG_PPP_IPV6CP_CFG_ACK: + // Our peer accepted our ifc id + MG_VERBOSE(("got IPV6CP config ack")); + break; + case MG_PPP_IPV6CP_CFG_NACK: + MG_VERBOSE(("got IPV6CP config nack")); + // NACK contains our "suggested" IFC id, use it + if (len >= 10 && + find_opt( + MG_PPP_IPV6CP_OPT_IFCID, 10, (const uint8_t *) (ipv6cp + 1), + len - sizeof(*ipv6cp), + (uint8_t *) &((struct mg_l2addr *) (ifp->mac))->addr.ieee64)) { + MG_DEBUG(("IPV6CP cfg, IFCID: %M", mg_print_ieee64, + &((struct mg_l2addr *) (ifp->mac))->addr.ieee64)); + ipv6cp->code = MG_PPP_IPV6CP_CFG_REQ; + ppp_tx_frame(ifp, MG_PPP_PROTO_IPV6CP, ipv6cpp, len); + } else { + MG_ERROR(("Peer is not able to offer an interface id")); + } + break; + case MG_PPP_IPV6CP_CFG_REJECT: + MG_ERROR(("Peer rejected our interface id")); + break; + } +} +#endif + +static bool ppp_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto, + struct mg_str *pay, struct mg_str *raw) { + struct ppp *ppp = (struct ppp *) pay->buf; + if (pay->len < sizeof(*ppp)) return false; // Truncated + pay->buf = (char *) (ppp + 1); + pay->len = pay->len - sizeof(*ppp); + switch (mg_ntohs(ppp->proto)) { + case MG_PPP_PROTO_LCP: + ppp_handle_lcp(ifp, (uint8_t *) pay->buf, pay->len); + return false; + case MG_PPP_PROTO_IPCP: + if (s_lcpup) ppp_handle_ipcp(ifp, (uint8_t *) pay->buf, pay->len); + return false; + case MG_PPP_PROTO_IP: + if (!s_lcpup) return false; + MG_VERBOSE(("got IP packet of %d bytes", pay->len)); + *proto = MG_TCPIP_L2PROTO_IPV4; + break; +#if MG_ENABLE_IPV6 + case MG_PPP_PROTO_IPV6CP: + if (s_lcpup) ppp_handle_ipv6cp(ifp, (uint8_t *) pay->buf, pay->len); + return false; + case MG_PPP_PROTO_IPV6: + if (!s_lcpup) return false; + MG_VERBOSE(("got IPv6 packet of %d bytes", pay->len)); + *proto = MG_TCPIP_L2PROTO_IPV6; + break; +#endif + default: { + struct lcp rej; + uint8_t *p; + size_t msglen; + MG_DEBUG(("unknown %u-byte PPP frame with proto 0x%04x:", + pay->len + sizeof(*ppp), mg_ntohs(ppp->proto))); + if (mg_log_level >= MG_LL_DEBUG) + mg_hexdump(ppp, pay->len > 14 ? 16 : pay->len + sizeof(*ppp)); + if (!s_lcpup) return false; // RFC-1661 5.7: must reject on link down + if (pay->len > (size_t) (ifp->mtu - 20)) + pay->len = (size_t) (ifp->mtu - 20); // truncate to some safe limit + rej.code = MG_PPP_LCP_REJECT; + mg_random(&rej.id, sizeof(rej.id)); + msglen = pay->len + sizeof(*ppp); + rej.len = mg_htons((uint16_t) (msglen + sizeof(rej))); + p = ppp_tx_frame_header(ifp, MG_PPP_PROTO_LCP); + memmove(p, &rej, sizeof(rej)); // LCP reject + memmove(p + sizeof(rej), ppp, msglen); // rejected PPP message + ppp_tx_frame_trailer(ifp, msglen + sizeof(rej), p + msglen + sizeof(rej)); + } + return false; + } + (void) raw; + return true; +} + +bool mg_l2_ppp_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto, + struct mg_str *pay, struct mg_str *raw) { + struct hdlc_ *hdlc = (struct hdlc_ *) raw->buf; + if (raw->len < sizeof(*hdlc) + 2) return false; // Truncated + if (hdlc->addr == MG_PPP_ADDR && hdlc->ctrl == MG_PPP_CTRL) { + pay->buf = (char *) (hdlc + 1); + pay->len = raw->len - sizeof(*hdlc) - 2; + } else { // Address-and-Control-Field-Compressed PPP header + pay->buf = (char *) raw->buf; + pay->len = raw->len - 2; + } + return ppp_rx(ifp, proto, pay, raw); +} + +extern struct mg_l2addr s_mapip; + +struct mg_l2addr *mg_l2_ppp_getaddr(struct mg_tcpip_if *ifp, uint8_t *frame) { + (void) ifp; + (void) frame; + return &s_mapip; // bogus +} + +extern struct mg_l2addr *mg_l2_eth_mapip(enum mg_l2addrtype, struct mg_addr *); + +struct mg_l2addr *mg_l2_ppp_mapip(enum mg_l2addrtype addrtype, + struct mg_addr *addr) { + return mg_l2_eth_mapip(addrtype, addr); +} + +#if MG_ENABLE_IPV6 +bool mg_l2_ppp_genip6(uint64_t *ip6, uint8_t prefix_len, + struct mg_l2addr *l2addr) { + if (prefix_len > 64) { + MG_ERROR(("Prefix length > 64, UNSUPPORTED")); + return false; + } + ip6[0] = 0; + ip6[1] = l2addr->addr.ieee64; + return false; +} + +bool mg_l2_ppp_ip6get(struct mg_l2addr *l2addr, uint8_t *opts, uint8_t len) { + (void) l2addr; + (void) opts; + (void) len; + return true; +} + +uint8_t mg_l2_ppp_ip6put(struct mg_l2addr *l2addr, uint8_t *opts) { + (void) l2addr; + (void) opts; + return 0; +} +#endif + +// Transmit a single PPPoE frame for the discovery phase +static size_t pppoe_tx_frame(struct mg_tcpip_if *ifp, uint8_t code, uint16_t id, + uint8_t *data, size_t datasz, + struct mg_l2addr *dst) { + uint8_t *l2p = (uint8_t *) ifp->tx.buf; + uint8_t *p = pppoe_header(ifp, MG_TCPIP_L2PROTO_PPPoE_DISC, code, id, + (struct mg_l2addr *) ifp->mac, dst, l2p); + memmove(p, data, datasz); + return mg_l2_driver_output(ifp, pppoe_trailer(ifp, datasz, p + datasz)); +} + +bool mg_l2_pppoe_poll(struct mg_tcpip_if *ifp, bool expired_1000ms) { + if (expired_1000ms && s_state == MG_PPPoE_ST_DISC && + ifp->state == MG_TCPIP_STATE_LINK_UP) { + uint16_t tags[2]; + tags[0] = mg_htons(0x0101); // Service Request + tags[1] = mg_htons(0x0000); // Any + pppoe_tx_frame(ifp, MG_PPPoE_PADI, 0, (uint8_t *) tags, sizeof(tags), + mg_l2_eth_mapip(MG_TCPIP_L2ADDR_BCAST, NULL)); + MG_DEBUG(("Sent PADI")); + } else if (expired_1000ms && (s_state != MG_PPPoE_ST_SESS || + ifp->state == MG_TCPIP_STATE_DOWN)) { + s_state = MG_PPPoE_ST_DISC; + } + return mg_l2_ppp_poll(ifp, expired_1000ms); +} + +extern bool mg_l2_eth_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto, + struct mg_str *pay, struct mg_str *raw); +extern struct mg_l2addr *mg_l2_eth_getaddr(struct mg_tcpip_if *, uint8_t *); + +bool mg_l2_pppoe_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto, + struct mg_str *pay, struct mg_str *raw) { + enum mg_l2proto eth_proto; // raw is handled by eth + struct pppoe *pppoe; + if (!mg_l2_eth_rx(ifp, ð_proto, pay, raw)) return false; + pppoe = (struct pppoe *) pay->buf; // here we handle pay, not raw + if (pay->len < sizeof(*pppoe)) return false; // Truncated + if (eth_proto == MG_TCPIP_L2PROTO_PPPoE_DISC) { + MG_VERBOSE(("PPPoE_DISC")); + if (s_state == MG_PPPoE_ST_DISC && pppoe->code == MG_PPPoE_PADO && + pppoe->id == 0) { + uint16_t tags[2]; + bool has_cookie = false; + size_t len = pay->len - sizeof(*pppoe); + uint8_t *p = (uint8_t *) (pppoe + 1); + uint16_t taglen; + while (len >= 4) { // parse tags for a possible AC-Cookie + uint16_t curtag = *((uint16_t *) p); + taglen = mg_ntohs(*(((uint16_t *) p) + 1)); + if (taglen > len - 4) return false; // truncated / malformed + if (curtag == mg_htons(0x0104)) { + has_cookie = true; + break; + } + len -= 4 + taglen; + p += 4 + taglen; + } + tags[0] = mg_htons(0x0101); // Service Request + tags[1] = mg_htons(0x0000); // Any + if (has_cookie) { // copy tags to before AC-Cookie tag in rx buffer + memcpy(p -= sizeof(tags), tags, sizeof(tags)); // point to all tags + taglen += 4 + sizeof(tags); // account for tags + } else { + p = (uint8_t *) tags; + taglen = sizeof(tags); + } + pppoe_tx_frame(ifp, MG_PPPoE_PADR, 0, p, taglen, + mg_l2_eth_getaddr(ifp, (uint8_t *) raw->buf)); + MG_DEBUG(("Sent PADR")); + s_state = MG_PPPoE_ST_REQ; + } else if (s_state == MG_PPPoE_ST_REQ && pppoe->code == MG_PPPoE_PADS) { + s_id = pppoe->id; + memcpy(&ifp->gwmac, + mg_l2_eth_getaddr(ifp, (uint8_t *) raw->buf)->addr.mac, 6); + MG_DEBUG(("PPPoE session 0x%04x started", mg_ntohs(s_id))); + s_state = MG_PPPoE_ST_SESS; + } else if (s_state == MG_PPPoE_ST_SESS && pppoe->code == MG_PPPoE_PADT && + pppoe->id == s_id) { + MG_ERROR(("Got PADT")); + s_id = 0; + s_lcpup = false; + s_state = MG_PPPoE_ST_DISC; + } + } else if (eth_proto == MG_TCPIP_L2PROTO_PPPoE_SESS && + s_state == MG_PPPoE_ST_SESS) { + pay->buf = (char *) (pppoe + 1); + pay->len = pay->len - sizeof(*pppoe); + return ppp_rx(ifp, proto, pay, raw); + } + return false; +} + +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/lfs.c" +#endif +// Copyright (c) 2023 Cesanta Software Limited +// SPDX-License-Identifier: GPL-2.0-only or commercial + + + + +#if MG_ENABLE_LFS + +#include +#include +#include +#include +#include + +#ifndef LFS_USE_RAM +#define LFS_USE_RAM 0 // If 1, use RAM FS. If 0, use flash +#endif + +#ifndef MG_LFS_BUF_SIZE +#define MG_LFS_BUF_SIZE 64 // Buffer size used for reads, writes, and cache +#endif + +#ifndef MG_LFS_FD_BASE +#define MG_LFS_FD_BASE 12000 // Starting file descriptor number +#endif + +#if LFS_USE_RAM && !defined(MG_LFS_BLOCK_SIZE) +#define MG_LFS_BLOCK_SIZE 8192 +#endif + +#ifndef DT_DIR +#define DT_DIR 4 +#endif +#ifndef DT_REG +#define DT_REG 8 +#endif + +typedef struct mg_lfs_fd DIR; +struct dirent { + char d_name[LFS_NAME_MAX + 1]; + unsigned char d_type; +}; + +struct mg_lfs_fd { + struct mg_lfs_fd *next; + bool isopen; + lfs_file_t file; + lfs_dir_t dir; + int fd; +}; + +static lfs_t s_lfs; +static struct mg_lfs_fd *s_fds; +static uint8_t *s_fs; +static int s_next_fd = MG_LFS_FD_BASE; +static bool s_lfs_ready; +#if LFS_USE_RAM +static uint8_t *s_ram_fs; +#endif + +static int lfs_driver_read(const struct lfs_config *cfg, lfs_block_t block, + lfs_off_t off, void *buf, lfs_size_t len); +static int lfs_driver_prog(const struct lfs_config *cfg, lfs_block_t block, + lfs_off_t off, const void *buf, lfs_size_t len); +static int lfs_driver_erase(const struct lfs_config *cfg, lfs_block_t block); +static int lfs_driver_sync(const struct lfs_config *cfg); + +static struct lfs_config s_cfg = { + .read = lfs_driver_read, + .prog = lfs_driver_prog, + .erase = lfs_driver_erase, + .sync = lfs_driver_sync, + .block_cycles = 200, + .cache_size = MG_LFS_BUF_SIZE, + .read_size = MG_LFS_BUF_SIZE, + .prog_size = MG_LFS_BUF_SIZE, + .lookahead_size = MG_LFS_BUF_SIZE / 8, +}; + +static bool flash_write_buf(void *addr, const void *buf, size_t len) { +#if LFS_USE_RAM + memmove(addr, buf, len); + return true; +#else + return mg_flash != NULL && mg_flash->write_fn(addr, buf, len); +#endif +} + +static int lfs_driver_read(const struct lfs_config *cfg, lfs_block_t block, + lfs_off_t off, void *buf, lfs_size_t len) { + memmove(buf, &s_fs[block * cfg->block_size + off], len); + return 0; +} + +static int lfs_driver_prog(const struct lfs_config *cfg, lfs_block_t block, + lfs_off_t off, const void *buf, lfs_size_t len) { + uint8_t *dst = &s_fs[block * cfg->block_size + off]; + if (!flash_write_buf(dst, buf, len)) return LFS_ERR_IO; + return 0; +} + +static int lfs_driver_erase(const struct lfs_config *cfg, lfs_block_t block) { + (void) cfg, (void) block; + return 0; +} + +static int lfs_driver_sync(const struct lfs_config *cfg) { + (void) cfg; + return 0; +} + +bool mg_lfs_init(size_t size) { + int result = 0; + if (s_lfs_ready) return true; + if (size == 0) return false; +#if LFS_USE_RAM + s_cfg.block_size = MG_LFS_BLOCK_SIZE; + if (s_ram_fs == NULL) s_ram_fs = (uint8_t *) mg_calloc(1, size); + s_fs = s_ram_fs; +#else + if (mg_flash == NULL || mg_flash->secsz == 0 || mg_flash->size < size) return false; + if (mg_flash->write_fn == NULL) return false; + s_cfg.block_size = mg_flash->secsz; + s_fs = (uint8_t *) mg_flash->start + mg_flash->size - size; +#endif + if (s_fs == NULL || s_cfg.block_size == 0 || size % s_cfg.block_size != 0) { + return false; + } + s_cfg.block_count = (lfs_size_t) (size / s_cfg.block_size); + if (s_cfg.block_count == 0) return false; + if (lfs_mount(&s_lfs, &s_cfg) != 0) { + lfs_format(&s_lfs, &s_cfg); + if (lfs_mount(&s_lfs, &s_cfg) != 0) result = -1; + } + s_lfs_ready = result == 0; + return s_lfs_ready; +} + +static struct mg_lfs_fd *find_fd(int fd) { + struct mg_lfs_fd *f; + for (f = s_fds; f != NULL; f = f->next) { + if (f->isopen && f->fd == fd) return f; + } + return NULL; +} + +static struct mg_lfs_fd *open_fd(void) { + struct mg_lfs_fd *f = NULL; + if (s_lfs_ready) { + f = (struct mg_lfs_fd *) mg_calloc(1, sizeof(*f)); + if (f != NULL) { + f->isopen = true; + f->fd = s_next_fd++; + f->next = s_fds; + s_fds = f; + if (s_next_fd < MG_LFS_FD_BASE) s_next_fd = MG_LFS_FD_BASE; + } + } + return f; +} + +static int close_fd(struct mg_lfs_fd *fd) { + struct mg_lfs_fd **f = &s_fds; + while (*f != NULL && *f != fd) f = &(*f)->next; + if (*f == NULL) return -1; + *f = fd->next; + fd->isopen = false; + mg_free(fd); + return 0; +} + +int _open(const char *path, int flags, mode_t mode) { + int err, lfs_flags = 0, fd = -1; + struct mg_lfs_fd *f = open_fd(); + (void) mode; + if (f == NULL) return -1; + fd = f->fd; + if ((flags & 3) == O_RDONLY) lfs_flags |= LFS_O_RDONLY; + if ((flags & 3) == O_WRONLY) lfs_flags |= LFS_O_WRONLY; + if ((flags & 3) == O_RDWR) lfs_flags |= LFS_O_RDWR; + if (flags & O_CREAT) lfs_flags |= LFS_O_CREAT; + if (flags & O_TRUNC) lfs_flags |= LFS_O_TRUNC; + if (flags & O_APPEND) lfs_flags |= LFS_O_APPEND; + err = lfs_file_open(&s_lfs, &f->file, path, lfs_flags); + if (err < 0) close_fd(f), fd = -1; + return fd; +} + +int _close(int fd) { + struct mg_lfs_fd *f = find_fd(fd); + if (fd < 3) return 0; + if (f == NULL) return -1; + lfs_file_close(&s_lfs, &f->file); + close_fd(f); + return 0; +} + +int _write(int fd, char *ptr, int len) { + struct mg_lfs_fd *f = find_fd(fd); + return fd < 3 ? len + : f == NULL ? -1 : lfs_file_write(&s_lfs, &f->file, ptr, len); +} + +int _read(int fd, char *ptr, int len) { + struct mg_lfs_fd *f = find_fd(fd); + return fd < 3 ? 0 : f == NULL ? -1 : lfs_file_read(&s_lfs, &f->file, ptr, len); +} + +int _lseek(int fd, int offset, int whence) { + struct mg_lfs_fd *f = find_fd(fd); + return fd < 3 ? 0 + : f == NULL ? -1 : lfs_file_seek(&s_lfs, &f->file, offset, + whence); +} + +int _rename(const char *oldname, const char *newname) { + return s_lfs_ready ? lfs_rename(&s_lfs, oldname, newname) : -1; +} + +int _unlink_r(void *r, const char *a) { + (void) r; + return s_lfs_ready ? lfs_remove(&s_lfs, a) : -1; +} + +DIR *opendir(const char *name) { + struct mg_lfs_fd *f = open_fd(); + if (f == NULL) return NULL; + if (lfs_dir_open(&s_lfs, &f->dir, name) != 0) { + close_fd(f); + return NULL; + } + return (DIR *) f; +} + +int closedir(DIR *dir) { + struct mg_lfs_fd *f = (struct mg_lfs_fd *) dir; + if (f == NULL || find_fd(f->fd) != f) return -1; + lfs_dir_close(&s_lfs, &f->dir); + return close_fd(f); +} + +struct dirent *readdir(DIR *dir) { + static struct dirent dirent; + struct mg_lfs_fd *f = (struct mg_lfs_fd *) dir; + struct lfs_info info; + if (f == NULL || !f->isopen) return NULL; + if (lfs_dir_read(&s_lfs, &f->dir, &info) < 1) return NULL; + memset(&dirent, 0, sizeof(dirent)); + strncpy(dirent.d_name, info.name, sizeof(dirent.d_name) - 1); + if (info.type == LFS_TYPE_DIR) dirent.d_type |= DT_DIR; + if (info.type == LFS_TYPE_REG) dirent.d_type |= DT_REG; + return &dirent; +} + +int _stat(const char *path, struct stat *st) { + struct lfs_info info; + if (!s_lfs_ready || lfs_stat(&s_lfs, path, &info) != 0) return -1; + st->st_mode = info.type == LFS_TYPE_DIR ? S_IFDIR : S_IFREG; + st->st_size = info.size; + return 0; +} + +int _fstat(int fd, struct stat *st) { + if (fd >= 3 && find_fd(fd) == NULL) return -1; + st->st_mode = S_IFCHR; + return 0; +} + +int mkdir(const char *path, mode_t mode) { + (void) mode; + return s_lfs_ready ? lfs_mkdir(&s_lfs, path) : -1; +} + +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/log.c" +#endif + + + + + +int mg_log_level = MG_LL_DEBUG; +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), logs("\r\n", 2), 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), logs("\r\n", 2); +} + +#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/modbus.c" +#endif + + + + + +#define MG_MODBUS_MIN_PAYLOAD_LEN 12 +#define MG_MODBUS_HEADER_LEN 7 + +static void mg_modbus_send_mbap(struct mg_connection *c, uint16_t txid, + uint16_t pdu_len, uint8_t unit_id) { + uint8_t hdr[7]; + memset(hdr, 0, sizeof(hdr)); + MG_STORE_BE16(&hdr[0], txid & 0xFFFFU); + MG_STORE_BE16(&hdr[4], (pdu_len + 1) & 0xFFFFU); + hdr[6] = unit_id; + mg_send(c, hdr, sizeof(hdr)); +} + +static void mg_modbus_send_response(struct mg_connection *c, + struct mg_modbus_req *req, + uint16_t txid, uint8_t unit_id) { + uint8_t buf[5]; + uint8_t out; + uint16_t reg; + uint16_t i, nbytes; + + memset(buf, 0, sizeof(buf)); + if (req == NULL) return; + if (req->error != MG_MODBUS_ERR_NONE) { + mg_modbus_send_mbap(c, txid, 2, unit_id); + buf[0] = (uint8_t) (req->func | 0x80); + buf[1] = req->error; + mg_send(c, buf, 2); + return; + } + switch (req->func) { + case MG_MODBUS_FUNC_READ_COILS: + case MG_MODBUS_FUNC_READ_DISCRETE_INPUTS: + if (req->u.bits == NULL) return; + nbytes = (uint16_t) ((req->len + 7) / 8); + mg_modbus_send_mbap(c, txid, (uint16_t) (nbytes + 2), unit_id); + buf[0] = req->func; + buf[1] = (uint8_t) nbytes; + mg_send(c, buf, 2); + memset(buf, 0, sizeof(buf)); + for (i = 0; i < req->len; i++) { + if (i % 8 == 0) out = 0; + if (req->u.bits[i]) out |= (uint8_t) (1 << (i % 8)); + if ((i % 8) == 7 || i + 1 == req->len) { + mg_send(c, &out, 1); + } + } + break; + case MG_MODBUS_FUNC_READ_HOLDING_REGISTERS: + case MG_MODBUS_FUNC_READ_INPUT_REGISTERS: + if (req->u.regs == NULL) return; + nbytes = (uint16_t) (req->len * 2); + mg_modbus_send_mbap(c, txid, (uint16_t) (nbytes + 2), unit_id); + buf[0] = req->func; + buf[1] = (uint8_t) nbytes; + mg_send(c, buf, 2); + for (i = 0; i < req->len; i++) { + MG_STORE_BE16(®, req->u.regs[i] & 0xFFFFU); + mg_send(c, ®, 2); + } + break; + case MG_MODBUS_FUNC_WRITE_SINGLE_COIL: + if (req->len != 1 || req->u.bits == NULL) return; + mg_modbus_send_mbap(c, txid, 5, unit_id); + buf[0] = req->func; + MG_STORE_BE16(&buf[1], req->addr & 0xFFFFu); + MG_STORE_BE16(&buf[3], req->u.bits[0] ? 0xFF00U : 0x0000U); + mg_send(c, buf, 5); + break; + case MG_MODBUS_FUNC_WRITE_SINGLE_REGISTER: + if (req->len != 1 || req->u.regs == NULL) return; + mg_modbus_send_mbap(c, txid, 5, unit_id); + buf[0] = req->func; + MG_STORE_BE16(&buf[1], req->addr & 0xFFFFU); + MG_STORE_BE16(&buf[3], req->u.regs[0] & 0xFFFFU); + mg_send(c, buf, 5); + break; + case MG_MODBUS_FUNC_WRITE_MULTIPLE_COILS: + case MG_MODBUS_FUNC_WRITE_MULTIPLE_REGISTERS: + mg_modbus_send_mbap(c, txid, 5, unit_id); + buf[0] = req->func; + MG_STORE_BE16(&buf[1], req->addr & 0xFFFFU); + MG_STORE_BE16(&buf[3], req->len & 0xFFFFU); + mg_send(c, buf, 5); + break; + default: + break; + } +} + +static void handle_pdu(struct mg_connection *c, uint8_t *buf, size_t len) { + uint16_t max_len, val, txid, addr, quantity; + uint8_t func, unit_id, byte_count, packed; + int i; + size_t pdu_len = len - MG_MODBUS_HEADER_LEN, len_check; + struct mg_modbus_req mr; + + if (len < MG_MODBUS_MIN_PAYLOAD_LEN) return; + memset(&mr, 0, sizeof(mr)); + txid = MG_LOAD_BE16(&buf[0]); + unit_id = buf[6]; + func = buf[7]; + addr = MG_LOAD_BE16(&buf[8]); + mr.func = func; + mr.addr = addr; + mr.error = MG_MODBUS_ERR_NONE; + mr.len = 0; + mr.u.bits = NULL; + switch (func) { + case MG_MODBUS_FUNC_READ_COILS: + case MG_MODBUS_FUNC_READ_DISCRETE_INPUTS: + if (pdu_len != 5) goto modbus_illegal_value; + quantity = MG_LOAD_BE16(&buf[10]); + max_len = 0x07D0; // 2000 bits + if (quantity == 0 || quantity > max_len) goto modbus_illegal_value; + mr.len = quantity; + mr.u.bits = (bool *) mg_calloc((size_t) mr.len, sizeof(bool)); + if (mr.u.bits == NULL) goto modbus_oom; + break; + case MG_MODBUS_FUNC_READ_HOLDING_REGISTERS: + case MG_MODBUS_FUNC_READ_INPUT_REGISTERS: + if (pdu_len != 5) goto modbus_illegal_value; + quantity = MG_LOAD_BE16(&buf[10]); + max_len = 0x007D; // 125 registers + if (quantity == 0 || quantity > max_len) goto modbus_illegal_value; + mr.len = quantity; + mr.u.regs = (uint16_t *) mg_calloc((size_t) mr.len, sizeof(uint16_t)); + if (mr.u.regs == NULL) goto modbus_oom; + break; + case MG_MODBUS_FUNC_WRITE_SINGLE_COIL: + if (pdu_len != 5) goto modbus_illegal_value; + mr.len = 1; + mr.u.bits = (bool *) mg_calloc(1, sizeof(bool)); + if (mr.u.bits == NULL) goto modbus_oom; + val = MG_LOAD_BE16(&buf[10]); + if (val != 0x0000 && val != 0xFF00) goto modbus_illegal_value; + mr.u.bits[0] = (val == 0xFF00); + break; + case MG_MODBUS_FUNC_WRITE_SINGLE_REGISTER: + if (pdu_len != 5) goto modbus_illegal_value; + mr.len = 1; + mr.u.regs = (uint16_t *) mg_calloc(1, sizeof(uint16_t)); + if (mr.u.regs == NULL) goto modbus_oom; + mr.u.regs[0] = MG_LOAD_BE16(&buf[10]); + break; + case MG_MODBUS_FUNC_WRITE_MULTIPLE_COILS: { + quantity = MG_LOAD_BE16(&buf[10]); + if (quantity == 0 || quantity > 0x07B0) goto modbus_illegal_value; + if (pdu_len < 6) goto modbus_illegal_value; + byte_count = buf[12]; + if (byte_count != (uint8_t) ((quantity + 7) / 8)) goto modbus_illegal_value; + len_check = (size_t) (6 + byte_count); + if (len_check != pdu_len) goto modbus_illegal_value; + mr.len = quantity; + mr.u.bits = (bool *) mg_calloc((size_t) mr.len, sizeof(bool)); + if (mr.u.bits == NULL) goto modbus_oom; + for (i = 0; i < mr.len; i++) { + packed = buf[13 + (i / 8)]; + mr.u.bits[i] = ((packed >> (i % 8)) & 1) != 0; + } + break; + } + case MG_MODBUS_FUNC_WRITE_MULTIPLE_REGISTERS: { + quantity = MG_LOAD_BE16(&buf[10]); + if (quantity == 0 || quantity > 0x007B) goto modbus_illegal_value; + if (pdu_len < 6) goto modbus_illegal_value; + byte_count = buf[12]; + if (byte_count != (uint8_t) (quantity * 2)) goto modbus_illegal_value; + len_check = (size_t) (6 + quantity * 2); + if (len_check != pdu_len) goto modbus_illegal_value; + mr.len = quantity; + mr.u.regs = (uint16_t *) mg_calloc((size_t) mr.len, sizeof(uint16_t)); + if (mr.u.regs == NULL) goto modbus_oom; + for (i = 0; i < mr.len; i++) { + mr.u.regs[i] = MG_LOAD_BE16(&buf[13 + i * 2]); + } + break; + } + default: + MG_ERROR(("Unsupported modbus function")); + mr.error = MG_MODBUS_ERR_ILLEGAL_FUNCTION; + mg_modbus_send_response(c, &mr, txid, unit_id); + return; + } + mg_call(c, MG_EV_MODBUS_REQ, &mr); + goto modbus_exit; + +modbus_illegal_value: + MG_ERROR(("Invalid data")); + mr.error = MG_MODBUS_ERR_ILLEGAL_VALUE; + goto modbus_exit; +modbus_oom: + MG_ERROR(("OOM")); + mr.error = MG_MODBUS_ERR_DEVICE_FAILURE; +modbus_exit: + mg_modbus_send_response(c, &mr, txid, unit_id); + if (mr.u.bits != NULL) mg_free(mr.u.bits); +} + +static void modbus_ev_handler(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_READ) { + while (c->recv.len >= MG_MODBUS_MIN_PAYLOAD_LEN) { + uint16_t len = MG_LOAD_BE16(&c->recv.buf[4]); // PDU length + uint16_t proto_id = MG_LOAD_BE16(&c->recv.buf[2]); + if (len < 6 || proto_id != 0) { + mg_error(c, "invalid pdu"); + break; + } else if (c->recv.len < len + 6U) { + break; // Partial frame, buffer more + } else { + handle_pdu(c, c->recv.buf, len + 6); // Parse PDU and call user + mg_iobuf_del(&c->recv, 0, len + 6); // Delete received PDU + } + } + } + (void) ev_data; +} + +struct mg_connection *mg_modbus_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 = modbus_ev_handler; + return c; +} + +#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}}; + +static bool 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)]); + return mg_send(c, buf, (size_t) (vlen - buf)); +} + +void mg_mqtt_send_header(struct mg_connection *c, uint8_t cmd, uint8_t flags, + uint32_t len) { + if (!mqtt_send_header(c, cmd, flags, len)) mg_error(c, "OOM"); +} + +static bool mg_send_u16(struct mg_connection *c, uint16_t value) { + return mg_send(c, &value, sizeof(value)); +} + +static bool mg_send_u32(struct mg_connection *c, uint32_t value) { + return 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, uint32_t *value) { + uint32_t mul = 1, ofs; + *value = 0; + for (ofs = 0; ofs < 4 && ofs < len; ofs++) { + uint8_t enc_byte = buf[ofs]; + *value += (enc_byte & 0x7f) * mul; + mul *= 128; + if ((enc_byte & 0x80) == 0) return ofs + 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 bool 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); + + if (!mg_send(c, buf, (size_t) len)) return false; + for (i = 0; i < nprops; i++) { + if (!mg_send(c, &props[i].id, sizeof(props[i].id))) return false; + switch (mqtt_prop_type_by_id(props[i].id)) { + case MQTT_PROP_TYPE_STRING_PAIR: + if (!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)) + return false; + break; + case MQTT_PROP_TYPE_BYTE: + if (!mg_send(c, &props[i].iv, sizeof(uint8_t))) return false; + break; + case MQTT_PROP_TYPE_SHORT: + if (!mg_send_u16(c, mg_htons((uint16_t) props[i].iv))) return false; + break; + case MQTT_PROP_TYPE_INT: + if (!mg_send_u32(c, mg_htonl((uint32_t) props[i].iv))) return false; + break; + case MQTT_PROP_TYPE_STRING: + if (!mg_send_u16(c, mg_htons((uint16_t) props[i].val.len)) || + !mg_send(c, props[i].val.buf, props[i].val.len)) + return false; + break; + case MQTT_PROP_TYPE_BINARY_DATA: + if (!mg_send_u16(c, mg_htons((uint16_t) props[i].val.len)) || + !mg_send(c, props[i].val.buf, props[i].val.len)) + return false; + break; + case MQTT_PROP_TYPE_VARIABLE_INT: + len = encode_varint(buf_v, props[i].iv); + if (!mg_send(c, buf_v, (size_t) len)) return false; + break; + } + } + return true; +} + +size_t mg_mqtt_next_prop(struct mg_mqtt_message *msg, struct mg_mqtt_prop *prop, + size_t ofs) { + uint8_t *props = (uint8_t *) msg->dgram.buf + msg->props_start; + uint8_t *props_end = props + msg->props_size; + uint8_t *i = props + ofs; + size_t new_pos = ofs, len; + + if (msg->props_start > msg->dgram.len || + msg->props_size > msg->dgram.len - msg->props_start || + ofs >= msg->props_size) + return 0; + + memset(prop, 0, sizeof(struct mg_mqtt_prop)); + prop->id = i[0]; + i++, new_pos++; + + switch (mqtt_prop_type_by_id(prop->id)) { + case MQTT_PROP_TYPE_STRING_PAIR: + if (i + 2 > props_end) return 0; + prop->key.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]); + if (i + 2 + prop->key.len > props_end) return 0; + prop->key.buf = (char *) i + 2; + i += 2 + prop->key.len; + if (i + 2 > props_end) return 0; + prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]); + prop->val.buf = (char *) i + 2; + if (i + 2 + prop->val.len > props_end) return 0; + new_pos += 2 * sizeof(uint16_t) + prop->val.len + prop->key.len; + break; + case MQTT_PROP_TYPE_BYTE: + if (i + 1 > props_end) return 0; + prop->iv = (uint8_t) i[0]; + new_pos++; + break; + case MQTT_PROP_TYPE_SHORT: + if (i + 2 > props_end) return 0; + prop->iv = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]); + new_pos += sizeof(uint16_t); + break; + case MQTT_PROP_TYPE_INT: + if (i + 4 > props_end) return 0; + 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: + if (i + 2 > props_end) return 0; + prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]); + prop->val.buf = (char *) i + 2; + if (i + 2 + prop->val.len > props_end) return 0; + new_pos += 2 + prop->val.len; + break; + case MQTT_PROP_TYPE_BINARY_DATA: + if (i + 2 > props_end) return 0; + prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]); + prop->val.buf = (char *) i + 2; + if (i + 2 + prop->val.len > props_end) return 0; + new_pos += 2 + prop->val.len; + break; + case MQTT_PROP_TYPE_VARIABLE_INT: + len = decode_varint(i, (size_t) (props_end - i), &prop->iv); + if (i + len > props_end) return 0; + new_pos = (len == 0) ? 0 : new_pos + len; + break; + default: + new_pos = 0; + break; + } + + 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', 0, 0}; + hdr[6] = opts->version; + + 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); + } + + // keepalive == 0 means "do not disconnect us!" + if (!mqtt_send_header(c, MQTT_CMD_CONNECT, 0, (uint32_t) total_len) || + !mg_send(c, hdr, sizeof(hdr)) || + !mg_send_u16(c, mg_htons((uint16_t) opts->keepalive))) + goto fail; + + if (c->is_mqtt5 && !mg_send_mqtt_properties(c, opts->props, opts->num_props)) + goto fail; + + if (!mg_send_u16(c, mg_htons((uint16_t) cid.len)) || + !mg_send(c, cid.buf, cid.len)) + goto fail; + + if (hdr[7] & MQTT_HAS_WILL) { + if (c->is_mqtt5 && + !mg_send_mqtt_properties(c, opts->will_props, opts->num_will_props)) + goto fail; + + if (!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)) + goto fail; + } + 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))) + goto fail; + 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))) + goto fail; + return; +fail: + mg_error(c, "OOM"); +} + +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", c->id, (int) opts->topic.len, + (char *) opts->topic.buf, + (int) (opts->message.len <= 10 ? opts->message.len : 10), + (char *) opts->message.buf, opts->message.len <= 10 ? ']' : ' ')); + 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; + if (!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)) + goto fail; + 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; + } + if (!mg_send_u16(c, mg_htons(id))) goto fail; + } + + if (c->is_mqtt5 && !mg_send_mqtt_properties(c, opts->props, opts->num_props)) + goto fail; + + if (opts->message.len > 0 && + !mg_send(c, opts->message.buf, opts->message.len)) + goto fail; + return id; + +fail: + mg_error(c, "OOM"); + return id; +} + +static void mg_mqtt_sub_unsub(struct mg_connection *c, + const struct mg_mqtt_opts *opts, uint8_t cmd) { + uint8_t qos_ = opts->qos & 3; + bool is_sub = cmd == MQTT_CMD_SUBSCRIBE; + size_t plen = c->is_mqtt5 ? get_props_size(opts->props, opts->num_props) : 0; + size_t len = 2 + opts->topic.len + 2 + (is_sub ? 1 : 0) + plen; + + if (!mqtt_send_header(c, cmd, 2, (uint32_t) len)) goto fail; + if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id; + if (!mg_send_u16(c, mg_htons(c->mgr->mqtt_id))) goto fail; + + if (c->is_mqtt5 && !mg_send_mqtt_properties(c, opts->props, opts->num_props)) + goto fail; + + if (!mg_send_u16(c, mg_htons((uint16_t) opts->topic.len)) || + !mg_send(c, opts->topic.buf, opts->topic.len)) + goto fail; + if (is_sub && !mg_send(c, &qos_, sizeof(qos_))) goto fail; + return; +fail: + mg_error(c, "OOM"); +} + +void mg_mqtt_sub(struct mg_connection *c, const struct mg_mqtt_opts *opts) { + mg_mqtt_sub_unsub(c, opts, MQTT_CMD_SUBSCRIBE); +} + +void mg_mqtt_unsub(struct mg_connection *c, const struct mg_mqtt_opts *opts) { + mg_mqtt_sub_unsub(c, opts, MQTT_CMD_UNSUBSCRIBE); +} + +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, tmp; + + 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), &tmp); + if (!len_len) return MQTT_MALFORMED; + m->props_size = (size_t) tmp; + 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", c->id, (int) mm.topic.len, + mm.topic.buf, + (int) (mm.data.len <= 10 ? mm.data.len : 10), mm.data.buf, + mm.data.len <= 10 ? ']' : ' ')); + 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 + + if (!mqtt_send_header(c, + (uint8_t) (mm.qos == 2 ? MQTT_CMD_PUBREC + : MQTT_CMD_PUBACK), + 0, remaining_len) || + !mg_send(c, &id, sizeof(id))) + goto fail; + + if (c->is_mqtt5) { + uint16_t zero = 0; + if (!mg_send(c, &zero, sizeof(zero))) goto fail; + } + } + 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 + if (!mqtt_send_header(c, MQTT_CMD_PUBREL, 2, + remaining_len) // MQTT5 3.6.1-1, flags = 2 + || !mg_send(c, &id, sizeof(id))) + goto fail; + 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 + if (!mqtt_send_header(c, MQTT_CMD_PUBCOMP, 0, remaining_len) || + !mg_send(c, &id, sizeof(id))) + goto fail; + break; + } + } + mg_call(c, MG_EV_MQTT_CMD, &mm); + mg_iobuf_del(&c->recv, 0, mm.dgram.len); + } else { + break; + } + } + } + (void) ev_data; + return; +fail: + mg_error(c, "OOM"); +} + +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); + if (!mqtt_send_header(c, MQTT_CMD_DISCONNECT, 0, (uint32_t) len)) goto fail; + + if (c->is_mqtt5) { + uint8_t zero = 0; + if (!mg_send(c, &zero, sizeof(zero)) // reason code + || !mg_send_mqtt_properties(c, opts->props, opts->num_props)) + goto fail; + } + return; +fail: + mg_error(c, "OOM"); +} + +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_svc(mgr, url, fn, fn_data, mqtt_cb, NULL); + if (c != NULL) { + struct mg_mqtt_opts empty; + memset(&empty, 0, sizeof(empty)); + mg_mqtt_login(c, opts == NULL ? &empty : opts); + } + 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; + size_t expected = mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap); + size_t actual = c->send.len - old; + if (actual != expected) { + mg_error(c, "OOM"); + c->send.len = old; + actual = 0; + } + return actual; +} + +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->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->addr.ip, 0, sizeof(addr->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->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->addr.ip, sizeof(ipv4)); + memset(addr->addr.ip, 0, sizeof(addr->addr.ip)); + addr->addr.ip[10] = addr->addr.ip[11] = 255; + memcpy(&addr->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; // sets addr->is_ip6 + 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 = 0; // 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->addr.ip[n] = (uint8_t) ((val >> 8) & 255); + addr->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->addr.ip[n] = addr->addr.ip[n + 1] = 0; // For trailing :: + } else if (str.buf[i] == '%') { // Scope ID, last in string + if (mg_str_to_num(mg_str_n(&str.buf[i + 1], str.len - i - 1), 10, + &addr->scope_id, sizeof(uint8_t))) { + addr->is_ip6 = true; + return true; + } else { + return false; + } + } else { + return false; + } + } + if (n < 14 && dc == 42) return false; + if (n < 14) { + memmove(&addr->addr.ip[dc + (14 - n)], &addr->addr.ip[dc], n - dc + 2); + memset(&addr->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 *) mg_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)); + mg_free(c); +} + +struct mg_connection *mg_connect_svc(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data, + mg_event_handler_t pfn, void *pfn_data) { + struct mg_connection *c = NULL; + if (url == NULL || url[0] == '\0') { + MG_ERROR(("null url")); +#if MG_ENABLE_TCPIP + } else if (mgr->ifp != NULL && mgr->ifp->state != MG_TCPIP_STATE_READY) { + MG_ERROR(("Network is down")); +#endif + } 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; + c->is_tls = (mg_url_is_ssl(url) != 0); + c->pfn = pfn; + c->pfn_data = pfn_data; + mg_call(c, MG_EV_OPEN, (void *) url); + MG_DEBUG(("%lu %ld %s", c->id, c->fd, url)); + mg_resolve(c, url); + } + return c; +} + +struct mg_connection *mg_connect(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data) { + return mg_connect_svc(mgr, url, fn, fn_data, NULL, NULL); +} + +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", url)); + MG_PROF_FREE(c); + mg_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; + c->is_tls = (mg_url_is_ssl(url) != 0); + mg_call(c, MG_EV_OPEN, NULL); + 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 *) mg_calloc(1, sizeof(*t)); + if (t != NULL) { + flags |= MG_TIMER_AUTODELETE; // We have alloc'ed it, so autodelete + mg_timer_init(&mgr->timers, t, milliseconds, flags, fn, arg); + } + 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, mg_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); +#if MG_ENABLE_TCPIP + if (mgr->ifp) mg_tcpip_free(mgr->ifp); +#endif +} + +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 MG_ARCH == MG_ARCH_UNIX + // 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 +#if MG_ENABLE_BSD_SOCKETS + mgr->pipe.q = NULL; +#else + mgr->pipe.fd = MG_INVALID_SOCKET; +#endif + 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); + MG_DEBUG(("MG_IO_SIZE: %lu, TLS: %s", MG_IO_SIZE, + MG_TLS == MG_TLS_NONE ? "none" + : MG_TLS == MG_TLS_MBED ? "MbedTLS" + : MG_TLS == MG_TLS_OPENSSL ? "OpenSSL" + : MG_TLS == MG_TLS_BUILTIN ? "builtin" + : MG_TLS == MG_TLS_WOLFSSL ? "WolfSSL" + : "custom")); +} + +#if MG_ENABLE_TCPIP +void mg_tcpip_mapip(struct mg_connection *, struct mg_addr *); +#endif +void mg_multicast_restore(struct mg_connection *c, uint8_t *from) { + memcpy(&c->rem, from, sizeof(c->rem)); +#if MG_ENABLE_TCPIP + mg_tcpip_mapip(c, &c->rem); +#endif +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/net_builtin.c" +#endif + + + +#if MG_ENABLE_TCPIP +#define MG_EPHEMERAL_PORT_BASE 32768 +#define PDIFF(a, b) ((size_t) (((char *) (b)) - ((char *) (a)))) + +#ifndef MG_TCPIP_KEEPALIVE_MS +#define MG_TCPIP_KEEPALIVE_MS 45000 // TCP keep-alive period, ms +#endif + +#define MG_TCPIP_ACK_MS 150 // Timeout for ACKing +#define MG_TCPIP_ARP_MS 100 // Timeout for ARP response +#define MG_TCPIP_SYN_MS 15000 // Timeout for connection establishment +#define MG_TCPIP_FIN_MS 1000 // Timeout for closing connection + +#ifndef MG_TCPIP_WIN +#define MG_TCPIP_WIN 6000 // TCP window size +#endif + +struct connstate { + uint32_t seq, ack; // TCP seq/ack counters + uint64_t timer; // TCP timer (see 'ttype' below) + uint32_t acked; // Last ACK-ed number + size_t unacked; // Not acked bytes + uint32_t maxseq; // Max send seq (ack + window) + uint16_t win; // destination current window size + uint16_t dmss; // destination MSS (from TCP opts) + uint8_t mac[sizeof(struct mg_l2addr)]; // Peer hw address + uint8_t ttype; // Timer type: +#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 + bool fin_rcvd; // We have received FIN from the peer + bool twclosure; // 3-way closure done +}; + +#if defined(__DCC__) +#pragma pack(1) +#else +#pragma pack(push, 1) +#endif + +struct ip { + uint8_t ver; // Version + uint8_t tos; // Unused + uint16_t len; // Datagram length + uint16_t id; // Unused + uint16_t frag; // Fragmentation +#define IP_FRAG_OFFSET_MSK 0x1fff +#define IP_MORE_FRAGS_MSK 0x2000 + 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 label[3]; // Flow label + uint16_t plen; // Payload length + uint8_t next; // Upper level protocol + uint8_t hops; // Hop limit + uint64_t src[2]; // Source IP + uint64_t dst[2]; // Destination IP +}; + +struct icmp { + uint8_t type; + uint8_t code; + uint16_t csum; +}; + +struct icmp6 { + uint8_t type; + uint8_t code; + uint16_t csum; +}; + +struct ndp_na { + uint8_t res[4]; // R S O, reserved + uint64_t addr[2]; // Target address +}; + +struct ndp_ra { + uint8_t cur_hop_limit; + uint8_t flags; // M,O,Prf,Resvd + uint16_t router_lifetime; + uint32_t reachable_time; + uint32_t retrans_timer; +}; + +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_STDFLAGS 0x3f + // #define TH_ECE 0x40 // not part of TCP but RFC-3168 (ECN) + // #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[30 + sizeof(((struct mg_tcpip_if *) 0)->dhcp_name)]; +}; + +struct dhcp6 { + union { + uint8_t type; + uint32_t xid; + }; + uint8_t options[30 + sizeof(((struct mg_tcpip_if *) 0)->dhcp_name)]; +}; + +struct pseudoip { + uint32_t src; // Source IP + uint32_t dst; // Destination IP + uint8_t zero; + uint8_t proto; // Upper level protocol + uint16_t len; // Datagram length +}; + +struct pseudoip6 { + uint64_t src[2]; // Source IP + uint64_t dst[2]; // Destination IP + uint32_t plen; // Payload length + uint8_t zero[3]; + uint8_t next; // Upper level protocol +}; + +#if defined(__DCC__) +#pragma pack(0) +#else +#pragma pack(pop) +#endif + +// pkt is 8-bit aligned, pointers to headers hint compilers to generate +// byte-copy code for micros with alignment constraints +struct pkt { + struct mg_str raw; // Raw packet data + struct mg_str pay; // Payload data + uint8_t *l2; // Ethernet, PPP [, etc] frame data + struct arp *arp; + struct ip *ip; + struct ip6 *ip6; + struct icmp *icmp; + struct icmp6 *icmp6; + struct tcp *tcp; + struct udp *udp; + struct dhcp *dhcp; + struct dhcp6 *dhcp6; +}; + +// L2 API +void mg_l2_init(struct mg_tcpip_if *ifp); +uint8_t *mg_l2_header(struct mg_tcpip_if *ifp, enum mg_l2proto proto, + uint8_t *src, uint8_t *dst, uint8_t *frame); +size_t mg_l2_trailer(struct mg_tcpip_if *ifp, size_t len, uint8_t *cur); +bool mg_l2_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto, + struct mg_str *pay, struct mg_str *raw); +uint8_t *mg_l2_getaddr(struct mg_tcpip_if *ifp, uint8_t *frame); +uint8_t *mg_l2_mapip(enum mg_l2type type, enum mg_l2addrtype addrtype, + struct mg_addr *ip); +#if MG_ENABLE_IPV6 +bool mg_l2_genip6(enum mg_l2type type, uint64_t *ip6, uint8_t prefix_len, + uint8_t *addr); +bool mg_l2_ip6get(enum mg_l2type type, uint8_t *addr, uint8_t *opts, + uint8_t len); +uint8_t mg_l2_ip6put(enum mg_l2type type, uint8_t *addr, uint8_t *opts); +#endif +bool mg_l2_poll(struct mg_tcpip_if *ifp, bool expired_1000ms); + +static void mg_tcpip_call(struct mg_tcpip_if *ifp, int ev, void *ev_data) { +#if 0 && MG_ENABLE_PROFILE + const char *names[] = {"TCPIP_EV_STATE_CHANGE", "TCPIP_EV_DHCP_DNS", + "TCPIP_EV_DHCP_SNTP", "TCPIP_EV_ARP", + "TCPIP_EV_TIMER_1S", "TCPIP_EV_WIFI_SCAN_RESULT", + "TCPIP_EV_WIFI_SCAN_END", "TCPIP_EV_WIFI_CONNECT_ERR", + "TCPIP_EV_DRIVER", "TCPIP_EV_STATE6_CHANGE", + "TCPIP_EV_USER"}; + if (ev != MG_TCPIP_EV_TIMER_1S && ev < (int) (sizeof(names) / sizeof(names[0]))) { + MG_PROF_ADD(ifp, names[ev]); // TODO(): call MG_PROF_DUMP() MG_PROF_FREE() + } +#endif + // Fire protocol handler first, user handler second. See #2559 + if (ifp->pfn != NULL) ifp->pfn(ifp, ev, ev_data); + if (ifp->fn != NULL) ifp->fn(ifp, ev, ev_data); +} + +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->pay.buf[pkt->pay.len] - (char *) p)); +} + +// NOTE(): DOES NOT handle reentries after odd length, use last +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((uint16_t) ((uint16_t) ~sum & 0xffff)); +} + +static uint16_t ipcsum(const void *buf, size_t len) { + uint32_t sum = csumup(0, buf, len); + return csumfin(sum); +} + +static bool ipcsum_ok(const void *d) { + struct ip *ip = (struct ip *) d; + return (ipcsum(d, (ip->ver & 0x0F) * 4) == 0); +} + +static bool icmpcsum_ok(const void *d, size_t len) { + return (ipcsum(d, len) == 0); +} + +static uint16_t pcsum(void *d, void *p, size_t plen) { + uint32_t sum; + struct ip *ip = (struct ip *) d; +#if defined(__DCC__) + volatile /* Makes PPC & Diab4.3 happy */ +#endif + struct pseudoip pip; + pip.src = ip->src; + pip.dst = ip->dst; + pip.zero = 0; + pip.proto = ip->proto; + pip.len = mg_htons((uint16_t) plen); + sum = csumup(0, &pip, sizeof(pip)); // even length + sum = csumup(sum, p, plen); // possibly odd length: last + return csumfin(sum); +} + +static bool udpcsum_ok(void *d, void *u) { + struct udp *udp = (struct udp *) u; + if (udp->csum == 0) return true; + if (udp->csum == 0xFFFF) udp->csum = 0; + return (pcsum(d, u, (size_t) mg_ntohs(udp->len)) == 0); +} + +static bool tcpcsum_ok(void *d, void *t) { + struct ip *ip = (struct ip *) d; + return (pcsum(d, t, (size_t) (mg_ntohs(ip->len) - (ip->ver & 0x0F) * 4)) == + 0); +} + +#if MG_ENABLE_IPV6 +static uint16_t p6csum(void *d, void *p, size_t plen) { + uint32_t sum; + struct ip6 *ip6 = (struct ip6 *) d; +#if defined(__DCC__) + volatile /* Makes PPC & Diab4.3 happy */ +#endif + struct pseudoip6 pip6; + pip6.src[0] = ip6->src[0], pip6.src[1] = ip6->src[1]; + pip6.dst[0] = ip6->dst[0], pip6.dst[1] = ip6->dst[1]; + pip6.zero[0] = 0, pip6.zero[1] = 0, pip6.zero[2] = 0; + pip6.plen = mg_htonl((uint32_t) plen); + pip6.next = ip6->next; + sum = csumup(0, &pip6, sizeof(pip6)); // even length + sum = csumup(sum, p, plen); // possibly odd length: last + return csumfin(sum); +} + +static bool udp6csum_ok(void *d, void *u) { + struct udp *udp = (struct udp *) u; + if (udp->csum == 0) return false; // mandatory in IPv6 + if (udp->csum == 0xFFFF) udp->csum = 0; + return (p6csum(d, u, (size_t) mg_ntohs(udp->len)) == 0); +} +static bool tcp6csum_ok(void *d, void *t) { + struct ip6 *ip6 = (struct ip6 *) d; + return (p6csum(d, t, (size_t) mg_ntohs(ip6->plen)) == 0); +} +static bool icmp6csum_ok(void *d, void *i) { + struct ip6 *ip6 = (struct ip6 *) d; + return (p6csum(d, i, (size_t) mg_ntohs(ip6->plen)) == 0); +} + +static void ip6sn(uint64_t *addr, uint64_t *sn_addr) { + // Build solicited-node multicast address from a given unicast IP + // RFC-4291 2.7 + uint8_t *sn = (uint8_t *) sn_addr; + memset(sn_addr, 0, 16); + sn[0] = 0xff; + sn[1] = 0x02; + sn[11] = 0x01; + sn[12] = 0xff; + sn[13] = ((uint8_t *) addr)[13]; + sn[14] = ((uint8_t *) addr)[14]; + sn[15] = ((uint8_t *) addr)[15]; +} + +static const struct mg_addr ip6_allrouters = { + {{0xFF, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x02}}, 0, 0, true}; +static const struct mg_addr ip6_allnodes = { + {{0xFF, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01}}, 0, 0, true}; + +#define MG_IP6MATCH(a, b) (a[0] == b[0] && a[1] == b[1]) +#endif + +static void settmout(struct mg_connection *c, uint8_t type) { + struct mg_tcpip_if *ifp = c->mgr->ifp; + struct connstate *s = (struct connstate *) (c + 1); + unsigned n = type == MIP_TTYPE_ACK ? MG_TCPIP_ACK_MS + : type == MIP_TTYPE_ARP ? MG_TCPIP_ARP_MS + : type == MIP_TTYPE_SYN ? MG_TCPIP_SYN_MS + : type == MIP_TTYPE_FIN ? MG_TCPIP_FIN_MS + : MG_TCPIP_KEEPALIVE_MS; + if (s->ttype == MIP_TTYPE_FIN) return; // skip if 3-way closing + s->timer = ifp->now + n; + s->ttype = type; + MG_VERBOSE(("%lu %d -> %llx", c->id, type, s->timer)); +} + +static size_t driver_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; +} + +// RFC826, ARP assumes Ethernet MAC addresses +void mg_tcpip_arp_request(struct mg_tcpip_if *ifp, uint32_t ip, uint8_t *mac) { + uint8_t *l2p = (uint8_t *) ifp->tx.buf; + struct arp *arp = (struct arp *) mg_l2_header( + ifp, MG_TCPIP_L2PROTO_ARP, ifp->mac, + mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_BCAST, NULL), l2p); + 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)); + if (mac != NULL) memcpy(arp->tha, mac, sizeof(arp->tha)); + driver_output(ifp, mg_l2_trailer(ifp, sizeof(*arp), (uint8_t *) (arp + 1))); +} + +static void onstatechange(struct mg_tcpip_if *ifp) { + if (ifp->state == MG_TCPIP_STATE_IP && + (ifp->l2type == MG_TCPIP_L2_PPP || ifp->l2type == MG_TCPIP_L2_PPPoE)) + ifp->state = MG_TCPIP_STATE_READY; + 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)); + if (ifp->l2type == MG_TCPIP_L2_ETH || + ifp->l2type == MG_TCPIP_L2_PPPoE) // TODO(): print other l2 + MG_INFO((" MAC: %M", mg_print_mac, ifp->mac)); + if (ifp->l2type == MG_TCPIP_L2_ETH) + mg_tcpip_arp_request(ifp, ifp->ip, ifp->mac); // gratuitous ARP annc + if (ifp->is_ip_changed) { + struct mg_connection *c; + for (c = ifp->mgr->conns; c != NULL; c = c->next) { + if (!c->is_listening && !c->is_udp) c->is_closing = 1; + } + ifp->is_ip_changed = false; + } + } else if (ifp->state == MG_TCPIP_STATE_IP) { + if (ifp->gw != 0 && ifp->l2type == MG_TCPIP_L2_ETH) + mg_tcpip_arp_request(ifp, ifp->gw, NULL); // unsolicited GW ARP request + } else if (ifp->state == MG_TCPIP_STATE_UP) { + srand((unsigned int) mg_millis()); + } else if (ifp->state == MG_TCPIP_STATE_DOWN) { + MG_ERROR(("Link down")); + } + mg_tcpip_call(ifp, MG_TCPIP_EV_STATE_CHANGE, &ifp->state); +} + +static struct ip *tx_ip(struct mg_tcpip_if *ifp, uint8_t *l2_dst, uint8_t proto, + uint32_t ip_src, uint32_t ip_dst, size_t plen) { + // ifp->tx.buf is 8-bit aligned, keep other headers as pointers, see pkt + uint8_t *l2p = (uint8_t *) ifp->tx.buf; + struct ip *ip = (struct ip *) mg_l2_header(ifp, MG_TCPIP_L2PROTO_IPV4, + ifp->mac, l2_dst, l2p); + memset(ip, 0, sizeof(*ip)); + ip->ver = 0x45; // Version 4, header length 5 words + ip->frag = mg_htons(0x4000); // 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; +} + +#if MG_ENABLE_IPV6 +static struct ip6 *tx_ip6(struct mg_tcpip_if *ifp, uint8_t *l2_dst, + uint8_t next, uint64_t *ip_src, uint64_t *ip_dst, + size_t plen); +#endif + +static bool tx_udp(struct mg_tcpip_if *ifp, uint8_t *l2_dst, + struct mg_addr *ip_src, struct mg_addr *ip_dst, + const void *buf, size_t len) { + uint8_t *l3p; + size_t l2_len; + struct ip *ip = NULL; + struct udp *udp; +#if MG_ENABLE_IPV6 + struct ip6 *ip6 = NULL; + if (ip_dst->is_ip6) { + ip6 = tx_ip6(ifp, l2_dst, 17, ip_src->addr.ip6, ip_dst->addr.ip6, + len + sizeof(struct udp)); + udp = (struct udp *) (ip6 + 1); + l2_len = sizeof(*ip6) + sizeof(*udp) + len; + l3p = (uint8_t *) ip6; + } else +#endif + { + ip = tx_ip(ifp, l2_dst, 17, ip_src->addr.ip4, ip_dst->addr.ip4, + len + sizeof(struct udp)); + udp = (struct udp *) (ip + 1); + l2_len = sizeof(*ip) + sizeof(*udp) + len; + l3p = (uint8_t *) ip; + } + udp->sport = ip_src->port; + udp->dport = ip_dst->port; + udp->len = mg_htons((uint16_t) (sizeof(*udp) + len)); + udp->csum = 0; + memmove(udp + 1, buf, len); +#if MG_ENABLE_IPV6 + if (ip_dst->is_ip6) { + udp->csum = p6csum(ip6, udp, sizeof(*udp) + len); + } else +#endif + { + udp->csum = pcsum(ip, udp, sizeof(*udp) + len); + } + l2_len = mg_l2_trailer(ifp, l2_len, l3p + l2_len); + return (driver_output(ifp, l2_len) == l2_len); +} + +static bool tx_udp4(struct mg_tcpip_if *ifp, uint8_t *l2_dst, uint32_t ip_src, + uint16_t sport, uint32_t ip_dst, uint16_t dport, + const void *buf, size_t len) { + struct mg_addr ips, ipd; + memset(&ips, 0, sizeof(ips)); + ips.addr.ip4 = ip_src; + ips.port = sport; + memset(&ipd, 0, sizeof(ipd)); + ipd.addr.ip4 = ip_dst; + ipd.port = dport; + return tx_udp(ifp, l2_dst, &ips, &ipd, buf, len); +} + +static void tx_dhcp(struct mg_tcpip_if *ifp, uint8_t *l2_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 + // NOTE(): assumes Ethernet: htype=1 hlen=6, copy 6 bytes + 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, 6); + memcpy(&dhcp.xid, ifp->mac + 2, sizeof(dhcp.xid)); + memcpy(&dhcp.options, opts, optslen); + if (ciaddr) dhcp.ciaddr = ip_src; + tx_udp4(ifp, l2_dst, ip_src, mg_htons(68), ip_dst, mg_htons(67), &dhcp, + sizeof(dhcp)); +} + +// RFC-2131 #4.3.6, #4.4.1; RFC-2132 #9.8 +static void tx_dhcp_request_sel(struct mg_tcpip_if *ifp, uint32_t ip_req, + uint32_t ip_srv) { + uint8_t extra = (uint8_t) ((ifp->enable_req_dns ? 1 : 0) + + (ifp->enable_req_sntp ? 1 : 0)); + size_t len = strlen(ifp->dhcp_name); + size_t olen = 21 + len + extra + 2 + 1; // Total length of options +#define OPTS_MAXLEN (21 + sizeof(ifp->dhcp_name) + 2 + 2 + 1) + uint8_t opts[OPTS_MAXLEN]; // Allocate options (max size possible) + uint8_t *p = opts; + assert(olen <= sizeof(opts)); + memset(opts, 0, sizeof(opts)); + *p++ = 53, *p++ = 1, *p++ = 3; // Type: DHCP request + *p++ = 54, *p++ = 4, memcpy(p, &ip_srv, 4), p += 4; // DHCP server ID + *p++ = 50, *p++ = 4, memcpy(p, &ip_req, 4), p += 4; // Requested IP + *p++ = 12, *p++ = (uint8_t) (len & 255); // DHCP host + memcpy(p, ifp->dhcp_name, len), p += len; // name + *p++ = 55, *p++ = 2 + extra, *p++ = 1, *p++ = 3; // GW, MASK + if (ifp->enable_req_dns) *p++ = 6; // DNS + if (ifp->enable_req_sntp) *p++ = 42; // SNTP + *p++ = 255; // End of options + // assert((size_t) (p - opts) < olen); + tx_dhcp(ifp, mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_BCAST, NULL), 0, + 0xffffffff, opts, olen, 0); + 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 *l2_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, l2_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, mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_BCAST, NULL), 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 && pkt->arp->spa == c->rem.addr.ip4) break; +#if MG_ENABLE_IPV6 + if (c->is_arplooking && pkt->icmp6 && pkt->icmp6->type == 136) { + struct ndp_na *na = (struct ndp_na *) (pkt->icmp6 + 1); + if (MG_IP6MATCH(na->addr, c->rem.addr.ip6)) break; + } +#endif + if (c->is_udp && pkt->udp && c->loc.port == pkt->udp->dport && + !(c->loc.is_ip6 ^ (pkt->ip6 != NULL))) // IP or IPv6 to same dest + break; + if (!c->is_udp && pkt->tcp && c->loc.port == pkt->tcp->dport && + ((lsn && c->is_listening && !(c->loc.is_ip6 ^ (pkt->ip6 != NULL))) || + (!lsn && !c->is_listening && c->rem.port == pkt->tcp->sport && + ((!c->loc.is_ip6 && c->rem.addr.ip4 == pkt->ip->src) +#if MG_ENABLE_IPV6 + || (c->loc.is_ip6 && MG_IP6MATCH(c->rem.addr.ip6, pkt->ip6->src)) +#endif + )))) // validate addr for established (not listening) conns + break; + } + return c; +} + +static void l2addr_resolved(struct mg_connection *c); +static uint8_t *get_return_l2addr(struct mg_tcpip_if *ifp, struct mg_addr *rem, + bool is_udp, struct pkt *pkt); + +// RFC826, ARP assumes Ethernet MAC addresses +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_VERBOSE(("ARP req from %M", mg_print_ip4, &pkt->arp->spa)); + uint8_t *l2p = (uint8_t *) ifp->tx.buf; + struct arp *arp = (struct arp *) mg_l2_header( + ifp, MG_TCPIP_L2PROTO_ARP, ifp->mac, mg_l2_getaddr(ifp, pkt->l2), l2p); + *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)); + driver_output(ifp, mg_l2_trailer(ifp, sizeof(*arp), (uint8_t *) (arp + 1))); + } else if (pkt->arp->op == mg_htons(2)) { + if (memcmp(pkt->arp->tha, ifp->mac, sizeof(pkt->arp->tha)) != 0) return; + // MG_VERBOSE(("ARP resp from %M", mg_print_ip4, &pkt->arp->spa)); + if (pkt->arp->spa == ifp->gw) { + // Got response for the GW ARP request. Set ifp->gwmac and IP -> READY + memcpy(ifp->gwmac, pkt->arp->sha, sizeof(ifp->gwmac)); + ifp->gw_ready = true; + if (ifp->state == MG_TCPIP_STATE_IP) { + ifp->state = MG_TCPIP_STATE_READY; + onstatechange(ifp); + } + } 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.addr.ip4, mg_print_mac, s->mac)); + c->is_arplooking = 0; + l2addr_resolved(c); + } + } + } +} + +static void rx_icmp(struct mg_tcpip_if *ifp, struct pkt *pkt) { + size_t plen = pkt->pay.len; + if (!icmpcsum_ok(pkt->icmp, sizeof(struct icmp) + plen)) return; + if (pkt->icmp->type == 8 && pkt->ip != NULL && pkt->ip->dst == ifp->ip) { + size_t l2_max_overhead = ifp->framesize - ifp->l2mtu; + size_t hlen = sizeof(struct ip) + sizeof(struct icmp); + size_t room = ifp->tx.len - hlen - l2_max_overhead; + uint8_t *l2addr; + struct ip *ip; + struct icmp *icmp; + struct mg_addr ips; + ips.addr.ip4 = pkt->ip->src; + ips.is_ip6 = false; + if ((l2addr = get_return_l2addr(ifp, &ips, false, pkt)) == NULL) + return; // safety net for lousy networks + if (plen > room) plen = room; + ip = tx_ip(ifp, l2addr, 1, ifp->ip, pkt->ip->src, sizeof(*icmp) + plen); + icmp = (struct icmp *) (ip + 1); + memset(icmp, 0, sizeof(*icmp)); // Set csum, type, code to 0 + memcpy(icmp + 1, pkt->pay.buf, plen); // Copy RX payload to TX + icmp->csum = ipcsum(icmp, sizeof(*icmp) + plen); + driver_output( + ifp, mg_l2_trailer(ifp, hlen + plen, (uint8_t *) ip + hlen + plen)); + } +} + +static void setdns4(struct mg_tcpip_if *ifp, uint32_t *ip); + +static bool dhcp_opt_len_ok(uint8_t len, uint8_t *p, uint8_t *end) { + return (len >= 4 && (len & 3) == 0 && p + 6 < end); +} + +static void rx_dhcp_client(struct mg_tcpip_if *ifp, struct pkt *pkt) { + uint32_t ip = 0, gw = 0, mask = 0, lease = 0, dns = 0, sntp = 0, owner = 0; + uint8_t msgtype = 0, state = ifp->state; + // perform size check first, then access fields + uint8_t *p = (uint8_t *) pkt->pay.buf, + *end = (uint8_t *) &pkt->pay.buf[pkt->pay.len]; + // min header length checked at payload calculation, options are optional + if (memcmp(&pkt->dhcp->xid, ifp->mac + 2, sizeof(pkt->dhcp->xid))) return; + while (p + 1 < end && p[0] != 255) { // Parse options, get #1; RFC-2132 9 + if (p[0] == 1 && p[1] == 4 && p + 6 < end) { // Mask, 3.3 + memcpy(&mask, p + 2, sizeof(mask)); + } else if (p[0] == 3 && dhcp_opt_len_ok(p[1], p, end)) { // GW, 3.5 + memcpy(&gw, p + 2, sizeof(gw)); + ip = pkt->dhcp->yiaddr; + } else if (ifp->enable_req_dns && p[0] == 6 && + dhcp_opt_len_ok(p[1], p, end)) { // DNS, 3.8 + memcpy(&dns, p + 2, sizeof(dns)); + } else if (ifp->enable_req_sntp && p[0] == 42 && + dhcp_opt_len_ok(p[1], p, end)) { // SNTP, 8.3 + memcpy(&sntp, p + 2, sizeof(sntp)); + } 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]; + } else if (p[0] == 54 && p[1] == 4 && p + 6 < end) { // Server id 9.7 + memcpy(&owner, p + 2, sizeof(sntp)); // This is the lease owner + } + 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 a server (2131 4.4.1, 2132 9.7): lease owner takes precedence, + // otherwise use siaddr (fallback to IP source addr on foul play). + // This is broadcast, otherwise siaddr would be the destination. + tx_dhcp_request_sel(ifp, ip, + owner ? owner + : 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 + uint64_t rand; + ifp->lease_expire = ifp->now + (uint64_t) lease * 1000; + MG_INFO(("Lease: %u sec (%lld)", lease, ifp->lease_expire / 1000)); + // assume DHCP server = router until ARP resolves + memcpy(ifp->gwmac, mg_l2_getaddr(ifp, pkt->l2), sizeof(ifp->gwmac)); + ifp->gw_ready = true; // NOTE(): actual gw ARP won't retry now + if (ifp->ip != ip) ifp->is_ip_changed = true; + ifp->ip = ip, ifp->gw = gw, ifp->mask = mask; + ifp->state = MG_TCPIP_STATE_IP; // BOUND state + mg_random(&rand, sizeof(rand)); + srand((unsigned int) (rand + mg_millis())); + if (ifp->enable_req_dns && dns != 0) { + setdns4(ifp, &dns); + mg_tcpip_call(ifp, MG_TCPIP_EV_DHCP_DNS, &dns); + } + if (ifp->enable_req_sntp && sntp != 0) + mg_tcpip_call(ifp, MG_TCPIP_EV_DHCP_SNTP, &sntp); + } else if (ifp->state == MG_TCPIP_STATE_READY && ifp->ip == ip) { // renew + ifp->lease_expire = ifp->now + (uint64_t) 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 *mac; + uint8_t op = 0, *p = (uint8_t *) pkt->pay.buf, + *end = (uint8_t *) &pkt->pay.buf[pkt->pay.len]; + // NOTE(): assumes Ethernet: htype=1 hlen=6, copy 6 bytes + struct dhcp res = {2, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}}; + // min header length checked at payload calculation, options are optional + 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, 0, // 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 + }; + opts[2] = msg; + 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; + mac = mg_l2_getaddr(ifp, pkt->l2); + if (ifp->enable_get_gateway) { + ifp->gw = res.yiaddr; // set gw IP, best-effort gwmac as DHCP server's + memcpy(ifp->gwmac, mac, sizeof(ifp->gwmac)); + } + tx_udp4(ifp, mac, ifp->ip, mg_htons(67), op == 1 ? ~0U : res.yiaddr, + mg_htons(68), &res, sizeof(res)); + } +} + +#if MG_ENABLE_IPV6 +static struct ip6 *tx_ip6(struct mg_tcpip_if *ifp, uint8_t *l2_dst, + uint8_t next, uint64_t *ip_src, uint64_t *ip_dst, + size_t plen) { + // ifp->tx.buf is 8-bit aligned, keep other headers as pointers, see pkt + uint8_t *l2p = (uint8_t *) ifp->tx.buf; + struct ip6 *ip6 = (struct ip6 *) mg_l2_header(ifp, MG_TCPIP_L2PROTO_IPV6, + ifp->mac, l2_dst, l2p); + memset(ip6, 0, sizeof(*ip6)); + ip6->ver = 0x60; // Version 6, traffic class 0 + ip6->plen = mg_htons((uint16_t) plen); + ip6->next = next; + ip6->hops = 255; // NDP requires max + ip6->src[0] = *ip_src++; + ip6->src[1] = *ip_src; + ip6->dst[0] = *ip_dst++; + ip6->dst[1] = *ip_dst; + return ip6; +} + +static void tx_icmp6(struct mg_tcpip_if *ifp, uint8_t *l2_dst, uint64_t *ip_src, + uint64_t *ip_dst, uint8_t type, uint8_t code, + const void *buf, size_t len) { + struct ip6 *ip6; + struct icmp6 *icmp6; + ip6 = tx_ip6(ifp, l2_dst, 58, ip_src, ip_dst, sizeof(*icmp6) + len); + icmp6 = (struct icmp6 *) (ip6 + 1); + memset(icmp6, 0, sizeof(*icmp6)); // Set csum to 0 + icmp6->type = type; + icmp6->code = code; + memcpy(icmp6 + 1, buf, len); // Copy payload + icmp6->csum = 0; // RFC-4443 2.3, RFC-8200 8.1 + icmp6->csum = p6csum(ip6, icmp6, sizeof(*icmp6) + len); + driver_output(ifp, + mg_l2_trailer(ifp, sizeof(*ip6) + sizeof(*icmp6) + len, + (uint8_t *) (ip6 + 1) + sizeof(*icmp6) + len)); +} + +// Neighbor Discovery Protocol, RFC-4861 +// Neighbor Advertisement, 4.4 +static void tx_ndp_na(struct mg_tcpip_if *ifp, uint8_t *l2_dst, + uint64_t *ip_src, uint64_t *ip_dst, bool solicited, + uint8_t *l2) { + uint8_t data[20 + 16]; // NOTE(): optional len upto 2 hw addr + memset(data, 0, sizeof(data)); + data[0] = solicited ? 0x60 : 0x20; // O + S + memcpy(data + 4, ip_src, 16); // Target address + data[20] = 2; // 4.6.1, target hwaddr + data[21] = mg_l2_ip6put(ifp->l2type, l2, data + 22); // option length / 8 + tx_icmp6(ifp, l2_dst, ip_src, ip_dst, 136, 0, data, + 20 + (size_t) (8 * data[21])); +} + +static void onstate6change(struct mg_tcpip_if *ifp); + +static void rx_ndp_na(struct mg_tcpip_if *ifp, struct pkt *pkt) { + struct ndp_na *na = (struct ndp_na *) pkt->pay.buf; + uint8_t *opts = (uint8_t *) (na + 1), *endp = opts + pkt->pay.len - sizeof(*na); + if (pkt->pay.len < (sizeof(*na) + 2)) return; // first 2 bytes in opts + if ((na->res[0] & 0x40) == 0) return; // not "solicited" + if (*opts++ != 2) return; // no target hwaddr, must have + MG_VERBOSE(("NDP NA resp from %M", mg_print_ip6, (char *) &na->addr)); + if (MG_IP6MATCH(na->addr, ifp->gw6)) { + // Got response for the GW NS request. Set ifp->gw6mac and IP6 -> READY + uint8_t len = *opts++; // check valid hwaddr and get it + if ((opts + 8 * len - 2) > endp) return; // truncated + if (!mg_l2_ip6get(ifp->l2type, ifp->gw6mac, opts, len)) return; + ifp->gw6_ready = true; + if (ifp->state6 == MG_TCPIP_STATE_IP) { + ifp->state6 = MG_TCPIP_STATE_READY; + onstate6change(ifp); + } + } else { + struct mg_connection *c = getpeer(ifp->mgr, pkt, false); + if (c != NULL && c->is_arplooking) { + struct connstate *s = (struct connstate *) (c + 1); + uint8_t len = *opts++; // check valid hwaddr and get it + if ((opts + 8 * len - 2) > endp) return; // truncated + if (!mg_l2_ip6get(ifp->l2type, s->mac, opts, len)) return; + MG_DEBUG(("%lu NDP resolved %M -> %M", c->id, mg_print_ip6, + &c->rem.addr.ip6, mg_print_l2addr, ifp->l2type, s->mac)); + c->is_arplooking = 0; + l2addr_resolved(c); + } + } +} + +// Neighbor Solicitation, 4.3 +static void rx_ndp_ns(struct mg_tcpip_if *ifp, struct pkt *pkt) { + struct ndp_na *ns = (struct ndp_na *) pkt->pay.buf; // struct ndp_ns = ndp_na + uint64_t target[2]; + if (pkt->pay.len < (sizeof(*ns) + 2)) return; // first 2 bytes in opts + memcpy(target, ns->addr, sizeof(target)); + if (MG_IP6MATCH(target, ifp->ip6ll) || MG_IP6MATCH(target, ifp->ip6)) { + uint64_t req[2]; // requester address + uint8_t l2[sizeof(struct mg_l2addr)]; + uint8_t len, *opts = (uint8_t *) (ns + 1), *endp = opts + pkt->pay.len - sizeof(*ns); + if (*opts++ != 1) return; // no requester hwaddr (source) + len = *opts++; // check valid hwaddr and get it + if ((opts + 8 * len - 2) > endp) return; // truncated + if (!mg_l2_ip6get(ifp->l2type, l2, opts, len)) return; + req[0] = pkt->ip6->src[0], req[1] = pkt->ip6->src[1]; // align to 64-bit + tx_ndp_na(ifp, l2, target, req, true, ifp->mac); + } +} + +// - use solicited node multicast to resolve a l2 address (l2_addr = NULL) +// - use unicast to verify presence (l2_addr = neighbor l2 address) +static void tx_ndp_ns(struct mg_tcpip_if *ifp, uint64_t *ip_dst, + uint8_t *l2_addr) { + uint8_t payload[4 + 16 + 16]; // NOTE(): 16 --> optional len upto 2 hw addr + uint64_t ip_unspec[2] = {0, 0}; + size_t payload_len = 20; + bool mcast = (l2_addr == NULL); + uint64_t ip_mcast[2] = {0, 0}; + uint8_t *l2 = l2_addr; + + if (ifp->l2type == MG_TCPIP_L2_PPP || ifp->l2type == MG_TCPIP_L2_PPPoE) { + MG_DEBUG(("SKIP NS for %M", mg_print_ip6, ip_dst)); + return; + } + memset(payload, 0, sizeof(payload)); + memcpy(payload + 4, ip_dst, 16); + if (mcast) { + struct mg_addr ipd; + ip6sn(ip_dst, ip_mcast); + ipd.addr.ip6[0] = ip_mcast[0], ipd.addr.ip6[1] = ip_mcast[1], + ipd.is_ip6 = true; + l2 = mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_MCAST6, &ipd); + } + payload_len = 20; + // TODO(robertc2000): using only link-local IP addr for now + // We might consider to add an option to use either link-local or global IP + if (!MG_IP6MATCH(ifp->ip6ll, ip_unspec)) { + payload[20] = 1; // 4.6.1, source hwaddr; option length in 8-byte units + payload[21] = mg_l2_ip6put(ifp->l2type, ifp->mac, payload + 22); + payload_len += 8 * payload[21]; + } + tx_icmp6(ifp, l2, ifp->ip6ll, mcast ? ip_mcast : ip_dst, 135, 0, payload, + payload_len); +} + +// Router Solicitation, 4.1 +static void tx_ndp_rs(struct mg_tcpip_if *ifp) { + uint8_t payload[4 + 16]; // reserved + optional len upto 2 hw addr NOTE() + size_t payload_len = 4; + uint64_t ip_unspec[2] = {0, 0}; + + memset(payload, 0, sizeof(payload)); + + if (!MG_IP6MATCH(ifp->ip6ll, ip_unspec)) { + payload[4] = 1; // 4.6.1, source hwaddr; option length in 8-byte units + payload[5] = mg_l2_ip6put(ifp->l2type, ifp->mac, payload + 6); + payload_len += 8 * payload[5]; + } + tx_icmp6(ifp, + mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_MCAST6, + (struct mg_addr *) &ip6_allrouters), + ifp->ip6ll, (uint64_t *) ip6_allrouters.addr.ip6, 133, 0, payload, + payload_len); + MG_DEBUG(("NDP Router Solicitation sent")); +} + +static void fill_prefix(uint8_t *dst, uint8_t *src, uint8_t len) { + uint8_t full = len / 8; + uint8_t rem = len % 8; + if (full > 0) memcpy(dst, src, full); + if (rem > 0) { + uint8_t mask = (uint8_t) (0xFF << (8 - rem)); + dst[full] |= src[full] & mask; // mg_l2_genip6() zeroes dst + } +} + +static bool match_prefix(uint8_t *newp, uint8_t *curp, uint8_t len) { + uint8_t full = len / 8; + uint8_t rem = len % 8; + if (full > 0 && memcmp(curp, newp, full) != 0) return false; + if (rem > 0) { + uint8_t mask = (uint8_t) (0xFF << (8 - rem)); + if (curp[full] != (newp[full] & mask)) return false; + } + return true; +} + +static bool fill_global(struct mg_tcpip_if *ifp, uint8_t *prefix, + uint8_t prefix_len) { + if (!mg_l2_genip6(ifp->l2type, ifp->ip6, prefix_len, ifp->mac)) return false; + fill_prefix((uint8_t *) ifp->ip6, prefix, prefix_len); + fill_prefix(ifp->prefix, prefix, prefix_len); + ifp->prefix_len = prefix_len; + return true; +} + +// Router Advertisement, 4.2 +static void rx_ndp_ra(struct mg_tcpip_if *ifp, struct pkt *pkt) { + struct ndp_ra *ra = (struct ndp_ra *) pkt->pay.buf; + uint8_t *opts = (uint8_t *) (ra + 1); + size_t opt_left = pkt->pay.len - sizeof(*ra); + bool gotl2addr = false, gotprefix = false, changed = false; + uint8_t l2[sizeof(struct mg_l2addr)]; + uint32_t mtu = 0; + uint8_t *prefix, prefix_len; + + if (pkt->pay.len < sizeof(*ra)) return; + if (ifp->state6 == MG_TCPIP_STATE_UP) { + MG_DEBUG(("Received NDP RA")); // fill gw6 address + // parse options + while (opt_left >= 2) { + uint8_t type = opts[0], len = opts[1]; + size_t length = (size_t) len * 8; + if (length == 0 || length > opt_left) break; // malformed + if (type == 1 && length >= 8) { + // Received router's L2 address + if (!mg_l2_ip6get(ifp->l2type, l2, opts + 2, len)) break; + gotl2addr = true; + } else if (type == 5 && length >= 8) { + // process MTU if available, ignore if it smells + mtu = MG_LOAD_BE32(opts + 4); + if (mtu < 1280 || mtu > ifp->l2mtu) mtu = 0; // RFC-8200, minimum MTU + } else if (type == 3 && length >= 32) { + // process prefix, 4.6.2 + uint8_t pfx_flags = opts[3]; // L=0x80, A=0x40 + uint32_t valid = MG_LOAD_BE32(opts + 4); + uint32_t pref_lifetime = MG_LOAD_BE32(opts + 8); + prefix_len = opts[2]; + prefix = opts + 16; + + // TODO (robertc2000): handle prefix options if necessary + (void) pfx_flags; + (void) valid; + (void) pref_lifetime; + + gotprefix = true; + if (prefix_len != ifp->prefix_len || !match_prefix(prefix, ifp->prefix, ifp->prefix_len)) + changed = true; + } + opts += length; + opt_left -= length; + } + + // fill prefix and global + if (gotprefix && !fill_global(ifp, prefix, prefix_len)) return; + if (changed) ifp->is_ip6_changed = true; + ifp->gw6[0] = pkt->ip6->src[0], ifp->gw6[1] = pkt->ip6->src[1]; + if (gotl2addr) memcpy(ifp->gw6mac, l2, sizeof(ifp->gw6mac)); + if (gotl2addr || ifp->l2type == MG_TCPIP_L2_PPP || ifp->l2type == MG_TCPIP_L2_PPPoE) { + ifp->state6 = MG_TCPIP_STATE_READY; + ifp->gw6_ready = true; + } + if (mtu != 0 && ifp->mtu != mtu) { + MG_ERROR( + ("got an MTU: %u, that differs from the configured one. " + "All devices in an IPv6 network should have the same MTU, " + "using the router's instead...", + mtu)); + ifp->mtu = (uint16_t) mtu; + } + if (ifp->state6 != MG_TCPIP_STATE_READY) { + tx_ndp_ns(ifp, ifp->gw6, NULL); // unsolicited GW hwaddr resolution + ifp->state6 = MG_TCPIP_STATE_IP; + } + onstate6change(ifp); + } +} + +static void rx_icmp6(struct mg_tcpip_if *ifp, struct pkt *pkt) { + if (!icmp6csum_ok(pkt->ip6, pkt->icmp6)) return; + switch (pkt->icmp6->type) { + case 128: { // Echo Request, RFC-4443 4.1 + uint64_t target[2]; + target[0] = pkt->ip6->dst[0], target[1] = pkt->ip6->dst[1]; + if (MG_IP6MATCH(target, ifp->ip6ll) || MG_IP6MATCH(target, ifp->ip6)) { + size_t l2_max_overhead = ifp->framesize - ifp->l2mtu; + size_t hlen = sizeof(struct ip6) + sizeof(struct icmp6); + size_t room = ifp->tx.len - hlen - l2_max_overhead, plen = pkt->pay.len; + struct mg_addr ips; + uint8_t *l2addr; + + ips.addr.ip6[0] = pkt->ip6->src[0], ips.addr.ip6[1] = pkt->ip6->src[1]; + ips.is_ip6 = true; + if ((l2addr = get_return_l2addr(ifp, &ips, false, pkt)) == NULL) + return; // safety net for lousy networks + if (plen > room) plen = room; // Copy (truncated) RX payload to TX + // Echo Reply, 4.2 + tx_icmp6(ifp, l2addr, target, ips.addr.ip6, 129, 0, pkt->pay.buf, plen); + } + } break; + case 134: // Router Advertisement + rx_ndp_ra(ifp, pkt); + break; + case 135: // Neighbor Solicitation + rx_ndp_ns(ifp, pkt); + break; + case 136: // Neighbor Advertisement + rx_ndp_na(ifp, pkt); + break; + default: + if (mg_log_level >= MG_LL_VERBOSE) + mg_hexdump(pkt->icmp6, pkt->pay.len > 16 ? 16 : pkt->pay.len); + break; + } +} + +static void onstate6change(struct mg_tcpip_if *ifp) { + if (ifp->state6 == MG_TCPIP_STATE_READY) { + MG_INFO(("READY, IP: %M", mg_print_ip6, &ifp->ip6)); + MG_INFO((" GW: %M", mg_print_ip6, &ifp->gw6)); + if (ifp->l2type == MG_TCPIP_L2_ETH) // TODO(): print other l2 + MG_INFO((" MAC: %M", mg_print_mac, &ifp->mac)); + if (ifp->l2type == MG_TCPIP_L2_ETH) { // gratuitous NA annc + tx_ndp_na(ifp, // RFC-4861 7.2.6 + mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_MCAST6, + (struct mg_addr *) &ip6_allnodes), + ifp->ip6ll, (uint64_t *) ip6_allnodes.addr.ip6, false, + ifp->mac); + if (ifp->ip6[0] != 0 && ifp->ip6[1] != 0) + tx_ndp_na(ifp, // RFC-9131 4.1 + mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_MCAST6, + (struct mg_addr *) &ip6_allrouters), + ifp->ip6, (uint64_t *) ip6_allrouters.addr.ip6, false, + ifp->mac); + } + if (ifp->is_ip6_changed) { + struct mg_connection *c; + for (c = ifp->mgr->conns; c != NULL; c = c->next) { + if (!c->is_listening && !c->is_udp) c->is_closing = 1; + } + ifp->is_ip6_changed = false; + } + } else if (ifp->state6 == MG_TCPIP_STATE_IP) { + if ((ifp->gw6[0] != 0 || ifp->gw6[1] != 0) && (ifp->l2type != MG_TCPIP_L2_PPP && ifp->l2type != MG_TCPIP_L2_PPPoE)) + tx_ndp_ns(ifp, ifp->gw6, NULL); // unsolicited GW hwaddr resolution + } else if (ifp->state6 == MG_TCPIP_STATE_UP) { + MG_INFO(("IP: %M", mg_print_ip6, &ifp->ip6ll)); + } + if (ifp->state6 > MG_TCPIP_STATE_UP) + mg_tcpip_call(ifp, MG_TCPIP_EV_STATE6_CHANGE, &ifp->state6); +} +#endif + +static uint8_t *tcpip_mapip(struct mg_tcpip_if *ifp, struct mg_addr *ip) { +#if MG_ENABLE_IPV6 + if (ip->is_ip6) { + if (MG_IP6MATCH(ip->addr.ip6, ip6_allnodes.addr.ip6)) // local broadcast + return mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_MCAST6, + (struct mg_addr *) &ip6_allnodes); + if (*ip->addr.ip == 0xFF) // multicast + return mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_MCAST6, ip); + } else +#endif + { // global/local broadcast + if (ip->addr.ip4 == 0xffffffff || ip->addr.ip4 == (ifp->ip | ~ifp->mask)) + return mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_BCAST, NULL); + if ((*ip->addr.ip & 0xE0) == 0xE0) // 224 ~ 239 = E0 ~ EF, multicast + return mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_MCAST, ip); + } + return NULL; +} + +static uint8_t *get_return_l2addr(struct mg_tcpip_if *ifp, struct mg_addr *rem, + bool is_udp, struct pkt *pkt) { + uint8_t *l2addr; + if (is_udp && (l2addr = tcpip_mapip(ifp, rem)) != NULL) + return l2addr; // broadcast or multicast +#if MG_ENABLE_IPV6 + if (rem->is_ip6) { + if (rem->addr.ip6[0] == ifp->ip6ll[0] || + match_prefix((uint8_t *) rem->addr.ip6, ifp->prefix, ifp->prefix_len)) + return mg_l2_getaddr(ifp, pkt->l2); // same LAN, get from frame + if (ifp->gw6_ready) // use the router + return ifp->gw6mac; // ignore source address in frame + } else +#endif + { + if (ifp->ip != 0 && ((rem->addr.ip4 & ifp->mask) == (ifp->ip & ifp->mask))) + return mg_l2_getaddr(ifp, pkt->l2); // same LAN, get from frame + if (ifp->gw_ready) // use the router + return ifp->gwmac; // ignore source address in frame + } + MG_ERROR(("%M %s: No way back, can't respond", mg_print_ip_port, rem, + is_udp ? "UDP" : "TCP")); + return NULL; +} + +static bool rx_udp(struct mg_tcpip_if *ifp, struct pkt *pkt) { + struct mg_connection *c = getpeer(ifp->mgr, pkt, true); + struct connstate *s; + uint8_t *l2addr; + if (c == NULL) return false; // No UDP listener on this port + s = (struct connstate *) (c + 1); + c->rem.port = pkt->udp->sport; +#if MG_ENABLE_IPV6 + if (c->loc.is_ip6) { // matching of v4/v6 to dest is done bt getpeer() + if (!udp6csum_ok(pkt->ip6, pkt->udp)) return false; + c->rem.addr.ip6[0] = pkt->ip6->src[0], + c->rem.addr.ip6[1] = pkt->ip6->src[1], c->rem.is_ip6 = true; + } else +#endif + { + if (!udpcsum_ok(pkt->ip, pkt->udp)) return false; + c->rem.addr.ip4 = pkt->ip->src; + } + if ((l2addr = get_return_l2addr(ifp, &c->rem, true, pkt)) == NULL) + return false; // safety net for lousy networks + memcpy(s->mac, l2addr, 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); + } + return true; +} + +static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *l2_dst, + struct mg_addr *ip_src, struct mg_addr *ip_dst, + uint8_t flags, uint32_t seq, uint32_t ack, const void *buf, + size_t len) { + uint8_t *l3p; + struct ip *ip = NULL; + struct tcp *tcp; + uint16_t opts[4 / 2]; + size_t hlen = sizeof(*tcp); +#if MG_ENABLE_IPV6 + struct ip6 *ip6 = NULL; +#endif + + // Handle any options first, here, to determine header size + if (flags & TH_SYN) { // Send MSS + uint16_t mss; +#if MG_ENABLE_IPV6 // RFC-9293 3.7.1; RFC-6691 2 + mss = (uint16_t) (ifp->mtu - 60); +#else + mss = (uint16_t) (ifp->mtu - 40); +#endif + opts[0] = mg_htons(0x0204); // RFC-9293 3.2 + opts[1] = mg_htons(mss); + hlen += sizeof(opts); // always whole number of 32-bit words + } + +#if MG_ENABLE_IPV6 + if (ip_dst->is_ip6) { + ip6 = + tx_ip6(ifp, l2_dst, 6, ip_src->addr.ip6, ip_dst->addr.ip6, hlen + len); + tcp = (struct tcp *) (ip6 + 1); + l3p = (uint8_t *) ip6; + } else +#endif + { + ip = tx_ip(ifp, l2_dst, 6, ip_src->addr.ip4, ip_dst->addr.ip4, hlen + len); + tcp = (struct tcp *) (ip + 1); + l3p = (uint8_t *) ip; + } + memset(tcp, 0, sizeof(*tcp)); + memmove(tcp + 1, opts, hlen - sizeof(*tcp)); // copy opts if any + if (buf != NULL && len) memmove((uint8_t *) tcp + hlen, buf, len); + tcp->sport = ip_src->port; + tcp->dport = ip_dst->port; + tcp->seq = seq; + tcp->ack = ack; + tcp->flags = flags; + tcp->win = mg_htons(MG_TCPIP_WIN); + tcp->off = (uint8_t) (hlen / 4 << 4); +#if MG_ENABLE_IPV6 + if (ip_dst->is_ip6) { + tcp->csum = p6csum(ip6, tcp, hlen + len); + } else +#endif + { + tcp->csum = pcsum(ip, tcp, hlen + len); + } + MG_VERBOSE(("TCP %M -> %M fl %x len %u", mg_print_ip_port, ip_src, + mg_print_ip_port, ip_dst, tcp->flags, len)); + return driver_output( + ifp, mg_l2_trailer(ifp, PDIFF(l3p, (uint8_t *) tcp + hlen + len), + (uint8_t *) tcp + hlen + len)); +} + +static size_t tx_tcp_ctrlresp(struct mg_tcpip_if *ifp, struct pkt *pkt, + uint8_t flags, uint32_t seqno) { + uint32_t ackno = mg_htonl(mg_ntohl(pkt->tcp->seq) + (uint32_t) pkt->pay.len + + ((pkt->tcp->flags & (TH_SYN | TH_FIN)) ? 1 : 0)); + struct mg_addr ips, ipd; + uint8_t *l2addr; + memset(&ips, 0, sizeof(ips)); + memset(&ipd, 0, sizeof(ipd)); + if (pkt->ip != NULL) { + ips.addr.ip4 = pkt->ip->dst; + ipd.addr.ip4 = pkt->ip->src; + } else { + ips.addr.ip6[0] = pkt->ip6->dst[0], ips.addr.ip6[1] = pkt->ip6->dst[1]; + ipd.addr.ip6[0] = pkt->ip6->src[0], ipd.addr.ip6[1] = pkt->ip6->src[1]; + ips.is_ip6 = true; + ipd.is_ip6 = true; + } + ips.port = pkt->tcp->dport; + ipd.port = pkt->tcp->sport; + if ((l2addr = get_return_l2addr(ifp, &ipd, false, pkt)) == NULL) + return 0; // safety net for lousy networks + return tx_tcp(ifp, l2addr, &ips, &ipd, flags, seqno, ackno, NULL, 0); +} + +static size_t tx_tcp_rst(struct mg_tcpip_if *ifp, struct pkt *pkt, bool toack) { + return tx_tcp_ctrlresp(ifp, pkt, toack ? TH_RST : (TH_RST | TH_ACK), + toack ? pkt->tcp->ack : 0); +} + +static struct mg_connection *accept_conn(struct mg_connection *lsn, + struct pkt *pkt, uint16_t mss) { + struct connstate *s; + uint8_t *l2addr; + struct mg_connection *c = mg_alloc_conn(lsn->mgr); + if (c == NULL) { + MG_ERROR(("OOM")); + return NULL; + } + s = (struct connstate *) (c + 1); + s->dmss = mss; // from options in client SYN + s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq); + s->win = mg_ntohs(pkt->tcp->win), s->maxseq = (uint32_t)(s->seq + s->win); +#if MG_ENABLE_IPV6 + if (lsn->loc.is_ip6) { + c->rem.addr.ip6[0] = pkt->ip6->src[0], + c->rem.addr.ip6[1] = pkt->ip6->src[1], c->rem.is_ip6 = true; + c->loc.addr.ip6[0] = c->mgr->ifp->ip6[0], + c->loc.addr.ip6[1] = c->mgr->ifp->ip6[1], c->loc.is_ip6 = true; + // TODO(): compare lsn to link-local, or rem as link-local: use ll instead + } else +#endif + { + c->rem.addr.ip4 = pkt->ip->src; + c->loc.addr.ip4 = c->mgr->ifp->ip; + } + c->rem.port = pkt->tcp->sport; + c->loc.port = lsn->loc.port; + if ((l2addr = get_return_l2addr(lsn->mgr->ifp, &c->rem, false, pkt)) == + NULL) { + free(c); // safety net for lousy networks, not actually needed + return NULL; // as path has already been checked at SYN (sending SYN+ACK) + } + memcpy(s->mac, l2addr, sizeof(s->mac)); + settmout(c, MIP_TTYPE_KEEPALIVE); + 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->pfn_data = lsn->pfn_data; + c->fn = lsn->fn; + c->fn_data = lsn->fn_data; + c->is_tls = lsn->is_tls; + mg_call(c, MG_EV_OPEN, NULL); + mg_call(c, MG_EV_ACCEPT, NULL); + if (!c->is_tls_hs) c->is_tls = 0; // user did not call mg_tls_init() + return c; +} + +static size_t trim_len(struct mg_connection *c, size_t len) { + struct mg_tcpip_if *ifp = c->mgr->ifp; + size_t l2_max_overhead = ifp->framesize - ifp->l2mtu; + size_t ip_max_h_len = c->rem.is_ip6 ? 40 : 24; // we don't send options + size_t tcp_max_h_len = 60 /* RFC-9293 3.7.1; RFC-6691 2 */, udp_h_len = 8; + size_t max_headers_len = + ip_max_h_len + (c->is_udp ? udp_h_len : tcp_max_h_len); + size_t min_mtu = c->rem.is_ip6 ? 1280 /* RFC-8200, IPv6 minimum */ + : c->is_udp ? 68 /* RFC-791, IP minimum */ + : max_headers_len /* fit full TCP header */; + // NOTE(): We are effectively reducing transmitted TCP segment length by 20, + // accounting for possible options; though we currently don't send options + // except for SYN. + + // If the frame exceeds the available buffer, trim the length. + if (len + max_headers_len + l2_max_overhead > ifp->tx.len) + len = ifp->tx.len - max_headers_len - l2_max_overhead; + // Ensure the MTU isn't lower than the minimum allowed value + if (ifp->mtu < min_mtu) { + MG_ERROR(("MTU is lower than minimum, raising 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 > ifp->mtu) { + len = ifp->mtu - max_headers_len; + if (c->is_udp) MG_ERROR(("UDP datagram exceeds MTU. Truncating it.")); + } + + return len; +} + +static bool udp_send(struct mg_connection *c, const void *buf, size_t len) { + struct mg_tcpip_if *ifp = c->mgr->ifp; + struct connstate *s = (struct connstate *) (c + 1); + struct mg_addr ips; + memset(&ips, 0, sizeof(ips)); +#if MG_ENABLE_IPV6 + if (c->loc.is_ip6) { + ips.addr.ip6[0] = ifp->ip6[0], ips.addr.ip6[1] = ifp->ip6[1], + ips.is_ip6 = true; + // TODO(): detect link-local (c->rem) and use it + } else +#endif + { + ips.addr.ip4 = ifp->ip; + } + ips.port = c->loc.port; + return tx_udp(ifp, s->mac, &ips, &c->rem, buf, len); +} + +long mg_io_send(struct mg_connection *c, const void *buf, size_t len) { + struct connstate *s = (struct connstate *) (c + 1); + len = trim_len(c, len); + if (c->is_udp) { + if (!udp_send(c, buf, len)) return MG_IO_WAIT; + } else { // TCP, cap to peer's MSS and check window + struct mg_tcpip_if *ifp = c->mgr->ifp; + size_t sent; + uint32_t room = s->maxseq - s->seq; + if (room == 0) return MG_IO_WAIT; + if (len > s->dmss) len = s->dmss; // RFC-6691: reduce if sending opts + if ((uint32_t) len > room) len = room; + sent = tx_tcp(ifp, s->mac, &c->loc, &c->rem, TH_PUSH | TH_ACK, + 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) { + size_t avail = mg_tls_pending(c); // will change after mg_tls_recv() + size_t min = avail > MG_MAX_RECV_SIZE ? MG_MAX_RECV_SIZE : avail; + struct mg_iobuf *io = &c->recv; // allocated on first avail > 0 + if (io->size - io->len < min && !mg_iobuf_resize(io, io->len + min)) { + mg_error(c, "oom"); + } else { + // Decrypt data directly into c->recv. If io->buf = NULL or + // io->len = io->size (no room), there can be outstanding data that can't be + // moved (mg_tls_pending() > 0 after mg_tls_recv() returns). So mg_tls_recv() + // takes care of returning 0 in this case (commented as "MIP") + long n = mg_tls_recv(c, io->buf != NULL ? &io->buf[io->len] : io->buf, + io->size - io->len); + if (n == MG_IO_ERR) { + mg_error(c, "TLS recv error"); + } else if (n > 0) { + // Decrypted successfully - trigger MG_EV_READ + io->len += (size_t) n; + mg_call(c, MG_EV_READ, &n); + } // else n < 0: outstanding data to be moved to c->recv + } +} + +static void handle_ack(struct connstate *s, uint32_t ackno, uint16_t win) { + if (ackno < (s->seq - s->win) || ackno > s->seq) return; + s->maxseq = (uint32_t)(ackno + win); + s->win = win; +} + +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); + if (pkt->tcp->flags & TH_FIN) { + uint8_t flags = TH_ACK; + if (mg_ntohl(pkt->tcp->seq) != s->ack) { + MG_VERBOSE(("ignoring FIN, %x != %x", mg_ntohl(pkt->tcp->seq), s->ack)); + tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_ACK, mg_htonl(s->seq), + mg_htonl(s->ack), "", 0); + return; + } + // 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 + s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len + 1); + s->fin_rcvd = true; + 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 + } + s->twclosure = true; + } else { + // Peer closed first: send ACK only, enter CLOSE_WAIT. + // The connection loop will call init_closure after pending send data + // is flushed, then send our FIN. + c->is_draining = 1; + } + tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, flags, mg_htonl(s->seq), + mg_htonl(s->ack), "", 0); + if (pkt->pay.len == 0) return; // if no data, we're done + } else if (pkt->pay.len <= 1 && mg_ntohl(pkt->tcp->seq) == s->ack - 1) { + // Keep-Alive (RFC-9293 3.8.4, allow erroneous implementations) + MG_VERBOSE(("%lu keepalive ACK", c->id)); + tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_ACK, mg_htonl(s->seq), + mg_htonl(s->ack), NULL, 0); + return; // no data to process + } else if (pkt->pay.len == 0) { // this is an ACK + if (pkt->tcp->flags & TH_ACK) + handle_ack(s, mg_ntohl(pkt->tcp->ack), mg_ntohs(pkt->tcp->win)); + if (s->fin_rcvd && s->ttype == MIP_TTYPE_FIN) s->twclosure = true; + return; // no data to process + } 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(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_ACK, mg_htonl(s->seq), + mg_htonl(s->ack), "", 0); + } + return; // drop it + } else if (io->size - io->len < pkt->pay.len && + !mg_iobuf_resize(io, io->len + pkt->pay.len)) { + mg_error(c, "oom"); + return; // drop it + } + if (pkt->tcp->flags & TH_ACK) + handle_ack(s, mg_ntohl(pkt->tcp->ack), mg_ntohs(pkt->tcp->win)); + // 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 > MG_TCPIP_WIN / 2 && s->acked != s->ack) { + // Send ACK immediately + MG_VERBOSE(("%lu imm ACK %lu", c->id, s->acked)); + tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_ACK, 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) : handle_tls_recv(c); + } else { + // Plain text connection, data is already in c->recv, trigger MG_EV_READ + mg_call(c, MG_EV_READ, &pkt->pay.len); + } +} + +// TCP backlog +struct mg_backlog { + uint16_t port, mss; // use port=0 for available entries + uint8_t age; +}; + +static int backlog_insert(struct mg_connection *c, uint16_t port, + uint16_t mss) { + struct mg_backlog *p = (struct mg_backlog *) c->data; + size_t i; + for (i = 0; i < sizeof(c->data) / sizeof(*p); i++) { + if (p[i].port != 0) continue; + p[i].age = 2; // remove after two calls, average 1.5 call rate + p[i].port = port, p[i].mss = mss; + return (int) i; + } + return -1; +} + +static struct mg_backlog *backlog_retrieve(struct mg_connection *c, + uint16_t key, uint16_t port) { + struct mg_backlog *p = (struct mg_backlog *) c->data; + if (key >= sizeof(c->data) / sizeof(*p)) return NULL; + if (p[key].port != port) return NULL; + p += key; + return p; +} + +static void backlog_remove(struct mg_connection *c, uint16_t key) { + struct mg_backlog *p = (struct mg_backlog *) c->data; + p[key].port = 0; +} + +static void backlog_maintain(struct mg_connection *c) { + struct mg_backlog *p = (struct mg_backlog *) c->data; + size_t i; // dec age and remove those where it reaches 0 + for (i = 0; i < sizeof(c->data) / sizeof(*p); i++) { + if (p[i].port == 0) continue; + if (p[i].age != 0) --p[i].age; + if (p[i].age == 0) p[i].port = 0; + } +} + +static void backlog_poll(struct mg_mgr *mgr) { + struct mg_connection *c = NULL; + for (c = mgr->conns; c != NULL; c = c->next) { + if (!c->is_udp && c->is_listening) backlog_maintain(c); + } +} + +// process options (MSS) +static bool handle_opt(struct connstate *s, struct tcp *tcp, bool ip6) { + uint8_t *opts = (uint8_t *) (tcp + 1); + int len = 4 * ((int) (tcp->off >> 4) - ((int) sizeof(*tcp) / 4)); + s->dmss = ip6 ? 1220 : 536; // assume default, RFC-9293 3.7.1 + while (len > 0) { // RFC-9293 3.1 3.2 + uint8_t kind = opts[0], optlen = 1; + if (kind != 1) { // No-Operation + if (kind == 0) break; // End of Option List + if (len < 2 || opts[1] == 0) return false; // Malformed options + optlen = opts[1]; + if (kind == 2 && optlen == 4) // set received MSS + s->dmss = (uint16_t) (((uint16_t) opts[2] << 8) + opts[3]); + } + MG_VERBOSE(("kind: %u, optlen: %u, len: %d\n", kind, optlen, len)); + opts += optlen; + len -= optlen; + } + return true; +} + +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 MG_ENABLE_IPV6 // matching of v4/v6 to dest is done by getpeer() + if (pkt->ip6 != NULL && !tcp6csum_ok(pkt->ip6, pkt->tcp)) return; +#endif + if (pkt->ip != NULL && !tcpcsum_ok(pkt->ip, pkt->tcp)) return; + pkt->tcp->flags &= TH_STDFLAGS; // tolerate creative usage (ECN, ?) + // Order is VERY important; RFC-9293 3.5.2 + // - check clients (Group 1) and established connections (Group 3) + if (c != NULL && c->is_connecting && pkt->tcp->flags == (TH_SYN | TH_ACK)) { + // client got a server connection accept + if (!handle_opt(s, pkt->tcp, pkt->ip6 != NULL)) + return; // process options (MSS) + s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq) + 1; + s->win = mg_ntohs(pkt->tcp->win), s->maxseq = (uint32_t)(s->seq + s->win); + tx_tcp_ctrlresp(ifp, pkt, TH_ACK, pkt->tcp->ack); + c->is_connecting = 0; // Client connected + settmout(c, MIP_TTYPE_KEEPALIVE); + mg_call(c, MG_EV_CONNECT, NULL); // Let user know + if (c->is_tls_hs) mg_tls_handshake(c); + if (!c->is_tls_hs) c->is_tls = 0; // user did not call mg_tls_init() + } else if (c != NULL && c->is_connecting && pkt->tcp->flags != TH_ACK) { + mg_error(c, "connection refused"); + } else if (c != NULL && pkt->tcp->flags & TH_RST) { + uint32_t seqno = mg_ntohl(pkt->tcp->seq); + if (seqno >= s->ack && seqno < (s->ack + MG_TCPIP_WIN)) // RFC-9293 3.5.3 + mg_error(c, "peer RST"); // RFC-1122 4.2.2.13 + } else if (c != NULL) { + // process segment + 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 + // - we don't listen on that port; RFC-9293 3.5.2 Group 1 + // - check listening connections; RFC-9293 3.5.2 Group 2 + if ((c = getpeer(ifp->mgr, pkt, true)) == NULL) { + // not listening on that port + if (!(pkt->tcp->flags & TH_RST)) { + tx_tcp_rst(ifp, pkt, pkt->tcp->flags & TH_ACK); + } // else silently discard + } else if (pkt->tcp->flags == TH_SYN) { + // listener receives a connection request + struct connstate cs; // At this point, s = NULL, there is no connection + int key; + uint32_t isn; + if (pkt->tcp->sport != 0) { + if (!handle_opt(&cs, pkt->tcp, pkt->ip6 != NULL)) + return; // process options (MSS) + key = backlog_insert(c, pkt->tcp->sport, + cs.dmss); // backlog options (MSS) + if (key < 0) return; // no room in backlog, discard SYN, client retries + // Use peer's src port and bl key as ISN, to later identify the + // handshake + isn = (mg_htonl(((uint32_t) key << 16) | mg_ntohs(pkt->tcp->sport))); + if (tx_tcp_ctrlresp(ifp, pkt, TH_SYN | TH_ACK, isn) == 0) + backlog_remove(c, (uint16_t) key); // safety net for lousy networks + } // what should we do when port=0 ? Linux takes port 0 as any other + // port + } else if (pkt->tcp->flags == TH_ACK) { + // listener receives an ACK + struct mg_backlog *b = NULL; + if ((uint16_t) (mg_htonl(pkt->tcp->ack) - 1) == + mg_htons(pkt->tcp->sport)) { + uint16_t key = (uint16_t) ((mg_htonl(pkt->tcp->ack) - 1) >> 16); + b = backlog_retrieve(c, key, pkt->tcp->sport); + if (b != NULL) { // ACK is a response to a SYN+ACK + accept_conn(c, pkt, b->mss); // pass options + backlog_remove(c, key); + } // else not an actual match, reset + } + if (b == NULL) tx_tcp_rst(ifp, pkt, true); + } else if (pkt->tcp->flags & TH_RST) { + // silently discard + } else if (pkt->tcp->flags & TH_ACK) { // ACK + something else != RST + tx_tcp_rst(ifp, pkt, true); + } else if (pkt->tcp->flags & TH_SYN) { // SYN + something else != ACK + tx_tcp_rst(ifp, pkt, false); + } // else silently discard +} + +static void rx_ip(struct mg_tcpip_if *ifp, struct pkt *pkt) { + uint8_t ihl; + uint16_t frag, len; + if (pkt->pay.len < sizeof(*pkt->ip)) return; // Truncated + if ((pkt->ip->ver >> 4) != 4) return; // Not IP + ihl = pkt->ip->ver & 0x0F; + if (ihl < 5) return; // bad IHL + if (pkt->pay.len < (uint16_t) (ihl * 4)) return; // Truncated / malformed + // There can be link padding, take length from IP header + len = mg_ntohs(pkt->ip->len); // IP datagram length + if (len < (uint16_t) (ihl * 4) || len > pkt->pay.len) return; // malformed + pkt->pay.len = len; // strip padding + mkpay(pkt, (uint32_t *) pkt->ip + ihl); // account for opts + if (!ipcsum_ok(pkt->ip)) return; + frag = mg_ntohs(pkt->ip->frag); + if (frag & IP_MORE_FRAGS_MSK || frag & IP_FRAG_OFFSET_MSK) { + struct mg_connection *c; + if (pkt->ip->proto == 17) pkt->udp = (struct udp *) (pkt->pay.buf); + if (pkt->ip->proto == 6) pkt->tcp = (struct tcp *) (pkt->pay.buf); + 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->pay.buf); + 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->pay.buf); + if (pkt->pay.len < sizeof(*pkt->udp)) return; // truncated + // Take length from UDP header + len = mg_ntohs(pkt->udp->len); // UDP datagram length + if (len < sizeof(*pkt->udp) || len > pkt->pay.len) return; // malformed + pkt->pay.len = len; // strip excess data + 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) && + len >= offsetof(struct dhcp, options)) { + pkt->dhcp = (struct dhcp *) (pkt->udp + 1); + mkpay(pkt, &pkt->dhcp->options); + rx_dhcp_client(ifp, pkt); + } else if (ifp->enable_dhcp_server && pkt->udp->dport == mg_htons(67) && + len >= offsetof(struct dhcp, options)) { + pkt->dhcp = (struct dhcp *) (pkt->udp + 1); + mkpay(pkt, &pkt->dhcp->options); + rx_dhcp_server(ifp, pkt); + } else if (!rx_udp(ifp, pkt)) { + // Should send ICMP Destination Unreachable for unicasts, but keep + // silent + } + } else if (pkt->ip->proto == 6) { + uint8_t off; + pkt->tcp = (struct tcp *) (pkt->pay.buf); + if (pkt->pay.len < sizeof(*pkt->tcp)) return; + off = pkt->tcp->off >> 4; // account for opts + if (pkt->pay.len < (uint16_t) (4 * off)) return; + mkpay(pkt, (uint32_t *) pkt->tcp + 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); + } else { + MG_DEBUG(("Unknown IP proto %x", (int) pkt->ip->proto)); + if (mg_log_level >= MG_LL_VERBOSE) + mg_hexdump(pkt->ip, pkt->pay.len >= 32 ? 32 : pkt->pay.len); + } +} + +#if MG_ENABLE_IPV6 +static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) { + uint16_t len = 0, plen; + uint8_t next, *nhdr; + bool loop = true; + if (pkt->pay.len < sizeof(*pkt->ip6)) return; // Truncated + if ((pkt->ip6->ver >> 4) != 0x6) return; // Not IPv6 + plen = mg_ntohs(pkt->ip6->plen); + if (plen > (pkt->pay.len - sizeof(*pkt->ip6))) return; // malformed + next = pkt->ip6->next; + nhdr = (uint8_t *) (pkt->ip6 + 1); + while (loop) { + uint16_t hlen; + switch (next) { + case 0: // Hop-by-Hop 4.3 + case 43: // Routing 4.4 + case 60: // Destination Options 4.6 + case 51: // Authentication RFC-4302 + MG_INFO(("IPv6 extension header %d", (int) next)); + if (((uint32_t) len + 2) > plen) return; // nhdr[0, 1]; malformed + next = nhdr[0]; + hlen = (uint16_t) (8 * (nhdr[1] + 1)); + if (((uint32_t) len + hlen) > plen) return; // malformed + len += hlen; + nhdr += hlen; + break; + case 44: // Fragment 4.5 + { + struct mg_connection *c; + if (((uint32_t) len + 2) > plen) return; // nhdr[0, 1]; malformed + if (nhdr[0] == 17) pkt->udp = (struct udp *) (pkt->pay.buf); + if (nhdr[0] == 6) pkt->tcp = (struct tcp *) (pkt->pay.buf); + c = getpeer(ifp->mgr, pkt, false); + if (c) mg_error(c, "Received fragmented packet"); + } + return; + case 59: // No Next Header 4.7 + return; + case 50: // IPsec ESP RFC-4303, unsupported + default: + loop = false; + break; + } + } + // There can be link padding, take payload length from IPv6 header - options + pkt->pay.buf = (char *) nhdr; + pkt->pay.len = plen - len; + if (next == 58) { + pkt->icmp6 = (struct icmp6 *) (pkt->pay.buf); + if (pkt->pay.len < sizeof(*pkt->icmp6)) return; + mkpay(pkt, pkt->icmp6 + 1); + MG_DEBUG(("ICMPv6 %M -> %M len %u", mg_print_ip6, &pkt->ip6->src, + mg_print_ip6, &pkt->ip6->dst, (int) pkt->pay.len)); + rx_icmp6(ifp, pkt); + } else if (next == 17) { + pkt->udp = (struct udp *) (pkt->pay.buf); + if (pkt->pay.len < sizeof(*pkt->udp)) return; + // Take length from UDP header + len = mg_ntohs(pkt->udp->len); // UDP datagram length + if (len < sizeof(*pkt->udp) || len > pkt->pay.len) return; // malformed + pkt->pay.len = len; // strip excess data + mkpay(pkt, pkt->udp + 1); + MG_DEBUG(("UDP %M:%hu -> %M:%hu len %u", mg_print_ip6, &pkt->ip6->src, + mg_ntohs(pkt->udp->sport), mg_print_ip6, &pkt->ip6->dst, + mg_ntohs(pkt->udp->dport), (int) pkt->pay.len)); + if (ifp->enable_dhcp6_client && pkt->udp->dport == mg_htons(546)) { + pkt->dhcp6 = (struct dhcp6 *) (pkt->udp + 1); + mkpay(pkt, pkt->dhcp6 + 1); + // rx_dhcp6_client(ifp, pkt); +#if 0 + } else if (ifp->enable_dhcp_server && pkt->udp->dport == mg_htons(547)) { + pkt->dhcp6 = (struct dhcp6 *) (pkt->udp + 1); + mkpay(pkt, pkt->dhcp6 + 1); + rx_dhcp6_server(ifp, pkt); +#endif + } else if (!rx_udp(ifp, pkt)) { + // Should send ICMPv6 Destination Unreachable for unicasts, keep silent + } + } else if (next == 6) { + uint8_t off; + pkt->tcp = (struct tcp *) (pkt->pay.buf); + if (pkt->pay.len < sizeof(*pkt->tcp)) return; + off = pkt->tcp->off >> 4; // account for opts + if (pkt->pay.len < (uint16_t) (4 * off)) return; + mkpay(pkt, (uint32_t *) pkt->tcp + off); + MG_DEBUG(("TCP %M:%hu -> %M:%hu len %u", mg_print_ip6, &pkt->ip6->src, + mg_ntohs(pkt->tcp->sport), mg_print_ip6, &pkt->ip6->dst, + mg_ntohs(pkt->tcp->dport), (int) pkt->pay.len)); + rx_tcp(ifp, pkt); + } else { + MG_DEBUG(("Unknown IPv6 next hdr %x", (int) next)); + if (mg_log_level >= MG_LL_VERBOSE) + mg_hexdump(pkt->ip6, pkt->pay.len >= 32 ? 32 : pkt->pay.len); + } +} +#else +#define rx_ip6(x, y) +#endif + +static void mg_tcpip_rx(struct mg_tcpip_if *ifp, void *buf, size_t len) { + struct pkt pkt; + enum mg_l2proto proto; + memset(&pkt, 0, sizeof(pkt)); + pkt.raw.buf = (char *) buf; + pkt.raw.len = len; + pkt.l2 = (uint8_t *) pkt.raw.buf; + if (!mg_l2_rx(ifp, &proto, &pkt.pay, &pkt.raw)) return; + if (ifp->state < MG_TCPIP_STATE_UP) return; // discard while L2 is not up + if (proto == MG_TCPIP_L2PROTO_ARP) { + pkt.arp = (struct arp *) (pkt.pay.buf); + if (pkt.pay.len < sizeof(*pkt.arp)) return; // Truncated + mg_tcpip_call(ifp, MG_TCPIP_EV_ARP, &pkt.raw); + rx_arp(ifp, &pkt); + } else if (proto == MG_TCPIP_L2PROTO_IPV6) { + pkt.ip6 = (struct ip6 *) (pkt.pay.buf); + rx_ip6(ifp, &pkt); + } else if (proto == MG_TCPIP_L2PROTO_IPV4) { + pkt.ip = (struct ip *) (pkt.pay.buf); + rx_ip(ifp, &pkt); + } +} + +static void mg_ip_poll(struct mg_tcpip_if *ifp, bool s1) { + if (ifp->state < MG_TCPIP_STATE_UP) return; + // DHCP RFC-2131 (4.4) + if (ifp->enable_dhcp_client && s1) { + if (ifp->state == MG_TCPIP_STATE_UP) { + tx_dhcp_discover(ifp); // INIT (4.4.1) + } else if (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, mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_BCAST, NULL), ifp->ip, + 0xffffffff); + } // TODO(): Handle T1 (RENEWING) and T2 (REBINDING) (4.4.5) + } + } +} +static void mg_ip_link(struct mg_tcpip_if *ifp, bool drv_up, bool l2_up) { + bool cur_drv = (ifp->state != MG_TCPIP_STATE_DOWN); + bool cur_l2 = (ifp->state >= MG_TCPIP_STATE_UP); + if (!l2_up && ifp->enable_dhcp_client) ifp->ip = 0; + if (drv_up != cur_drv || l2_up != cur_l2) { // link/L2 state has changed + ifp->state = !drv_up ? MG_TCPIP_STATE_DOWN + : !l2_up ? MG_TCPIP_STATE_LINK_UP + : ifp->enable_dhcp_client || ifp->ip == 0 ? MG_TCPIP_STATE_UP + : MG_TCPIP_STATE_IP; + onstatechange(ifp); + } else if (!ifp->enable_dhcp_client && ifp->state == MG_TCPIP_STATE_UP && + ifp->ip) { + ifp->state = MG_TCPIP_STATE_IP; // ifp->fn has set an IP + onstatechange(ifp); + } +} + +#if MG_ENABLE_IPV6 +static void mg_ip6_poll(struct mg_tcpip_if *ifp, bool s1) { + if (ifp->state6 < MG_TCPIP_STATE_UP) return; + if (ifp->enable_slaac && s1 && ifp->state6 == MG_TCPIP_STATE_UP) + tx_ndp_rs(ifp); +} +static void mg_ip6_link(struct mg_tcpip_if *ifp, bool drv_up, bool l2_up) { + bool cur_drv = (ifp->state6 != MG_TCPIP_STATE_DOWN); + bool cur_l2 = (ifp->state6 >= MG_TCPIP_STATE_UP); + if (drv_up != cur_drv || l2_up != cur_l2) { // link/L2 state has changed + if (l2_up && ifp->ip6ll[0] == 0 && ifp->ip6ll[1] == 0) { // gen ll address + uint8_t px[8] = {0xfe, 0x80, 0, 0, 0, 0, 0, 0}; // RFC-4291 2.5.6 + mg_l2_genip6(ifp->l2type, ifp->ip6ll, 64, ifp->mac); + memcpy(ifp->ip6ll, px, 8); // RFC-4291 2.5.4 + } // just got our link local address if we didn't have one. + // If static configuration is used, global addresses, + // prefix length, and gw are already filled at this point. + if (ifp->ip6[0] == 0 && ifp->ip6[1] == 0) ifp->enable_slaac = true; + if (!l2_up && ifp->enable_slaac) ifp->ip6[0] = ifp->ip6[1] = 0; + ifp->state6 = !drv_up ? MG_TCPIP_STATE_DOWN + : !l2_up ? MG_TCPIP_STATE_LINK_UP + : ifp->enable_slaac || ifp->ip6[0] == 0 ? MG_TCPIP_STATE_UP + : MG_TCPIP_STATE_IP; + onstate6change(ifp); + } else if (!ifp->enable_slaac && ifp->state6 == MG_TCPIP_STATE_UP && + ifp->ip6[0]) { + ifp->state6 = MG_TCPIP_STATE_IP; // ifp->fn has set an IP + onstate6change(ifp); + } +} +#else +#define mg_ip6_poll(x, y) +#define mg_ip6_link(x, y, z) +#endif + +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 (expired_1000ms) { +#if MG_ENABLE_TCPIP_PRINT_DEBUG_STATS + const char *names[] = {"down", "lnk up", "lnk rdy", "req", "ip", "ready"}; + size_t max = sizeof(names) / sizeof(char *); + unsigned int state = ifp->state >= max ? max - 1 : ifp->state; + MG_INFO(("Status: %s, IP: %M, rx:%u, tx:%u, dr:%u, er:%u", names[state], + mg_print_ip4, &ifp->ip, ifp->nrecv, ifp->nsent, ifp->ndrop, + ifp->nerr)); +#if MG_ENABLE_IPV6 + state = ifp->state6 >= max ? max - 1 : ifp->state6; + if (state > MG_TCPIP_STATE_UP) + MG_INFO(("Status: %s, IPv6: %M", names[state], mg_print_ip6, &ifp->ip6)); +#endif +#endif + backlog_poll(ifp->mgr); + } + // Handle gw ARP request timeout, order is important + if (expired_1000ms && ifp->state == MG_TCPIP_STATE_IP) { + ifp->state = MG_TCPIP_STATE_READY; // keep best-effort MAC or poison mark + onstatechange(ifp); + } + if (expired_1000ms && ifp->state == MG_TCPIP_STATE_READY && !ifp->gw_ready && + ifp->gw != 0) + mg_tcpip_arp_request(ifp, ifp->gw, NULL); // retry GW ARP request +#if MG_ENABLE_IPV6 + // Handle gw NS/NA req/resp timeout, order is important + if (expired_1000ms && ifp->state6 == MG_TCPIP_STATE_IP) { + ifp->state6 = MG_TCPIP_STATE_READY; // keep best-effort MAC or poison mark + onstate6change(ifp); + } + if (expired_1000ms && ifp->state == MG_TCPIP_STATE_READY && !ifp->gw6_ready && + (ifp->gw6[0] != 0 || ifp->gw6[1] != 0)) + tx_ndp_ns(ifp, ifp->gw6, NULL); // retry GW hwaddr resolution +#endif + + { // poll driver and let L2 do its work, if any + // Handle physical interface up/down status + bool drv_up, l2_up; + drv_up = ifp->driver->poll ? ifp->driver->poll(ifp, expired_1000ms) : true; + l2_up = mg_l2_poll(ifp, expired_1000ms); // Handle L2 up/down link status; + if (expired_1000ms) { // ifp->state rules over state6 + mg_ip_link(ifp, drv_up, l2_up); // Handle IPv4 + mg_ip6_link(ifp, drv_up, l2_up); // Handle IPv6 + if (ifp->state < MG_TCPIP_STATE_UP) MG_ERROR(("Network is down")); + mg_tcpip_call(ifp, MG_TCPIP_EV_TIMER_1S, NULL); + } + } + + mg_ip_poll(ifp, expired_1000ms); // Handle IPv4 + mg_ip6_poll(ifp, expired_1000ms); // Handle IPv6 + + // Read data from the network (mg_tcpip_rx() will discard for us) + if (ifp->driver->rx != NULL) { // Simple polling driver, returns one frame + 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 { // Complex poll / Interrupt-based driver. Queues recvd frames + char *buf; + size_t len, cnt = 7; // Max 7 packets to fetch + while (cnt-- > 0 && (len = mg_queue_next(&ifp->recv_queue, &buf)) > 0) { + mg_tcpip_rx(ifp, buf, len); + mg_queue_del(&ifp->recv_queue, len); + } + } + if (ifp->state < MG_TCPIP_STATE_UP) return; // need to let L2 do its job + + // Process timeouts + for (c = ifp->mgr->conns; c != NULL; c = c->next) { + struct connstate *s = (struct connstate *) (c + 1); + if ((c->is_udp && !c->is_arplooking) || c->is_listening || c->is_resolving) + continue; + if (ifp->now > s->timer) { + if (s->ttype == MIP_TTYPE_ARP) { + mg_error(c, "ARP timeout"); + } else if (c->is_udp) { + continue; + } else 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, &c->loc, &c->rem, TH_ACK, mg_htonl(s->seq), + mg_htonl(s->ack), NULL, 0); + s->acked = s->ack; + } 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, &c->loc, &c->rem, TH_ACK, 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 L2 address is not set, make a random one; fill MTU + mg_l2_init(ifp); + ifp->mtu = ifp->l2mtu; + + if (ifp->dhcp_name[0] == '\0') // If DHCP name is not set, use "mip" + memcpy(ifp->dhcp_name, "mip", 4); + ifp->dhcp_name[sizeof(ifp->dhcp_name) - 1] = '\0'; // Just in case + + if (ifp->driver->init && !ifp->driver->init(ifp)) { + MG_ERROR(("driver init failed")); + } else { + ifp->tx.buf = (char *) mg_calloc(1, ifp->framesize), + ifp->tx.len = ifp->framesize; + if (ifp->recv_queue.size == 0) + ifp->recv_queue.size = ifp->driver->rx ? ifp->framesize : 8192; + ifp->recv_queue.buf = (char *) mg_calloc(1, ifp->recv_queue.size); + ifp->timer_1000ms = mg_millis(); + mgr->ifp = ifp; + ifp->mgr = mgr; + mgr->extraconnsize = sizeof(struct connstate); + if (ifp->ip == 0 && ifp->l2type != MG_TCPIP_L2_PPP && + ifp->l2type != MG_TCPIP_L2_PPPoE) + ifp->enable_dhcp_client = true; + 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) { + mg_free(ifp->recv_queue.buf); + mg_free(ifp->tx.buf); + mg_free(ifp->dns4_url); +} + +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)); + tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_SYN, isn, 0, NULL, 0); +} + +static void l2addr_resolved(struct mg_connection *c) { + if (c->is_udp) { + c->is_connecting = 0; + mg_call(c, MG_EV_CONNECT, NULL); + } else { + send_syn(c); + settmout(c, MIP_TTYPE_SYN); + } +} + +void mg_connect_resolved(struct mg_connection *c) { + struct mg_tcpip_if *ifp = c->mgr->ifp; + uint8_t *l2addr; + c->is_resolving = 0; + if (ifp->eport < MG_EPHEMERAL_PORT_BASE) ifp->eport = MG_EPHEMERAL_PORT_BASE; + c->loc.port = mg_htons(ifp->eport++); +#if MG_ENABLE_IPV6 + if (c->rem.is_ip6) { + if(c->rem.addr.ip6[0] == ifp->ip6ll[0]) { // same local LAN, use ll + c->loc.addr.ip6[0] = ifp->ip6ll[0], c->loc.addr.ip6[1] = ifp->ip6ll[1]; + } else { // use global address + c->loc.addr.ip6[0] = ifp->ip6[0], c->loc.addr.ip6[1] = ifp->ip6[1]; + } + c->loc.is_ip6 = true; + } else +#endif + { + c->loc.addr.ip4 = ifp->ip; + } + 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); + c->is_connecting = 1; + if (c->is_udp && (l2addr = tcpip_mapip(ifp, &c->rem)) != NULL) { + struct connstate *s = (struct connstate *) (c + 1); + memcpy(s->mac, l2addr, sizeof(s->mac)); + l2addr_resolved(c); // broadcast or multicast +#if MG_ENABLE_IPV6 + } else if (c->rem.is_ip6) { + if (match_prefix((uint8_t *) c->rem.addr.ip6, ifp->prefix, + ifp->prefix_len) // same global LAN + || (c->rem.addr.ip6[0] == ifp->ip6ll[0] // same local LAN + && !MG_IP6MATCH(c->rem.addr.ip6, ifp->gw6))) { // and not gw + // If we're in the same LAN, fire a Neighbor Solicitation + MG_DEBUG(("%lu NS lookup...", c->id)); + tx_ndp_ns(ifp, c->rem.addr.ip6, NULL); // RFC-4861 4.3, requesting + settmout(c, MIP_TTYPE_ARP); + c->is_arplooking = 1; + } else if (ifp->gw6_ready) { + struct connstate *s = (struct connstate *) (c + 1); + memcpy(s->mac, ifp->gw6mac, sizeof(s->mac)); + l2addr_resolved(c); + } else { + MG_ERROR(("No IPv6 gateway, can't connect")); + } +#endif + } else { + uint32_t rem_ip = c->rem.addr.ip4; + if (ifp->ip && ((rem_ip & ifp->mask) == (ifp->ip & ifp->mask)) && + rem_ip != ifp->gw) { // skip if gw (onstatechange -> ARP) + // If we're in the same LAN, fire an ARP lookup. + MG_DEBUG(("%lu ARP lookup...", c->id)); + mg_tcpip_arp_request(ifp, rem_ip, NULL); + settmout(c, MIP_TTYPE_ARP); + c->is_arplooking = 1; + } else if (ifp->gw_ready) { + struct connstate *s = (struct connstate *) (c + 1); + memcpy(s->mac, ifp->gwmac, sizeof(s->mac)); + l2addr_resolved(c); + } else { + MG_ERROR(("No gateway, can't connect")); + } + } +} + +bool mg_open_listener(struct mg_connection *c, const char *url) { + 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)); + return false; + } + 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); + // TODO(): mg_tls_send() may return 0 forever on steady OOM + 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, + tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_FIN | TH_ACK, + 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_close_conn(c); + (void) s; +} + +static bool can_write(struct mg_connection *c) { + return c->is_connecting == 0 && c->is_resolving == 0 && c->send.len > 0 && + c->is_tls_hs == 0 && c->is_arplooking == 0; +} + +void mg_mgr_poll(struct mg_mgr *mgr, int ms) { + struct mg_connection *c, *tmp; + uint64_t now = mg_millis(); + mg_timer_poll(&mgr->timers, now); + mg_ota_poll(mgr); + if (mgr->ifp == NULL || mgr->ifp->driver == NULL) return; + mg_tcpip_poll(mgr->ifp, now); + for (c = mgr->conns; c != NULL; c = tmp) { + struct connstate *s = (struct connstate *) (c + 1); + bool is_tls = c->is_tls && !c->is_resolving && !c->is_arplooking && + !c->is_listening && !c->is_connecting; + tmp = c->next; + mg_call(c, MG_EV_POLL, &now); + MG_VERBOSE(("%lu .. %c%c%c%c%c %lu %lu", 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', + mg_tls_pending(c), c->rtls.len)); + // order is important, TLS conn close with > 1 record in buffer (below) + if (is_tls && (c->rtls.len > 0 || mg_tls_pending(c) > 0)) + c->is_tls_hs ? mg_tls_handshake(c) : handle_tls_recv(c); + if (can_write(c)) write_conn(c); + if (is_tls && c->send.len == 0) mg_tls_flush(c); + if (c->is_draining && c->send.len == 0 && s->ttype != MIP_TTYPE_FIN) + init_closure(c); + // For non-TLS, close immediately upon completing the 3-way closure + // For TLS, handle any pending data (above) until MIP_TTYPE_FIN expires + if (s->twclosure && + (!c->is_tls || (c->rtls.len == 0 && mg_tls_pending(c) == 0))) + c->is_closing = 1; + 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 = c->mgr->ifp; + bool res = false; + if (!c->loc.is_ip6 && (ifp->ip == 0 || ifp->state != MG_TCPIP_STATE_READY)) { + mg_error(c, "net down"); +#if MG_ENABLE_IPV6 + } else if (c->loc.is_ip6 && ifp->state6 != MG_TCPIP_STATE_READY) { + mg_error(c, "net down"); +#endif + } else if (c->is_udp && (c->is_arplooking || c->is_resolving)) { + // Fail to send, no target MAC or IP + MG_VERBOSE(("still resolving...")); + } else if (c->is_udp) { + len = trim_len(c, len); // Trimming length if necessary + res = udp_send(c, buf, len); + } else { + res = len == 0 || mg_iobuf_add(&c->send, c->send.len, buf, len) > 0; + // returning 0 means an OOM condition (iobuf couldn't resize), yet this is + // so far recoverable, let the caller decide + } + return res; +} + +uint8_t mcast_addr[6] = {0x01, 0x00, 0x5e, 0x00, 0x00, 0xfb}; +void mg_multicast_add(struct mg_connection *c, char *ip) { + (void) ip; // ip4/6_mcastmac(mcast_mac, &ip); ipv6 param + // TODO(): actual IP -> MAC; check database, update + c->mgr->ifp->update_mac_hash_table = true; // mark dirty +} + +bool mg_dnsc_init(struct mg_mgr *mgr, struct mg_dns *dnsc); + +static void setdns4(struct mg_tcpip_if *ifp, uint32_t *ip) { + struct mg_dns *dnsc; + mg_free(ifp->dns4_url); + ifp->dns4_url = mg_mprintf("udp://%M:53", mg_print_ip4, ip); + dnsc = &ifp->mgr->dns4; + dnsc->url = (const char *) ifp->dns4_url; + MG_DEBUG(("Set DNS URL to %s", dnsc->url)); + if (ifp->mgr->use_dns6) return; + if (dnsc->c != NULL) mg_close_conn(dnsc->c); + if (!mg_dnsc_init(ifp->mgr, dnsc)) // create DNS connection + MG_ERROR(("DNS connection creation failed")); +} + +void mg_tcpip_mapip(struct mg_connection *c, struct mg_addr *ip) { + struct connstate *s = (struct connstate *) (c + 1); + uint8_t *l2addr = tcpip_mapip(c->mgr->ifp, ip); + if (l2addr == NULL) return; + memcpy(s->mac, l2addr, sizeof(s->mac)); +} + +#endif // MG_ENABLE_TCPIP + +#ifdef MG_ENABLE_LINES +#line 1 "src/ota.c" +#endif + + + + + + + + +#ifndef MG_OTA_MAX_URL_LEN +#define MG_OTA_MAX_URL_LEN 256 +#endif + +// Scannable version tag embedded in every firmware binary, for server-side +// version extraction. The version string starts after the "MG_VERSION:" prefix. +static const char s_fw_version[] = "MG_VERSION:" MG_OTA_FIRMWARE_VERSION; + +static bool s_autocommit_ok; // True after OTA server confirms "same version" + +static struct mg_ota_state { + char json_url[MG_OTA_MAX_URL_LEN]; + char url[MG_OTA_MAX_URL_LEN]; + size_t size; + uint8_t sha256[32]; + void (*fn)(const char *error_message); +} *s_ota; + +static void s_firmware_fn(struct mg_connection *c, int ev, void *ev_data); + +#if MG_ENABLE_CUSTOM_DEVICE_ID +#else +void mg_ota_device_id(char *buf, size_t len) { +#if defined(UID_BASE) && \ + (defined(__SYSTEM_STM32F4XX_H) || defined(__SYSTEM_STM32F7XX_H) || \ + defined(SYSTEM_STM32H5XX_H) || defined(SYSTEM_STM32H7XX_H) || \ + defined(SYSTEM_STM32N6XX_H) || defined(SYSTEM_STM32U5XX_H)) + uint32_t *p = (uint32_t *) UID_BASE; + mg_snprintf(buf, len, "stm32_%08x%08x%08x", p[0], p[1], p[2]); +#elif MG_ARCH == MG_ARCH_PICOSDK + pico_unique_board_id_t x = {0}; + pico_get_unique_board_id(&x); + mg_snprintf(buf, len, "rp_%02x%02x%02x%02x%02x%02x%02x%02x", x.id[0], x.id[1], + x.id[2], x.id[3], x.id[4], x.id[5], x.id[6], x.id[7]); +#elif defined(OCOTP_BASE) && OCOTP_BASE == 0x401F4000u + mg_snprintf(buf, len, "rt10xx_%08x%08x", OCOTP->CFG0, OCOTP->CFG1); +#elif defined(OCOTP_BASE) && OCOTP_BASE == 0x40CAC000u + mg_snprintf(buf, len, "rt11xx_%08x%08x", OCOTP->FUSEN[16].FUSE, + OCOTP->FUSEN[17].FUSE); +#elif MG_ARCH == MG_ARCH_ESP32 + uint8_t mac[6] = {0}; + esp_efuse_mac_get_default(mac); + mg_snprintf(buf, len, "esp32_%02x%02x%02x%02x%02x%02x", mac[0], mac[1], + mac[2], mac[3], mac[4], mac[5]); +#else + mg_snprintf(buf, len, "%d", 0); +#endif +} +#endif + +static void s_version_fn(struct mg_connection *c, int ev, void *ev_data) { + uint64_t expiration = *(uint64_t *) c->data; + if (ev == MG_EV_POLL) { + if (mg_millis() > expiration) mg_error(c, "Metadata timeout"); + } else if (ev == MG_EV_CONNECT) { + char id[40]; + struct mg_str host = mg_url_host(s_ota->json_url); + const char *uri = mg_url_uri(s_ota->json_url); + const char *sep = strchr(uri, '?') == NULL ? "?" : "&"; + mg_ota_device_id(id, sizeof(id)); + id[sizeof(id) - 1] = '\0'; + mg_printf( + c, + "GET %s%sarch=%d&version=%s&id=%s&interval=%d&boot=%d HTTP/1.1\r\n" + "Host: %.*s\r\n" + "Connection: close\r\n\r\n", + uri, sep, MG_ARCH, s_fw_version + 11, id, MG_OTA_PULL_INTERVAL_SECONDS, + MG_OTA_STATE_GET(), host.len, host.buf); + } else if (ev == MG_EV_HTTP_MSG) { + struct mg_http_message *hm = (struct mg_http_message *) ev_data; + char version[MG_OTA_MAX_VERSION_LEN]; + double result; + MG_DEBUG(("Got metadata: %.*s", hm->body.len, hm->body.buf)); + if (mg_http_status(hm) != 200 || mg_json_get(hm->body, "$", NULL) != 0 || + !mg_json_unescape(hm->body, "$.version", version, sizeof(version)) || + !mg_json_unescape(hm->body, "$.url", s_ota->url, sizeof(s_ota->url)) || + !mg_json_get_num(hm->body, "$.size", &result) || result <= 0) { + char buf[100]; + mg_snprintf(buf, sizeof(buf), "Bad metadata: %.*s", hm->body.len, + hm->body.buf); + s_ota->fn(buf); + mg_free(s_ota); + s_ota = NULL; + } else if (strcmp(version, s_fw_version + 11) == 0) { + s_autocommit_ok = true; + s_ota->fn("Same version"); + mg_free(s_ota); + s_ota = NULL; + } else { + struct mg_connection *fc; + s_ota->size = (size_t) result; + // TODO (robertc2000): parse and validate sha256 + MG_DEBUG(("Firmware version: %s, url: %s, size: %ld", version, s_ota->url, + s_ota->size)); + fc = mg_http_connect(c->mgr, s_ota->url, s_firmware_fn, NULL); + if (fc == NULL) { + s_ota->fn("Failed to connect"); + mg_free(s_ota); + s_ota = NULL; + } else { + *(uint64_t *) fc->data = mg_millis() + 5 * 1000; // Set expiration + } + } + c->is_closing = 1; + } else if (ev == MG_EV_ERROR) { + s_ota->fn((char *) ev_data); + mg_free(s_ota); + s_ota = NULL; + } +} + +static void status_fn(const char *errmsg) { + if (errmsg) MG_ERROR(("OTA failed: %s", errmsg)); +} + +static void status_fn_2(struct mg_connection *c, const char *errmsg) { + if (s_ota) s_ota->fn(errmsg); + mg_free(s_ota); + s_ota = NULL; + mg_http_reply(c, errmsg ? 500 : 200, "", "%s\n", errmsg ? errmsg : "ok"); +} + +static void s_firmware_fn(struct mg_connection *c, int ev, void *ev_data) { + uint64_t expiration = *(uint64_t *) c->data; + if (ev == MG_EV_POLL) { + if (mg_millis() > expiration) mg_error(c, "OTA timeout"); + } else if (ev == MG_EV_CONNECT) { + struct mg_str host = mg_url_host(s_ota->url); + mg_printf(c, + "GET %s HTTP/1.1\r\n" + "Host: %.*s\r\n" + "Connection: close\r\n\r\n", + mg_url_uri(s_ota->url), (int) host.len, host.buf); + *(uint64_t *) c->data = mg_millis() + 300 * 1000; // Set expiration + } else if (ev == MG_EV_HTTP_HDRS) { + struct mg_http_message *hm = (struct mg_http_message *) ev_data; + int status = mg_http_status(hm); + if (status != 200 || hm->body.len != s_ota->size) { + mg_error(c, "Bad HTTP response: status %d, size %lu vs %lu", status, + (unsigned long) hm->body.len, (unsigned long) s_ota->size); + } else { + MG_DEBUG(("Beginning OTA (%lu bytes)", (unsigned long) s_ota->size)); + mg_http_start_ota(c, hm, status_fn_2); + } + } else if (ev == MG_EV_ERROR) { + s_ota->fn((char *) ev_data); + mg_free(s_ota); + s_ota = NULL; + } +} + +void mg_ota_url_check(struct mg_mgr *mgr, const char *json_url, + void (*fn)(const char *error_message)) { + if (fn == NULL) fn = status_fn; + if (s_ota != NULL) { + fn("OTA already in progress"); + } else if ((s_ota = (struct mg_ota_state *) mg_calloc(1, sizeof(*s_ota))) == + NULL) { + fn("Out of memory"); + } else { + struct mg_connection *c; + mg_snprintf(s_ota->json_url, sizeof(s_ota->json_url), "%s", json_url); + MG_DEBUG(("Connecting to %s", json_url)); + c = mg_http_connect(mgr, s_ota->json_url, s_version_fn, NULL); + if (c == NULL) { + mg_free(s_ota); + s_ota = NULL; + fn("Failed to connect"); + } else { + s_ota->fn = fn; + *(uint64_t *) c->data = mg_millis() + 5 * 1000; // Set expiration + } + } +} + +void mg_ota_poll(struct mg_mgr *mgr) { + static uint64_t t = 5000; // Fire first time 5 sec after boot + + static uint64_t feed_timer; // Advances 500ms per tick; tracks elapsed time + if (MG_OTA_STATE_GET() == MG_OTA_TESTING && + mg_timer_expired(&feed_timer, 500, mg_millis())) { + if (feed_timer < (uint64_t) MG_OTA_ROLLBACK_TIMEOUT_SECONDS * 1000) { + MG_OTA_ROLLBACK_TIMER_FEED(); // Feed watchdog every 500ms + } else if (s_autocommit_ok) { + MG_INFO(("Auto-committing firmware")); + MG_OTA_STATE_SET(MG_OTA_CONFIRMED); // Stop feeding → IWDG resets cleanly + } else { + MG_INFO(("No commit confirmation, rolling back")); + // Stop feeding → IWDG fires → resets into MG_OTA_FAILED → rollback + } + } + + if (MG_OTA_URL != NULL && + mg_timer_expired(&t, MG_OTA_PULL_INTERVAL_SECONDS * 1000, mg_millis())) { + mg_ota_url_check(mgr, MG_OTA_URL, MG_OTA_STATUS_FN); + } +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/ota_ch32v307.c" +#endif + + + + +#if MG_OTA == MG_OTA_CH32V307 +// RM: https://www.wch-ic.com/downloads/CH32FV2x_V3xRM_PDF.html + +static bool mg_ch32v307_write(void *, const void *, size_t); +static bool mg_ch32v307_swap(void); + +static struct mg_flash s_mg_flash_ch32v307 = { + (void *) 0x08000000, // Start + 480 * 1024, // Size, first 320k is 0-wait + 4 * 1024, // Sector size, 4k + 4, // Align, 32 bit + mg_ch32v307_write, + mg_ch32v307_swap, +}; + +#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) + +MG_IRAM static void flash_unlock(void) { + static bool unlocked; + if (unlocked == false) { + MG_REG(FLASH_KEYR) = 0x45670123; + MG_REG(FLASH_KEYR) = 0xcdef89ab; + unlocked = true; + } +} + +MG_IRAM static void flash_wait(void) { + while (MG_REG(FLASH_STATR) & MG_BIT(0)) (void) 0; +} + +MG_IRAM static void mg_ch32v307_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(); +} + +MG_IRAM static bool is_page_boundary(const void *addr) { + uint32_t val = (uint32_t) addr; + return (val & (s_mg_flash_ch32v307.secsz - 1)) == 0; +} + +MG_IRAM static bool mg_ch32v307_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_ch32v307_erase(dst); + *dst++ = *src++; + flash_wait(); + } + MG_REG(FLASH_CTLR) &= ~MG_BIT(0); // Clear PG + return true; +} + +MG_IRAM bool mg_ch32v307_swap(void) { + return true; +} + +// just overwrite instead of swap +MG_IRAM static void single_bank_swap(char *p1, char *p2, size_t s, size_t ss) { + // no stdlib calls here + for (size_t ofs = 0; ofs < s; ofs += ss) { + mg_ch32v307_write(p1 + ofs, p2 + ofs, ss); + } + *((volatile uint32_t *) 0xbeef0000) |= 1U << 7; // NVIC_SystemReset() +} + +bool mg_ota_begin(size_t new_firmware_size) { + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_ch32v307); +} + +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_ch32v307); +} + +bool mg_ota_end(void) { + if (mg_ota_flash_end(&s_mg_flash_ch32v307)) { + // Swap partitions. Pray power does not go away + MG_INFO(("Swapping partitions, size %u (%u sectors)", + s_mg_flash_ch32v307.size, + s_mg_flash_ch32v307.size / s_mg_flash_ch32v307.secsz)); + MG_INFO(("Do NOT power off...")); + mg_log_level = MG_LL_NONE; + // TODO() disable IRQ, s_flash_irq_disabled = true; + // Runs in RAM, will reset when finished + single_bank_swap( + (char *) s_mg_flash_ch32v307.start, + (char *) s_mg_flash_ch32v307.start + s_mg_flash_ch32v307.size / 2, + s_mg_flash_ch32v307.size / 2, s_mg_flash_ch32v307.secsz); + } + return false; +} +struct mg_flash *mg_flash = &s_mg_flash_ch32v307; +#endif + +#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; +} +#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_imxrt.c" +#endif + + + + +#if MG_OTA >= MG_OTA_RT1020 && MG_OTA <= MG_OTA_RT1170 + +static bool mg_imxrt_write(void *, const void *, size_t); +static bool mg_imxrt_swap(void); + +#if MG_OTA <= MG_OTA_RT1060 +#define MG_IMXRT_FLASH_START 0x60000000 +#define FLEXSPI_NOR_INSTANCE 0 +#elif MG_OTA == MG_OTA_RT1064 +#define MG_IMXRT_FLASH_START 0x70000000 +#define FLEXSPI_NOR_INSTANCE 1 +#else // RT1170 +#define MG_IMXRT_FLASH_START 0x30000000 +#define FLEXSPI_NOR_INSTANCE 1 +#endif + +#if MG_OTA == MG_OTA_RT1050 +#define MG_IMXRT_SECTOR_SIZE (256 * 1024) +#define MG_IMXRT_PAGE_SIZE 512 +#else +#define MG_IMXRT_SECTOR_SIZE (4 * 1024) +#define MG_IMXRT_PAGE_SIZE 256 +#endif + +// TODO(): fill at init, support more devices in a dynamic way +// TODO(): then, check alignment is <= 256, see Wizard's #251 +static struct mg_flash s_mg_flash_imxrt = { + (void *) MG_IMXRT_FLASH_START, // Start, + 4 * 1024 * 1024, // Size, 4mb + MG_IMXRT_SECTOR_SIZE, // Sector size + MG_IMXRT_PAGE_SIZE, // Align + mg_imxrt_write, + mg_imxrt_swap, +}; + +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_DUMMY_RWDS_DDR 0x2D +#define MG_RADDR_SDR 0x02 +#define MG_RADDR_DDR 0x22 +#define MG_CADDR_DDR 0x23 +#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), \ + } + +#define MG_FLEXSPI_HYPER_LUT \ + { \ + [0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xA0, MG_RADDR_DDR, \ + MG_FLEXSPI_8PAD, 0x18), \ + [1] = MG_FLEXSPI_LUT_SEQ(MG_CADDR_DDR, MG_FLEXSPI_8PAD, 0x10, \ + MG_DUMMY_DDR, MG_FLEXSPI_8PAD, 0x0C), \ + [2] = MG_FLEXSPI_LUT_SEQ(MG_READ_DDR, MG_FLEXSPI_8PAD, 0x04, MG_STOP, \ + MG_FLEXSPI_1PAD, 0x0), \ + [4 * 1 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 1 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xAA), \ + [4 * 1 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x05), \ + [4 * 1 + 3] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x70), \ + [4 * 2 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xA0, \ + MG_RADDR_DDR, MG_FLEXSPI_8PAD, 0x18), \ + [4 * 2 + 1] = \ + MG_FLEXSPI_LUT_SEQ(MG_CADDR_DDR, MG_FLEXSPI_8PAD, 0x10, \ + MG_DUMMY_RWDS_DDR, MG_FLEXSPI_8PAD, 0x0B), \ + [4 * 2 + 2] = MG_FLEXSPI_LUT_SEQ(MG_READ_DDR, MG_FLEXSPI_8PAD, 0x4, \ + MG_STOP, MG_FLEXSPI_1PAD, 0x0), \ + [4 * 3 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 3 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xAA), \ + [4 * 3 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x05), \ + [4 * 3 + 3] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xAA), \ + [4 * 4 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 4 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x55), \ + [4 * 4 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x02), \ + [4 * 4 + 3] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x55), \ + [4 * 5 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 5 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xAA), \ + [4 * 5 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x05), \ + [4 * 5 + 3] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x80), \ + [4 * 6 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 6 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xAA), \ + [4 * 6 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x05), \ + [4 * 6 + 3] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xAA), \ + [4 * 7 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 7 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x55), \ + [4 * 7 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x02), \ + [4 * 7 + 3] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x55), \ + [4 * 8 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_RADDR_DDR, MG_FLEXSPI_8PAD, 0x18), \ + [4 * 8 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CADDR_DDR, MG_FLEXSPI_8PAD, 0x10, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 8 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x30, \ + MG_STOP, MG_FLEXSPI_1PAD, 0x0), \ + [4 * 9 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 9 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xAA), \ + [4 * 9 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x05), \ + [4 * 9 + 3] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xA0), \ + [4 * 10 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_RADDR_DDR, MG_FLEXSPI_8PAD, 0x18), \ + [4 * 10 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CADDR_DDR, MG_FLEXSPI_8PAD, 0x10, \ + MG_WRITE_DDR, MG_FLEXSPI_8PAD, 0x80), \ + [4 * 11 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 11 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xAA), \ + [4 * 11 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x05), \ + [4 * 11 + 3] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x80), \ + [4 * 12 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 12 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xAA), \ + [4 * 12 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x05), \ + [4 * 12 + 3] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xAA), \ + [4 * 13 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 13 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x55), \ + [4 * 13 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x02), \ + [4 * 13 + 3] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x55), \ + [4 * 14 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 14 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xAA), \ + [4 * 14 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x05), \ + [4 * 14 + 3] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x10), \ + } + +#define MG_LUT_CUSTOM_SEQ \ + { \ + {.seqNum = 0, .seqId = 0, .reserved = 0}, \ + {.seqNum = 2, .seqId = 1, .reserved = 0}, \ + {.seqNum = 2, .seqId = 3, .reserved = 0}, \ + {.seqNum = 4, .seqId = 5, .reserved = 0}, \ + {.seqNum = 2, .seqId = 9, .reserved = 0}, \ + {.seqNum = 4, .seqId = 11, .reserved = 0}, \ + } + +#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) + +#if MG_OTA == MG_OTA_RT1020 || MG_OTA == MG_OTA_RT1050 +// RT102X and RT105x boards support ROM API version 1.4 +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_OTA <= MG_OTA_RT1064 +// RT104x and RT106x support ROM API version 1.5 +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); +}; +#else +// RT117x support ROM API version 1.7 +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); + uint32_t reserved; + 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); + int (*erase_sector)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t address); + int (*erase_block)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t address); + void (*hw_reset)(uint32_t instance, uint32_t resetLogic); + int (*wait_busy)(uint32_t instance, struct mg_flexspi_nor_config *config, + bool isParallelMode, uint32_t address); + int (*set_clock_source)(uint32_t instance, uint32_t clockSrc); + void (*config_clock)(uint32_t instance, uint32_t freqOption, + uint32_t sampleClkMode); +}; +#endif + +#if MG_OTA <= MG_OTA_RT1064 +#define MG_FLEXSPI_BASE 0x402A8000 +#define flexspi_nor \ + (*((struct mg_flexspi_nor_driver_interface **) (*(uint32_t *) 0x0020001c + \ + 16))) +#else +#define MG_FLEXSPI_BASE 0x400CC000 +#define flexspi_nor \ + (*((struct mg_flexspi_nor_driver_interface **) (*(uint32_t *) 0x0021001c + \ + 12))) +#endif + +static bool s_flash_irq_disabled; + +MG_IRAM static bool flash_page_start(volatile uint32_t *dst) { + char *base = (char *) s_mg_flash_imxrt.start, + *end = base + s_mg_flash_imxrt.size; + volatile char *p = (char *) dst; + return p >= base && p < end && ((p - base) % s_mg_flash_imxrt.secsz) == 0; +} + +#if MG_OTA == MG_OTA_RT1050 +// Configuration for Hyper flash memory +static struct mg_flexspi_nor_config default_config = { + .memConfig = + { + .tag = MG_FLEXSPI_CFG_BLK_TAG, + .version = MG_FLEXSPI_CFG_BLK_VERSION, + .readSampleClkSrc = 3, // ReadSampleClk_LoopbackFromDqsPad + .csHoldTime = 3, + .csSetupTime = 3, + .columnAddressWidth = 3u, + .controllerMiscOption = + MG_BIT(6) | MG_BIT(4) | MG_BIT(3) | MG_BIT(0), + .deviceType = 1, // serial NOR + .sflashPadType = 8, + .serialClkFreq = 7, // 133MHz + .sflashA1Size = 64 * 1024 * 1024, + .dataValidTime = {15, 0}, + .busyOffset = 15, + .busyBitPolarity = 1, + .lutCustomSeqEnable = 0x1, + .lookupTable = MG_FLEXSPI_HYPER_LUT, + .lutCustomSeq = MG_LUT_CUSTOM_SEQ, + }, + .pageSize = 512, + .sectorSize = 256 * 1024, + .ipcmdSerialClkFreq = 1, + .serialNorType = 1u, + .blockSize = 256 * 1024, + .isUniformBlockSize = true}; +#else +// Note: this QSPI configuration works for RTs supporting QSPI +// Configuration for QSPI memory +static 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}; +#endif + +// must reside in RAM, as flash will be erased +MG_IRAM static int flexspi_nor_get_config( + struct mg_flexspi_nor_config **config) { + *config = &default_config; + return 0; +} + +#if 0 +// ROM API get_config call (ROM version >= 1.5) +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 (status) { + MG_ERROR(("Failed to extract flash configuration: status %u", status)); + } + return status; +} +#endif + +MG_IRAM static void mg_spin(volatile uint32_t count) { + while (count--) (void) 0; +} + +MG_IRAM static void flash_wait(void) { + while ((*((volatile uint32_t *) (MG_FLEXSPI_BASE + 0xE0)) & MG_BIT(1)) == 0) + mg_spin(1); +} + +MG_IRAM static bool flash_erase(struct mg_flexspi_nor_config *config, + void *addr) { + if (flash_page_start(addr) == false) { + MG_ERROR(("%p is not on a sector boundary", addr)); + return false; + } + + void *dst = (void *) ((char *) addr - (char *) s_mg_flash_imxrt.start); + + bool ok = (flexspi_nor->erase(FLEXSPI_NOR_INSTANCE, config, (uint32_t) dst, + s_mg_flash_imxrt.secsz) == 0); + MG_DEBUG(("Sector starting at %p erasure: %s", addr, ok ? "ok" : "fail")); + return ok; +} + +#if 0 +// standalone erase call +MG_IRAM static bool mg_imxrt_erase(void *addr) { + struct mg_flexspi_nor_config config, *config_ptr = &config; + bool ret; + // Interrupts must be disabled before calls to ROM API in RT1020 and 1060 + MG_ARM_DISABLE_IRQ(); + ret = (flexspi_nor_get_config(&config_ptr) == 0); + if (ret) ret = flash_erase(config_ptr, addr); + MG_ARM_ENABLE_IRQ(); + return ret; +} +#endif + +MG_IRAM bool mg_imxrt_swap(void) { + return true; +} + +MG_IRAM static bool mg_imxrt_write(void *addr, const void *buf, size_t len) { + struct mg_flexspi_nor_config config, *config_ptr = &config; + bool ok = false; + // Interrupts must be disabled before calls to ROM API in RT1020 and 1060 + MG_ARM_DISABLE_IRQ(); + if (flexspi_nor_get_config(&config_ptr) != 0) goto fwxit; + if ((len % s_mg_flash_imxrt.align) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, s_mg_flash_imxrt.align)); + goto fwxit; + } + if ((char *) addr < (char *) s_mg_flash_imxrt.start) { + MG_ERROR(("Invalid flash write address: %p", addr)); + goto fwxit; + } + + uint32_t *dst = (uint32_t *) addr; + uint32_t *src = (uint32_t *) buf; + uint32_t *end = (uint32_t *) ((char *) buf + len); + ok = true; + + while (ok && src < end) { + if (flash_page_start(dst) && flash_erase(config_ptr, dst) == false) { + ok = false; + break; + } + uint32_t status; + uint32_t dst_ofs = (uint32_t) dst - (uint32_t) s_mg_flash_imxrt.start; + if ((char *) buf >= (char *) s_mg_flash_imxrt.start) { + // If we copy from FLASH to FLASH, then we first need to copy the source + // to RAM + size_t tmp_buf_size = s_mg_flash_imxrt.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]; + } + status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, config_ptr, + (uint32_t) dst_ofs, tmp); + } else { + status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, config_ptr, + (uint32_t) dst_ofs, src); + } + src = (uint32_t *) ((char *) src + s_mg_flash_imxrt.align); + dst = (uint32_t *) ((char *) dst + s_mg_flash_imxrt.align); + if (status != 0) { + ok = false; + } + } + MG_DEBUG(("Flash write %lu bytes @ %p: %s.", len, dst, ok ? "ok" : "fail")); +fwxit: + if (!s_flash_irq_disabled) MG_ARM_ENABLE_IRQ(); + return ok; +} + +// just overwrite instead of swap +MG_IRAM static void single_bank_swap(char *p1, char *p2, size_t s, size_t ss) { + // no stdlib calls here + for (size_t ofs = 0; ofs < s; ofs += ss) { + mg_imxrt_write(p1 + ofs, p2 + ofs, ss); + } + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; +} + +bool mg_ota_begin(size_t new_firmware_size) { + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_imxrt); +} + +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_imxrt); +} + +bool mg_ota_end(void) { + if (mg_ota_flash_end(&s_mg_flash_imxrt)) { + if (0) { // is_dualbank() + // TODO(): no devices so far + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; + } else { + // Swap partitions. Pray power does not go away + MG_INFO(("Swapping partitions, size %u (%u sectors)", + s_mg_flash_imxrt.size, + s_mg_flash_imxrt.size / s_mg_flash_imxrt.secsz)); + MG_INFO(("Do NOT power off...")); + mg_log_level = MG_LL_NONE; + s_flash_irq_disabled = true; + // Runs in RAM, will reset when finished + single_bank_swap( + (char *) s_mg_flash_imxrt.start, + (char *) s_mg_flash_imxrt.start + s_mg_flash_imxrt.size / 2, + s_mg_flash_imxrt.size / 2, s_mg_flash_imxrt.secsz); + } + } + return false; +} +struct mg_flash *mg_flash = &s_mg_flash_imxrt; +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/ota_mcxn.c" +#endif + + + + +#if MG_OTA == MG_OTA_MCXN + +// - Flash phrase: 16 bytes; smallest portion programmed in one operation. +// - Flash page: 128 bytes; largest portion programmed in one operation. +// - Flash sector: 8 KB; smallest portion that can be erased in one operation. +// - Flash API mg_flash_driver->program: "start" and "len" must be page-size +// aligned; to use 'phrase', FMU register access is needed. Using ROM + +static bool mg_mcxn_write(void *, const void *, size_t); +static bool mg_mcxn_swap(void); + +static struct mg_flash s_mg_flash_mcxn = { + (void *) 0, // Start, filled at init + 0, // Size, filled at init + 0, // Sector size, filled at init + 0, // Align, filled at init + mg_mcxn_write, + mg_mcxn_swap, +}; + +struct mg_flash_config { + uint32_t addr; + uint32_t size; + uint32_t blocks; + uint32_t page_size; + uint32_t sector_size; + uint32_t ffr[6]; + uint32_t reserved0[5]; + uint32_t *bootctx; + bool useahb; +}; + +struct mg_flash_driver_interface { + uint32_t version; + uint32_t (*init)(struct mg_flash_config *); + uint32_t (*erase)(struct mg_flash_config *, uint32_t start, uint32_t len, + uint32_t key); + uint32_t (*program)(struct mg_flash_config *, uint32_t start, uint8_t *src, + uint32_t len); + uint32_t (*verify_erase)(struct mg_flash_config *, uint32_t start, + uint32_t len); + uint32_t (*verify_program)(struct mg_flash_config *, uint32_t start, + uint32_t len, const uint8_t *expected, + uint32_t *addr, uint32_t *failed); + uint32_t reserved1[12]; + uint32_t (*read)(struct mg_flash_config *, uint32_t start, uint8_t *dest, + uint32_t len); + uint32_t reserved2[4]; + uint32_t (*deinit)(struct mg_flash_config *); +}; +#define mg_flash_driver \ + ((struct mg_flash_driver_interface *) (*((uint32_t *) 0x1303fc00 + 4))) +#define MG_MCXN_FLASK_KEY (('k' << 24) | ('e' << 16) | ('f' << 8) | 'l') + +MG_IRAM static bool flash_sector_start(volatile uint32_t *dst) { + char *base = (char *) s_mg_flash_mcxn.start, + *end = base + s_mg_flash_mcxn.size; + volatile char *p = (char *) dst; + return p >= base && p < end && ((p - base) % s_mg_flash_mcxn.secsz) == 0; +} + +MG_IRAM static bool flash_erase(struct mg_flash_config *config, void *addr) { + if (flash_sector_start(addr) == false) { + MG_ERROR(("%p is not on a sector boundary", addr)); + return false; + } + uint32_t dst = + (uint32_t) addr - (uint32_t) s_mg_flash_mcxn.start; // future-proof + uint32_t status = mg_flash_driver->erase(config, dst, s_mg_flash_mcxn.secsz, + MG_MCXN_FLASK_KEY); + bool ok = (status == 0); + if (!ok) MG_ERROR(("Flash write error: %lu", status)); + MG_DEBUG(("Sector starting at %p erasure: %s", addr, ok ? "ok" : "fail")); + return ok; +} + +#if 0 +// read-while-write, no need to disable IRQs for standalone usage +MG_IRAM static bool mg_mcxn_erase(void *addr) { + uint32_t status; + struct mg_flash_config config; + if ((status = mg_flash_driver->init(&config)) != 0) { + MG_ERROR(("Flash driver init error: %lu", status)); + return false; + } + bool ok = flash_erase(&config, addr); + mg_flash_driver->deinit(&config); + return ok; +} +#endif + +MG_IRAM static bool mg_mcxn_swap(void) { + // TODO(): no devices so far + return true; +} + +static bool s_flash_irq_disabled; + +MG_IRAM static bool mg_mcxn_write(void *addr, const void *buf, size_t len) { + bool ok = false; + uint32_t status; + struct mg_flash_config config; + if ((status = mg_flash_driver->init(&config)) != 0) { + MG_ERROR(("Flash driver init error: %lu", status)); + return false; + } + if ((len % s_mg_flash_mcxn.align) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, s_mg_flash_mcxn.align)); + goto fwxit; + } + if ((((size_t) addr - (size_t) s_mg_flash_mcxn.start) % + s_mg_flash_mcxn.align) != 0) { + MG_ERROR(("%p is not on a page boundary", addr)); + goto fwxit; + } + + uint32_t *dst = (uint32_t *) addr; + uint32_t *src = (uint32_t *) buf; + uint32_t *end = (uint32_t *) ((char *) buf + len); + ok = true; + + MG_ARM_DISABLE_IRQ(); + while (ok && src < end) { + if (flash_sector_start(dst) && flash_erase(&config, dst) == false) { + ok = false; + break; + } + uint32_t dst_ofs = (uint32_t) dst - (uint32_t) s_mg_flash_mcxn.start; + // assume source is in RAM or in a different bank or read-while-write + status = mg_flash_driver->program(&config, dst_ofs, (uint8_t *) src, + s_mg_flash_mcxn.align); + src = (uint32_t *) ((char *) src + s_mg_flash_mcxn.align); + dst = (uint32_t *) ((char *) dst + s_mg_flash_mcxn.align); + if (status != 0) { + MG_ERROR(("Flash write error: %lu", status)); + ok = false; + } + } + if (!s_flash_irq_disabled) MG_ARM_ENABLE_IRQ(); + MG_DEBUG(("Flash write %lu bytes @ %p: %s.", len, dst, ok ? "ok" : "fail")); + +fwxit: + mg_flash_driver->deinit(&config); + return ok; +} + +// try to swap (honor dual image), otherwise just overwrite +MG_IRAM static void single_bank_swap(char *p1, char *p2, size_t s, size_t ss) { + char *tmp = mg_calloc(1, ss); + // no stdlib calls here + for (size_t ofs = 0; ofs < s; ofs += ss) { + if (tmp != NULL) + for (size_t i = 0; i < ss; i++) tmp[i] = p1[ofs + i]; + mg_mcxn_write(p1 + ofs, p2 + ofs, ss); + if (tmp != NULL) mg_mcxn_write(p2 + ofs, tmp, ss); + } + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; +} + +bool mg_ota_begin(size_t new_firmware_size) { + uint32_t status; + struct mg_flash_config config; + if ((status = mg_flash_driver->init(&config)) != 0) { + MG_ERROR(("Flash driver init error: %lu", status)); + return false; + } + s_mg_flash_mcxn.start = (void *) config.addr; + s_mg_flash_mcxn.size = config.size; + s_mg_flash_mcxn.secsz = config.sector_size; + s_mg_flash_mcxn.align = config.page_size; + mg_flash_driver->deinit(&config); + MG_DEBUG( + ("%lu-byte flash @%p, using %lu-byte sectors with %lu-byte-aligned pages", + s_mg_flash_mcxn.size, s_mg_flash_mcxn.start, s_mg_flash_mcxn.secsz, + s_mg_flash_mcxn.align)); + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_mcxn); +} + +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_mcxn); +} + +bool mg_ota_end(void) { + if (mg_ota_flash_end(&s_mg_flash_mcxn)) { + if (0) { // is_dualbank() + // TODO(): no devices so far + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; + } else { + // Swap partitions. Pray power does not go away + MG_INFO(("Swapping partitions, size %u (%u sectors)", + s_mg_flash_mcxn.size, + s_mg_flash_mcxn.size / s_mg_flash_mcxn.secsz)); + MG_INFO(("Do NOT power off...")); + mg_log_level = MG_LL_NONE; + s_flash_irq_disabled = true; + // Runs in RAM, will reset when finished + single_bank_swap( + (char *) s_mg_flash_mcxn.start, + (char *) s_mg_flash_mcxn.start + s_mg_flash_mcxn.size / 2, + s_mg_flash_mcxn.size / 2, s_mg_flash_mcxn.secsz); + } + } + return false; +} +struct mg_flash *mg_flash = &s_mg_flash_mcxn; +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/ota_picosdk.c" +#endif + + + + +#if MG_OTA == MG_OTA_PICOSDK + +// Both RP2040 and RP2350 have no flash, low-level flash access support in +// bootrom, and high-level support in Pico-SDK (2.0+ for the RP2350) +// - The RP2350 in RISC-V mode is not tested +// NOTE(): See OTA design notes + +static bool mg_picosdk_write(void *, const void *, size_t); +static bool mg_picosdk_swap(void); + +static struct mg_flash s_mg_flash_picosdk = { + (void *) 0x10000000, // Start; functions handle offset +#ifdef PICO_FLASH_SIZE_BYTES + PICO_FLASH_SIZE_BYTES, // Size, from board definitions +#else + 0x200000, // Size, guess... is 2M enough ? +#endif + FLASH_SECTOR_SIZE, // Sector size, from hardware_flash + FLASH_PAGE_SIZE, // Align, from hardware_flash + mg_picosdk_write, mg_picosdk_swap, +}; + +#define MG_MODULO2(x, m) ((x) & ((m) -1)) + +static bool __no_inline_not_in_flash_func(flash_sector_start)( + volatile uint32_t *dst) { + char *base = (char *) s_mg_flash_picosdk.start, + *end = base + s_mg_flash_picosdk.size; + volatile char *p = (char *) dst; + return p >= base && p < end && + MG_MODULO2(p - base, s_mg_flash_picosdk.secsz) == 0; +} + +static bool __no_inline_not_in_flash_func(flash_erase)(void *addr) { + if (flash_sector_start(addr) == false) { + MG_ERROR(("%p is not on a sector boundary", addr)); + return false; + } + void *dst = (void *) ((char *) addr - (char *) s_mg_flash_picosdk.start); + flash_range_erase((uint32_t) dst, s_mg_flash_picosdk.secsz); + MG_DEBUG(("Sector starting at %p erasure", addr)); + return true; +} + +static bool s_flash_irq_disabled; + +static bool __no_inline_not_in_flash_func(mg_picosdk_write)(void *addr, + const void *buf, + size_t len) { + if ((len % s_mg_flash_picosdk.align) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, s_mg_flash_picosdk.align)); + return false; + } + if ((((size_t) addr - (size_t) s_mg_flash_picosdk.start) % + s_mg_flash_picosdk.align) != 0) { + MG_ERROR(("%p is not on a page boundary", addr)); + return false; + } + + uint32_t *dst = (uint32_t *) addr; + uint32_t *src = (uint32_t *) buf; + uint32_t *end = (uint32_t *) ((char *) buf + len); + +#ifndef __riscv + MG_ARM_DISABLE_IRQ(); +#else + asm volatile("csrrc zero, mstatus, %0" : : "i"(1 << 3) : "memory"); +#endif + while (src < end) { + uint32_t dst_ofs = (uint32_t) dst - (uint32_t) s_mg_flash_picosdk.start; + if (flash_sector_start(dst) && flash_erase(dst) == false) break; + // flash_range_program() runs in RAM and handles writing up to + // FLASH_PAGE_SIZE bytes. Source must not be in flash + flash_range_program((uint32_t) dst_ofs, (uint8_t *) src, + s_mg_flash_picosdk.align); + src = (uint32_t *) ((char *) src + s_mg_flash_picosdk.align); + dst = (uint32_t *) ((char *) dst + s_mg_flash_picosdk.align); + } + if (!s_flash_irq_disabled) { +#ifndef __riscv + MG_ARM_ENABLE_IRQ(); +#else + asm volatile("csrrs mstatus, %0" : : "i"(1 << 3) : "memory"); +#endif + } + MG_DEBUG(("Flash write %lu bytes @ %p.", len, dst)); + return true; +} + +// just overwrite instead of swap +static void __no_inline_not_in_flash_func(single_bank_swap)(char *p1, char *p2, + size_t s, + size_t ss) { + char *tmp_1 = mg_calloc(1, ss); // copy from 1st partition + if (tmp_1 == NULL) return; + char *tmp_2 = mg_calloc(1, ss); // copy from 2nd partition + if (tmp_2 == NULL) return; +#if PICO_RP2040 + uint32_t xip[256 / sizeof(uint32_t)]; + void *dst_1 = (void *) ((char *) p1 - (char *) s_mg_flash_picosdk.start); + void *dst_2 = (void *) ((char *) p2- (char *) s_mg_flash_picosdk.start); + // use SDK function calls to get BootROM function pointers + rom_connect_internal_flash_fn connect = (rom_connect_internal_flash_fn) rom_func_lookup(ROM_FUNC_CONNECT_INTERNAL_FLASH); + rom_flash_exit_xip_fn xit = (rom_flash_exit_xip_fn) rom_func_lookup(ROM_FUNC_FLASH_EXIT_XIP); + rom_flash_range_program_fn program = (rom_flash_range_program_fn) rom_func_lookup(ROM_FUNC_FLASH_RANGE_PROGRAM); + rom_flash_flush_cache_fn flush = (rom_flash_flush_cache_fn) rom_func_lookup(ROM_FUNC_FLASH_FLUSH_CACHE); + // no stdlib calls here. + MG_ARM_DISABLE_IRQ(); + // 2nd bootloader (XIP) is in flash, SDK functions copy it to RAM on entry + for (size_t i = 0; i < 256 / sizeof(uint32_t); i++) + xip[i] = ((uint32_t *) (s_mg_flash_picosdk.start))[i]; + // flash has been erased, no XIP to copy. Only BootROM calls possible + for (uint32_t ofs = 0; ofs < s; ofs += ss) { + for (size_t i = 0; i < ss; i++) { + tmp_1[i] = p1[ofs + i]; + tmp_2[i] = p2[ofs + i]; + } + flash_range_erase((uint32_t) dst_1 + ofs, ss); + flash_range_erase((uint32_t) dst_2 + ofs, ss); + __compiler_memory_barrier(); + connect(); + xit(); + program((uint32_t) dst_1 + ofs, tmp_2, ss); + program((uint32_t) dst_2 + ofs, tmp_1, ss); + flush(); + ((void (*)(void))((intptr_t) xip + 1))(); // enter XIP again + } + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; // AIRCR = SYSRESETREQ +#else + // RP2350 has BootRAM and copies second bootloader there, SDK uses that copy, + // It might also be able to take advantage of partition swapping + rom_reboot_fn reboot = (rom_reboot_fn) rom_func_lookup(ROM_FUNC_REBOOT); + for (size_t ofs = 0; ofs < s; ofs += ss) { + for (size_t i = 0; i < ss; i++) { + tmp_1[i] = p1[ofs + i]; + tmp_2[i] = p2[ofs + i]; + } + mg_picosdk_write(p1 + ofs, tmp_2, ss); + mg_picosdk_write(p2 + ofs, tmp_1, ss); + } + reboot(BOOT_TYPE_NORMAL | 0x100, 1, 0, 0); // 0x100: NO_RETURN_ON_SUCCESS +#endif +} + +static bool __no_inline_not_in_flash_func(mg_picosdk_swap)(void) { + // TODO(): RP2350 might have some A/B functionality (DS 5.1) + // Swap partitions. Pray power does not go away + MG_INFO(("Swapping partitions, size %u (%u sectors)", + s_mg_flash_picosdk.size, + s_mg_flash_picosdk.size / s_mg_flash_picosdk.secsz)); + MG_INFO(("Do NOT power off...")); + mg_log_level = MG_LL_NONE; + s_flash_irq_disabled = true; + // Runs in RAM, will reset when finished or return on failure + single_bank_swap( + (char *) s_mg_flash_picosdk.start, + (char *) s_mg_flash_picosdk.start + s_mg_flash_picosdk.size / 2, + s_mg_flash_picosdk.size / 2, s_mg_flash_picosdk.secsz); + return false; +} + +bool mg_ota_begin(size_t new_firmware_size) { + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_picosdk); +} + +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_picosdk); +} + +bool mg_ota_end(void) { + if (mg_ota_flash_end(&s_mg_flash_picosdk)); + return false; +} +struct mg_flash *mg_flash = &s_mg_flash_picosdk; +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/ota_rw612.c" +#endif + + + + +#if MG_OTA == MG_OTA_RW612 + +MG_IRAM static bool mg_frdm_write(void *, const void *, size_t); +static bool mg_frdm_swap(void); + +static struct mg_flash s_mg_flash_frdm = {(void *) 0x08000000, // Start, + 0x200000, // Size + 0x1000, // Sector size + 0x100, // Align + mg_frdm_write, + mg_frdm_swap}; + +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 isDataOrderSwapped; + uint8_t reserved0[1]; + uint8_t serialNorType; + uint8_t needExitNoCmdMode; + uint8_t halfClkForNonReadCmd; + uint8_t needRestoreNoCmdMode; + uint32_t blockSize; + uint32_t flashStateCtx; + uint32_t reserve2[10]; +}; + +struct mg_flexspi_nor_driver_interface { + uint32_t version; + uint32_t (*init)(uint32_t instance, struct mg_flexspi_nor_config *config); + uint32_t (*wait_busy)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t address, bool keepState); + uint32_t (*page_program)(uint32_t instance, + struct mg_flexspi_nor_config *config, + uint32_t dstAddr, const uint32_t *src, + bool keepState); + uint32_t (*erase_all)(uint32_t instance, + struct mg_flexspi_nor_config *config); + uint32_t (*erase)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t start, uint32_t length); + uint32_t (*erase_sector)(uint32_t instance, + struct mg_flexspi_nor_config *config, + uint32_t address); + uint32_t (*erase_block)(uint32_t instance, + struct mg_flexspi_nor_config *config, + uint32_t address); + uint32_t (*read)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t *dst, uint32_t start, uint32_t bytes); + void (*config_clock)(uint32_t instance, uint32_t freqOption, + uint32_t sampleClkMode); + uint32_t (*set_clock_source)(uint32_t clockSrc); + uint32_t (*get_config)(uint32_t instance, + struct mg_flexspi_nor_config *config, + uint32_t *option); + void (*hw_reset)(uint32_t instance, uint32_t reset_logic); + uint32_t (*xfer)(uint32_t instance, char *xfer); + uint32_t (*update_lut)(uint32_t instance, uint32_t seqIndex, + const uint32_t *lutBase, uint32_t numberOfSeq); + uint32_t (*partial_program)(uint32_t instance, + struct mg_flexspi_nor_config *config, + uint32_t dstAddr, const uint32_t *src, + uint32_t length, bool keepState); +}; + +#define MG_FLEXSPI_CFG_BLK_TAG (0x42464346UL) +#define MG_FLEXSPI_BASE 0x40134000UL + +#define MG_CMD_SDR 0x01 +#define MG_RADDR_SDR 0x02 +#define MG_WRITE_SDR 0x08 +#define MG_READ_SDR 0x09 +#define MG_DUMMY_SDR 0x0C +#define MG_STOP_EXE 0 + +#define MG_FLEXSPI_1PAD 0 +#define MG_FLEXSPI_4PAD 2 + +#define MG_FLEXSPI_LUT_OPERAND0(x) (((x) &0xFF) << 0) +#define MG_FLEXSPI_LUT_NUM_PADS0(x) (((x) &0x3) << 8) +#define MG_FLEXSPI_LUT_OPCODE0(x) (((x) &0x3F) << 10) +#define MG_FLEXSPI_LUT_OPERAND1(x) (((x) &0xFF) << 16) +#define MG_FLEXSPI_LUT_NUM_PADS1(x) (((x) &0x3) << 24) +#define MG_FLEXSPI_LUT_OPCODE1(x) (((x) &0x3F) << 26) + +#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)) + +struct mg_flexspi_nor_config default_config = { + .memConfig = + { + .tag = MG_FLEXSPI_CFG_BLK_TAG, + .version = 0, + .readSampleClkSrc = 1, + .csHoldTime = 3, + .csSetupTime = 3, + .deviceModeCfgEnable = 1, + .deviceModeSeq = {.seqNum = 1, .seqId = 2}, + .deviceModeArg = 0x0740, + .configCmdEnable = 0, + .deviceType = 0x1, + .sflashPadType = 4, + .serialClkFreq = 4, + .sflashA1Size = 0x4000000U, + .sflashA2Size = 0, + .sflashB1Size = 0, + .sflashB2Size = 0, + .lookupTable = + { + [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 * 2 + 0] = + MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x01, + MG_WRITE_SDR, MG_FLEXSPI_1PAD, 0x02), + [4 * 3 + 0] = + MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x06, + MG_STOP_EXE, MG_FLEXSPI_1PAD, 0x00), + [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, 0x52, + 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, 0x00, + MG_STOP_EXE, MG_FLEXSPI_1PAD, 0x00), + [4 * 11 + 0] = + MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x60, + MG_STOP_EXE, MG_FLEXSPI_1PAD, 0x00), + }, + }, + .pageSize = 0x100, + .sectorSize = 0x1000, + .ipcmdSerialClkFreq = 0, + .blockSize = 0x8000, +}; + +#define MG_FLEXSPI_NOR_INSTANCE 0 +#define MG_ROMAPI_ADDRESS 0x13030000U +#define flexspi_nor \ + ((struct mg_flexspi_nor_driver_interface *) (( \ + (uint32_t *) MG_ROMAPI_ADDRESS)[5])) + +MG_IRAM static bool flash_page_start(volatile uint32_t *dst) { + char *base = (char *) s_mg_flash_frdm.start, + *end = base + s_mg_flash_frdm.size; + volatile char *p = (char *) dst; + return p >= base && p < end && ((p - base) % s_mg_flash_frdm.secsz) == 0; +} + +MG_IRAM static int flexspi_nor_get_config( + struct mg_flexspi_nor_config *config) { + uint32_t option = 0xc0000004; + return flexspi_nor->get_config(MG_FLEXSPI_NOR_INSTANCE, config, &option); +} + +MG_IRAM static int flash_init(void) { + static bool initialized = false; + if (!initialized) { + struct mg_flexspi_nor_config config; + memset(&config, 0, sizeof(config)); + flexspi_nor->set_clock_source(0); + flexspi_nor->config_clock(MG_FLEXSPI_NOR_INSTANCE, 1, 0); + if (flexspi_nor->init(MG_FLEXSPI_NOR_INSTANCE, &default_config)) { + return 1; + } + flexspi_nor_get_config(&config); + if (flexspi_nor->init(MG_FLEXSPI_NOR_INSTANCE, &config)) { + return 1; + } + initialized = true; + } + return 0; +} + +MG_IRAM static bool flash_erase(struct mg_flexspi_nor_config *config, + void *addr) { + if (flash_page_start(addr) == false) { + MG_ERROR(("%p is not on a sector boundary", addr)); + return false; + } + + void *dst = (void *) ((char *) addr - (char *) s_mg_flash_frdm.start); + bool ok = (flexspi_nor->erase_sector(MG_FLEXSPI_NOR_INSTANCE, config, + (uint32_t) dst) == 0); + MG_INFO(("Sector starting at %p erasure: %s", addr, ok ? "ok" : "fail")); + return ok; +} + +MG_IRAM bool mg_frdm_swap(void) { + return true; +} + +MG_IRAM static void flash_wait(void) { + while ((*((volatile uint32_t *) (MG_FLEXSPI_BASE + 0xE0)) & MG_BIT(1)) == 0) + (void) 0; +} + +static bool s_flash_irq_disabled; + +MG_IRAM static bool mg_frdm_write(void *addr, const void *buf, size_t len) { + struct mg_flexspi_nor_config config; + bool ok = false; + MG_ARM_DISABLE_IRQ(); + if (flash_init() != 0) goto fwxit; + if (flexspi_nor_get_config(&config) != 0) goto fwxit; + if ((len % s_mg_flash_frdm.align) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, s_mg_flash_frdm.align)); + goto fwxit; + } + if ((char *) addr < (char *) s_mg_flash_frdm.start) { + MG_ERROR(("Invalid flash write address: %p", addr)); + goto fwxit; + } + + uint32_t *dst = (uint32_t *) addr; + uint32_t *src = (uint32_t *) buf; + uint32_t *end = (uint32_t *) ((char *) buf + len); + ok = true; + + while (ok && src < end) { + if (flash_page_start(dst) && flash_erase(&config, dst) == false) { + ok = false; + break; + } + uint32_t status; + uint32_t dst_ofs = (uint32_t) dst - (uint32_t) s_mg_flash_frdm.start; + if ((char *) buf >= (char *) s_mg_flash_frdm.start && + (char *) buf < + (char *) (s_mg_flash_frdm.start + s_mg_flash_frdm.size)) { + // If we copy from FLASH to FLASH, then we first need to copy the source + // to RAM + size_t tmp_buf_size = s_mg_flash_frdm.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]; + } + status = flexspi_nor->page_program(MG_FLEXSPI_NOR_INSTANCE, &config, + (uint32_t) dst_ofs, tmp, false); + } else { + status = flexspi_nor->page_program(MG_FLEXSPI_NOR_INSTANCE, &config, + (uint32_t) dst_ofs, src, false); + } + src = (uint32_t *) ((char *) src + s_mg_flash_frdm.align); + dst = (uint32_t *) ((char *) dst + s_mg_flash_frdm.align); + if (status != 0) { + ok = false; + } + } + MG_INFO(("Flash write %lu bytes @ %p: %s.", len, dst, ok ? "ok" : "fail")); +fwxit: + if (!s_flash_irq_disabled) MG_ARM_ENABLE_IRQ(); + return ok; +} + +// just overwrite instead of swap +MG_IRAM static void single_bank_swap(char *p1, char *p2, size_t s, size_t ss) { + // no stdlib calls here + for (size_t ofs = 0; ofs < s; ofs += ss) { + mg_frdm_write(p1 + ofs, p2 + ofs, ss); + } + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; +} + +bool mg_ota_begin(size_t new_firmware_size) { + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_frdm); +} + +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_frdm); +} + +bool mg_ota_end(void) { + if (mg_ota_flash_end(&s_mg_flash_frdm)) { + if (0) { // is_dualbank() + // TODO(): no devices so far + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; + } else { + // Swap partitions. Pray power does not go away + MG_INFO(("Swapping partitions, size %u (%u sectors)", + s_mg_flash_frdm.size, + s_mg_flash_frdm.size / s_mg_flash_frdm.secsz)); + MG_INFO(("Do NOT power off...")); + mg_log_level = MG_LL_NONE; + s_flash_irq_disabled = true; + // Runs in RAM, will reset when finished + single_bank_swap( + (char *) s_mg_flash_frdm.start, + (char *) s_mg_flash_frdm.start + s_mg_flash_frdm.size / 2, + s_mg_flash_frdm.size / 2, s_mg_flash_frdm.secsz); + } + } + return false; +} +struct mg_flash *mg_flash = &s_mg_flash_frdm; +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/ota_stm32f.c" +#endif + + + + +#if MG_OTA == MG_OTA_STM32F + +static bool mg_stm32f_write(void *, const void *, size_t); +static bool mg_stm32f_swap(void); + +static struct mg_flash s_mg_flash_stm32f = { + (void *) 0x08000000, // Start + 0, // Size, FLASH_SIZE_REG + 0, // Irregular sector size + 32, // Align, 256 bit + mg_stm32f_write, + mg_stm32f_swap, +}; + +#define MG_FLASH_BASE 0x40023c00 +#define MG_FLASH_KEYR 0x04 +#define MG_FLASH_SR 0x0c +#define MG_FLASH_CR 0x10 +#define MG_FLASH_OPTCR 0x14 +#define MG_FLASH_SIZE_REG_F7 0x1FF0F442 +#define MG_FLASH_SIZE_REG_F4 0x1FFF7A22 + +#define STM_DBGMCU_IDCODE 0xE0042000 +#define STM_DEV_ID (MG_REG(STM_DBGMCU_IDCODE) & (MG_BIT(12) - 1)) +#define SYSCFG_MEMRMP 0x40013800 + +#define MG_FLASH_SIZE_REG_LOCATION \ + ((STM_DEV_ID >= 0x449) ? MG_FLASH_SIZE_REG_F7 : MG_FLASH_SIZE_REG_F4) + +static size_t flash_size(void) { + return (MG_REG(MG_FLASH_SIZE_REG_LOCATION) & 0xFFFF) * 1024; +} + +MG_IRAM static size_t last_sector_size(void) { + return (STM_DEV_ID >= 0x449 ? 256 : 128) * 1024; +} + +MG_IRAM static int is_dualbank(void) { + // only F42x/F43x series (0x419) support dual bank + return STM_DEV_ID == 0x419; +} + +MG_IRAM static void flash_unlock(void) { + static bool unlocked = false; + if (unlocked == false) { + MG_REG(MG_FLASH_BASE + MG_FLASH_KEYR) = 0x45670123; + MG_REG(MG_FLASH_BASE + MG_FLASH_KEYR) = 0xcdef89ab; + unlocked = true; + } +} + +#define MG_FLASH_CONFIG_16_64_128 1 // used by STM32F7 +#define MG_FLASH_CONFIG_32_128_256 2 // used by STM32F4 and F2 + +MG_IRAM static bool flash_page_start(volatile uint32_t *dst) { + char *base = (char *) s_mg_flash_stm32f.start; + char *end = base + s_mg_flash_stm32f.size; + + if (is_dualbank() && dst >= (uint32_t *) (base + (end - base) / 2)) { + dst = (uint32_t *) ((uint32_t) dst - (end - base) / 2); + } + + uint32_t flash_config = MG_FLASH_CONFIG_16_64_128; + if (STM_DEV_ID >= 0x449) { + flash_config = MG_FLASH_CONFIG_32_128_256; + } + + volatile char *p = (char *) dst; + if (p >= base && p < end) { + if (p < base + 16 * 1024 * 4 * flash_config) { + if ((p - base) % (16 * 1024 * flash_config) == 0) return true; + } else if (p == base + 16 * 1024 * 4 * flash_config) { + return true; + } else if ((p - base) % (128 * 1024 * flash_config) == 0) { + return true; + } + } + return false; +} + +MG_IRAM static int flash_sector(volatile uint32_t *addr) { + char *base = (char *) s_mg_flash_stm32f.start; + char *end = base + s_mg_flash_stm32f.size; + bool addr_in_bank_2 = false; + if (is_dualbank() && addr >= (uint32_t *) (base + (end - base) / 2)) { + addr = (uint32_t *) ((uint32_t) addr - (end - base) / 2); + addr_in_bank_2 = true; + } + volatile char *p = (char *) addr; + uint32_t flash_config = MG_FLASH_CONFIG_16_64_128; + if (STM_DEV_ID >= 0x449) { + flash_config = MG_FLASH_CONFIG_32_128_256; + } + int sector = -1; + if (p >= base && p < end) { + if (p < base + 16 * 1024 * 4 * flash_config) { + sector = (p - base) / (16 * 1024 * flash_config); + } else if (p >= base + 64 * 1024 * flash_config && + p < base + 128 * 1024 * flash_config) { + sector = 4; + } else { + sector = (p - base) / (128 * 1024 * flash_config) + 4; + } + } + if (sector == -1) return -1; + if (addr_in_bank_2) sector += 12; // a bank has 12 sectors + return sector; +} + +MG_IRAM static bool flash_is_err(void) { + return MG_REG(MG_FLASH_BASE + MG_FLASH_SR) & ((MG_BIT(7) - 1) << 1); +} + +MG_IRAM static void flash_wait(void) { + while (MG_REG(MG_FLASH_BASE + MG_FLASH_SR) & (MG_BIT(16))) (void) 0; +} + +MG_IRAM static void flash_clear_err(void) { + flash_wait(); // Wait until ready + MG_REG(MG_FLASH_BASE + MG_FLASH_SR) = 0xf2; // Clear all errors +} + +MG_IRAM static bool mg_stm32f_erase(void *addr) { + bool ok = false; + if (flash_page_start(addr) == false) { + MG_ERROR(("%p is not on a sector boundary", addr)); + } else { + int sector = flash_sector(addr); + if (sector < 0) return false; + uint32_t sector_reg = sector; + if (is_dualbank() && sector >= 12) { + // 3.9.8 Flash control register (FLASH_CR) for F42xxx and F43xxx + // BITS[7:3] + sector_reg -= 12; + sector_reg |= MG_BIT(4); + } + flash_unlock(); + flash_wait(); + uint32_t cr = MG_BIT(1); // SER + cr |= MG_BIT(16); // STRT + cr |= (sector_reg & 31) << 3; // sector + MG_REG(MG_FLASH_BASE + MG_FLASH_CR) = cr; + ok = !flash_is_err(); + MG_DEBUG(("Erase sector %lu @ %p %s. CR %#lx SR %#lx", sector, addr, + ok ? "ok" : "fail", MG_REG(MG_FLASH_BASE + MG_FLASH_CR), + MG_REG(MG_FLASH_BASE + MG_FLASH_SR))); + // After we have erased the sector, set CR flags for programming + // 2 << 8 is word write parallelism, bit(0) is PG. RM0385, section 3.7.5 + MG_REG(MG_FLASH_BASE + MG_FLASH_CR) = MG_BIT(0) | (2 << 8); + flash_clear_err(); + } + return ok; +} + +MG_IRAM static void single_bank_swap(char *p1, char *p2, size_t s, size_t ss); + +static bool s_flash_irq_disabled; + +MG_IRAM static bool mg_stm32f_write(void *addr, const void *buf, size_t len) { + if ((len % s_mg_flash_stm32f.align) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, s_mg_flash_stm32f.align)); + 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; + MG_ARM_DISABLE_IRQ(); + flash_unlock(); + flash_clear_err(); + MG_REG(MG_FLASH_BASE + MG_FLASH_CR) = MG_BIT(0) | MG_BIT(9); // PG, 32-bit + flash_wait(); + MG_DEBUG(("Writing flash @ %p, %lu bytes", addr, len)); + while (ok && src < end) { + if (flash_page_start(dst) && mg_stm32f_erase(dst) == false) break; + *(volatile uint32_t *) dst++ = *src++; + MG_DSB(); // ensure flash is written with no errors + flash_wait(); + if (flash_is_err()) ok = false; + } + if (!s_flash_irq_disabled) MG_ARM_ENABLE_IRQ(); + MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst, + ok ? "ok" : "fail", MG_REG(MG_FLASH_BASE + MG_FLASH_CR), + MG_REG(MG_FLASH_BASE + MG_FLASH_SR))); + MG_REG(MG_FLASH_BASE + MG_FLASH_CR) &= ~MG_BIT(0); // Clear programming flag + return ok; +} + +MG_IRAM static bool mg_stm32f_swap(void) { + // STM32 F42x/F43x support dual bank, however, the memory mapping + // change will not be carried through a hard reset. Therefore, we use + // the single-bank scratch-sector swap for this family as well. + size_t ss = last_sector_size(); + char *p1 = (char *) s_mg_flash_stm32f.start; + char *p2; + size_t s; + s_mg_flash_stm32f.size = flash_size(); + p2 = p1 + s_mg_flash_stm32f.size / 2; + s = s_mg_flash_stm32f.size / 2 - ss; + MG_INFO(("Swapping partitions, %u bytes", s)); + MG_INFO(("Do NOT power off...")); + MG_OTA_ROLLBACK_TIMER_FEED(); + mg_log_level = MG_LL_NONE; + s_flash_irq_disabled = true; + single_bank_swap(p1, p2, s, ss); + return true; // unreachable +} + +// True exchange between first half [p1..p1+s) and second half [p2..p2+s), +// using the last sector of the second half as scratch. Runs from RAM and resets. +MG_IRAM static void single_bank_swap(char *p1, char *p2, size_t s, size_t ss) { + char *scratch = p2 + s; + for (size_t ofs = 0; ofs < s; ofs += ss) { + MG_OTA_ROLLBACK_TIMER_FEED(); + mg_stm32f_write(scratch, p1 + ofs, ss); + MG_OTA_ROLLBACK_TIMER_FEED(); + mg_stm32f_write(p1 + ofs, p2 + ofs, ss); + MG_OTA_ROLLBACK_TIMER_FEED(); + mg_stm32f_write(p2 + ofs, scratch, ss); + } + *(volatile uint32_t *) 0xe000ed0cU = 0x5fa0004U; // NVIC_SystemReset() +} + +bool mg_ota_begin(size_t new_firmware_size) { + size_t max; + s_mg_flash_stm32f.size = flash_size(); +#ifdef __ZEPHYR__ + *((uint32_t *)0xE000ED94) = 0; + MG_DEBUG(("Jailbreak %s", *((uint32_t *)0xE000ED94) == 0 ? "successful" : "failed")); +#endif + max = s_mg_flash_stm32f.size / 2 - last_sector_size(); + if (new_firmware_size > max) { + MG_ERROR(("Firmware %lu too big for single-bank OTA, max %lu", + new_firmware_size, max)); + return false; + } + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_stm32f); +} + +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_stm32f); +} + +bool mg_ota_end(void) { + mg_ota_flash_end(&s_mg_flash_stm32f); + return false; +} +struct mg_flash *mg_flash = &s_mg_flash_stm32f; +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/ota_stm32h5.c" +#endif + + + + +#if MG_OTA == MG_OTA_STM32H5 + +static bool mg_stm32h5_write(void *, const void *, size_t); +static bool mg_stm32h5_swap(void); + +static struct mg_flash s_mg_flash_stm32h5 = { + (void *) 0x08000000, // Start + 0, // Size, FLASHSIZE_BASE + 8 * 1024, // Sector size, 8k + 16, // Align, 128 bit + mg_stm32h5_write, + mg_stm32h5_swap, +}; + +#define MG_FLASH_BASE 0x40022000 // Base address of the flash controller +#define FLASH_KEYR (MG_FLASH_BASE + 0x4) // See RM0481 7.11 +#define FLASH_OPTKEYR (MG_FLASH_BASE + 0xc) +#define FLASH_OPTCR (MG_FLASH_BASE + 0x1c) +#define FLASH_NSSR (MG_FLASH_BASE + 0x20) +#define FLASH_NSCR (MG_FLASH_BASE + 0x28) +#define FLASH_NSCCR (MG_FLASH_BASE + 0x30) +#define FLASH_OPTSR_CUR (MG_FLASH_BASE + 0x50) +#define FLASH_OPTSR_PRG (MG_FLASH_BASE + 0x54) +#ifndef FLASHSIZE_BASE +#define FLASHSIZE_BASE 0x08FFF80CUL +#endif + +static size_t flash_size(void) { + uint32_t kb = MG_REG(FLASHSIZE_BASE) & 0xFFFF; + return (kb == 0 || kb == 0xFFFF) ? 2 * 1024 * 1024 : kb * 1024; +} + +static uint32_t sectors_per_bank(void) { + return (uint32_t) (s_mg_flash_stm32h5.size / 2 / s_mg_flash_stm32h5.secsz); +} + +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; + } +} + +static int flash_page_start(volatile uint32_t *dst) { + char *base = (char *) s_mg_flash_stm32h5.start, + *end = base + s_mg_flash_stm32h5.size; + volatile char *p = (char *) dst; + return p >= base && p < end && ((p - base) % s_mg_flash_stm32h5.secsz) == 0; +} + +static bool flash_is_err(void) { + return MG_REG(FLASH_NSSR) & ((MG_BIT(8) - 1) << 17); // RM0481 7.11.9 +} + +static void flash_wait(void) { + while ((MG_REG(FLASH_NSSR) & MG_BIT(0)) && + (MG_REG(FLASH_NSSR) & MG_BIT(16)) == 0) { + (void) 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 bool flash_bank_is_swapped(void) { + return MG_REG(FLASH_OPTCR) & MG_BIT(31); // RM0481 7.11.8 +} + +static bool mg_stm32h5_erase(void *location) { + bool ok = false; + if (flash_page_start(location) == false) { + MG_ERROR(("%p is not on a sector boundary")); + } else { + uintptr_t diff = (char *) location - (char *) s_mg_flash_stm32h5.start; + uint32_t sector = diff / s_mg_flash_stm32h5.secsz; + uint32_t bank_sectors = sectors_per_bank(); + uint32_t saved_cr = MG_REG(FLASH_NSCR); // Save CR value + flash_unlock(); + flash_clear_err(); + MG_REG(FLASH_NSCR) = 0; + if ((sector < bank_sectors && flash_bank_is_swapped()) || + (sector >= bank_sectors && !flash_bank_is_swapped())) { + MG_REG(FLASH_NSCR) |= MG_BIT(31); // Set FLASH_CR_BKSEL + } + if (sector >= bank_sectors) sector -= bank_sectors; + 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 + } + return ok; +} + +static bool mg_stm32h5_swap(void) { + uint32_t desired = flash_bank_is_swapped() ? 0 : MG_BIT(31); + flash_unlock(); + flash_clear_err(); + MG_SET_BITS(MG_REG(FLASH_OPTSR_PRG), MG_BIT(31), desired); + MG_REG(FLASH_OPTCR) |= MG_BIT(1); // OPTSTART; triggers auto-reset on H5 + while ((MG_REG(FLASH_OPTSR_CUR) & MG_BIT(31)) != desired) (void) 0; + return true; +} + +static bool mg_stm32h5_write(void *addr, const void *buf, size_t len) { + if ((len % s_mg_flash_stm32h5.align) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, s_mg_flash_stm32h5.align)); + 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; + MG_ARM_DISABLE_IRQ(); + flash_unlock(); + flash_clear_err(); + MG_REG(FLASH_NSCR) = MG_BIT(1); // Set programming flag + while (ok && src < end) { + if (flash_page_start(dst) && mg_stm32h5_erase(dst) == false) { + ok = false; + break; + } + *(volatile uint32_t *) dst++ = *src++; + flash_wait(); + if (flash_is_err()) ok = false; + } + 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; +} + +bool mg_ota_begin(size_t new_firmware_size) { + s_mg_flash_stm32h5.size = flash_size(); +#ifdef __ZEPHYR__ + *((uint32_t *)0xE000ED94) = 0; + MG_DEBUG(("Jailbreak %s", *((uint32_t *)0xE000ED94) == 0 ? "successful" : "failed")); +#endif + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_stm32h5); +} + +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_stm32h5); +} + +bool mg_ota_end(void) { + if (!mg_ota_flash_end(&s_mg_flash_stm32h5)) return false; + *(volatile uint32_t *) 0xe000ed0c = 0x5fa0004U; // NVIC_SystemReset() + return true; +} + +struct mg_flash *mg_flash = &s_mg_flash_stm32h5; +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/ota_stm32h7.c" +#endif + + + + +#if MG_OTA == MG_OTA_STM32H7 || MG_OTA == MG_OTA_STM32H7_DUAL_CORE + +// - H723/735 RM 4.3.3: Note: The application can simultaneously request a read +// and a write operation through the AXI interface. +// - We only need IRAM for partition swapping in the H723, however, all +// related functions must reside in IRAM for this to be possible. +// - Linker files for other devices won't define a .iram section so there's no +// associated penalty + +static bool mg_stm32h7_write(void *, const void *, size_t); +static bool mg_stm32h7_swap(void); + +static struct mg_flash s_mg_flash_stm32h7 = { + (void *) 0x08000000, // Start + 0, // Size, FLASH_SIZE_REG + 128 * 1024, // Sector size, 128k + 32, // Align, 256 bit + mg_stm32h7_write, + mg_stm32h7_swap, +}; + +#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 + +#define IS_DUALCORE() (MG_OTA == MG_OTA_STM32H7_DUAL_CORE) + +MG_IRAM static size_t flash_size(void) { + size_t size = MG_REG(FLASH_SIZE_REG) * 1024; + return IS_DUALCORE() ? size / 2 : size; +} + +MG_IRAM static bool is_dualbank(void) { + if (IS_DUALCORE()) { + // H745/H755 and H747/H757 are running on dual core. + // Using only the 1st bank (mapped to CM7), in order not to interfere + // with the 2nd bank (CM4), possibly causing CM4 to boot unexpectedly. + return false; + } + return (s_mg_flash_stm32h7.size < 2 * 1024 * 1024) ? false : true; +} + +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 (is_dualbank()) { + 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; + } +} + +MG_IRAM static bool flash_page_start(volatile uint32_t *dst) { + char *base = (char *) s_mg_flash_stm32h7.start, + *end = base + s_mg_flash_stm32h7.size; + volatile char *p = (char *) dst; + return p >= base && p < end && ((p - base) % s_mg_flash_stm32h7.secsz) == 0; +} + +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 +} + +MG_IRAM static void flash_wait(uint32_t bank) { + while (MG_REG(bank + FLASH_SR) & (MG_BIT(0) | MG_BIT(2))) (void) 0; +} + +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 +} + +MG_IRAM static bool flash_bank_is_swapped(uint32_t bank) { + return MG_REG(bank + FLASH_OPTCR) & MG_BIT(31); // RM0433 4.9.7 +} + +// Figure out flash bank based on the address +MG_IRAM static uint32_t flash_bank(void *addr) { + size_t ofs = (char *) addr - (char *) s_mg_flash_stm32h7.start; + if (!is_dualbank()) return FLASH_BASE1; + return ofs < s_mg_flash_stm32h7.size / 2 ? FLASH_BASE1 : FLASH_BASE2; +} + +// read-while-write, no need to disable IRQs for standalone usage +MG_IRAM static bool mg_stm32h7_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 *) s_mg_flash_stm32h7.start; + uint32_t sector = diff / s_mg_flash_stm32h7.secsz; + 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 + flash_wait(bank); + 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; +} + +MG_IRAM static void single_bank_swap(char *p1, char *p2, size_t s, size_t ss); + +static bool s_flash_irq_disabled; + +MG_IRAM static bool mg_stm32h7_swap(void) { + s_mg_flash_stm32h7.size = flash_size(); + if (!is_dualbank()) { + // True sector-by-sector swap using the last sector of the second half as + // scratch. Resets inside single_bank_swap, never returns. + size_t ss = s_mg_flash_stm32h7.secsz; + char *p1 = (char *) s_mg_flash_stm32h7.start; + char *p2 = p1 + s_mg_flash_stm32h7.size / 2; + size_t s = s_mg_flash_stm32h7.size / 2 - ss; + MG_INFO(("Swapping partitions, %u bytes (%u sectors)", s, s / ss)); + MG_INFO(("Do NOT power off...")); + MG_OTA_ROLLBACK_TIMER_FEED(); + mg_log_level = MG_LL_NONE; + s_flash_irq_disabled = true; + single_bank_swap(p1, p2, s, ss); + return true; // unreachable + } + uint32_t bank = FLASH_BASE1; + uint32_t desired = flash_bank_is_swapped(bank) ? 0 : MG_BIT(31); + flash_unlock(); + flash_clear_err(bank); + MG_SET_BITS(MG_REG(bank + FLASH_OPTSR_PRG), MG_BIT(31), desired); + MG_REG(bank + FLASH_OPTCR) |= MG_BIT(1); // OPTSTART + while ((MG_REG(bank + FLASH_OPTSR_CUR) & MG_BIT(31)) != desired) (void) 0; + return true; +} + +MG_IRAM static bool mg_stm32h7_write(void *addr, const void *buf, size_t len) { + if ((len % s_mg_flash_stm32h7.align) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, s_mg_flash_stm32h7.align)); + return false; + } + 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; + MG_ARM_DISABLE_IRQ(); + 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 + while (ok && src < end) { + if (flash_page_start(dst) && mg_stm32h7_erase(dst) == false) { + ok = false; + break; + } + *(volatile uint32_t *) dst++ = *src++; + flash_wait(bank); + if (flash_is_err(bank)) ok = false; + } + if (!s_flash_irq_disabled) MG_ARM_ENABLE_IRQ(); + MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, addr, + 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; +} + +// True sector-by-sector exchange between first half [p1..p1+s) and second half +// [p2..p2+s), using the sector at p2+s as scratch. Runs from RAM. Resets on +// completion. Symmetric: calling twice returns flash to original state. +MG_IRAM static void single_bank_swap(char *p1, char *p2, size_t s, size_t ss) { + char *scratch = p2 + s; // Last sector of second half, reserved as scratch + for (size_t ofs = 0; ofs < s; ofs += ss) { + MG_OTA_ROLLBACK_TIMER_FEED(); + mg_stm32h7_write(scratch, p1 + ofs, ss); // Save p1[i] to scratch + MG_OTA_ROLLBACK_TIMER_FEED(); + mg_stm32h7_write(p1 + ofs, p2 + ofs, ss); // Copy p2[i] to p1[i] + MG_OTA_ROLLBACK_TIMER_FEED(); + mg_stm32h7_write(p2 + ofs, scratch, ss); // Copy scratch to p2[i] + } + *(volatile uint32_t *) 0xe000ed0cU = 0x5fa0004U; // NVIC_SystemReset() +} + +bool mg_ota_begin(size_t new_firmware_size) { + s_mg_flash_stm32h7.size = flash_size(); +#ifdef __ZEPHYR__ + *((uint32_t *) 0xE000ED94) = 0; + MG_DEBUG(("Jailbreak %s", *((uint32_t *) 0xE000ED94) == 0 ? "ok" : "failed")); +#endif + if (!is_dualbank()) { + // Last sector of the second half is reserved as swap scratch; enforce limit. + size_t max = s_mg_flash_stm32h7.size / 2 - s_mg_flash_stm32h7.secsz; + if (new_firmware_size > max) { + MG_ERROR(("Firmware %lu too big for single-bank OTA, max %lu", new_firmware_size, max)); + return false; + } + } + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_stm32h7); +} + +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_stm32h7); +} + +bool mg_ota_end(void) { + if (mg_ota_flash_end(&s_mg_flash_stm32h7)) { + if (is_dualbank()) { + // Bank swap is deferred until reset, been executing in flash, reset + *(volatile uint32_t *) 0xe000ed0cU = 0x5fa0004U; // NVIC_SystemReset() + } + } + return false; +} + +struct mg_flash *mg_flash = &s_mg_flash_stm32h7; +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/printf.c" +#endif + + + + +size_t mg_queue_printf(struct mg_queue *q, const char *fmt, ...) { + char *buf; + size_t len; + va_list ap1, ap2; + va_start(ap1, fmt); + len = mg_vsnprintf(NULL, 0, fmt, &ap1); + va_end(ap1); + if (len == 0 || mg_queue_book(q, &buf, len + 1) < len + 1) + return 0; // Nah. Not enough space + va_start(ap2, fmt); + len = mg_vsnprintf(buf, len + 1, fmt, &ap2); + mg_queue_add(q, len); + va_end(ap2); + 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 + } +} + +void mg_pfn_iobuf_noresize(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 = {0, 0, 0, 0}; + size_t n; + io.buf = (uint8_t *) buf, io.size = len; + n = mg_vxprintf(mg_pfn_iobuf_noresize, &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->addr.ip); + return print_ip4(out, arg, (uint8_t *) &addr->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)); +} + +static size_t print_mac(void (*out)(char, void *), void *arg, uint8_t *p) { + return mg_xprintf(out, arg, "%02x:%02x:%02x:%02x:%02x:%02x", p[0], p[1], p[2], + p[3], p[4], p[5]); +} + +size_t mg_print_mac(void (*out)(char, void *), void *arg, va_list *ap) { + uint8_t *p = va_arg(*ap, uint8_t *); + return print_mac(out, arg, p); +} + +static size_t print_ieee64(void (*out)(char, void *), void *arg, uint8_t *p) { + return mg_xprintf(out, arg, "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", p[0], + p[1], p[2], p[3], p[4], p[5], p[6], p[7]); +} + +size_t mg_print_ieee64(void (*out)(char, void *), void *arg, va_list *ap) { + uint8_t *p = va_arg(*ap, uint8_t *); + return print_ieee64(out, arg, p); +} + +#if MG_ENABLE_TCPIP +size_t mg_print_l2addr(void (*out)(char, void *), void *arg, va_list *ap) { + enum mg_l2type type = (enum mg_l2type) va_arg(*ap, int); + switch (type) { + case MG_TCPIP_L2_ETH: + case MG_TCPIP_L2_PPPoE: { + uint8_t *p = va_arg(*ap, uint8_t *); + return print_mac(out, arg, p); + } break; + default: break; + } + return 0; +} +#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]; + } + 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] = {0, 0, '=', '='}; + tmp[0] = t[c1 >> 2], tmp[1] = 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); +} + +size_t mg_print_html_esc(void (*out)(char, void *), void *arg, va_list *ap) { + size_t i, n = 0; + int len = va_arg(*ap, int); + const char *s = va_arg(*ap, const char *); + for (i = 0; i < (size_t) len; i++) { + const char *esc = NULL; + switch (s[i]) { + // clang-format off + case '&': esc = "&"; break; + case '<': esc = "<"; break; + case '>': esc = ">"; break; + case '"': esc = """; break; + default: break; + // clang-format on + } + if (esc != NULL) { + while (*esc != '\0') { + out(*esc++, arg); + n++; + } + } else { + out(s[i], arg); + n++; + } + } + return n; +} + +#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 *) mg_calloc(1, sizeof(*rpc)); + if (rpc != NULL) { + rpc->method = mg_strdup(method); + rpc->fn = fn; + rpc->fn_data = fn_data; + rpc->next = *head, *head = rpc; + } +} + +void mg_rpc_del(struct mg_rpc **head, void (*fn)(struct mg_rpc_req *)) { + struct mg_rpc *r; + while ((r = *head) != NULL) { + if (r->fn == fn || fn == NULL) { + *head = r->next; + mg_free((void *) r->method.buf); + mg_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 */ +/* 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_sha256(uint8_t dst[32], uint8_t *data, size_t datasz) { + mg_sha256_ctx ctx; + mg_sha256_init(&ctx); + mg_sha256_update(&ctx, data, datasz); + mg_sha256_final(dst, &ctx); +} + +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); +} + +#define rotr64(x, n) (((x) >> (n)) | ((x) << (64 - (n)))) +#define ep064(x) (rotr64(x, 28) ^ rotr64(x, 34) ^ rotr64(x, 39)) +#define ep164(x) (rotr64(x, 14) ^ rotr64(x, 18) ^ rotr64(x, 41)) +#define sig064(x) (rotr64(x, 1) ^ rotr64(x, 8) ^ ((x) >> 7)) +#define sig164(x) (rotr64(x, 19) ^ rotr64(x, 61) ^ ((x) >> 6)) + +static const uint64_t mg_sha256_k2[80] = { +#if defined(__DCC__) + 0x428a2f98d728ae22ull, 0x7137449123ef65cdull, 0xb5c0fbcfec4d3b2full, + 0xe9b5dba58189dbbcull, 0x3956c25bf348b538ull, 0x59f111f1b605d019ull, + 0x923f82a4af194f9bull, 0xab1c5ed5da6d8118ull, 0xd807aa98a3030242ull, + 0x12835b0145706fbeull, 0x243185be4ee4b28cull, 0x550c7dc3d5ffb4e2ull, + 0x72be5d74f27b896full, 0x80deb1fe3b1696b1ull, 0x9bdc06a725c71235ull, + 0xc19bf174cf692694ull, 0xe49b69c19ef14ad2ull, 0xefbe4786384f25e3ull, + 0x0fc19dc68b8cd5b5ull, 0x240ca1cc77ac9c65ull, 0x2de92c6f592b0275ull, + 0x4a7484aa6ea6e483ull, 0x5cb0a9dcbd41fbd4ull, 0x76f988da831153b5ull, + 0x983e5152ee66dfabull, 0xa831c66d2db43210ull, 0xb00327c898fb213full, + 0xbf597fc7beef0ee4ull, 0xc6e00bf33da88fc2ull, 0xd5a79147930aa725ull, + 0x06ca6351e003826full, 0x142929670a0e6e70ull, 0x27b70a8546d22ffcull, + 0x2e1b21385c26c926ull, 0x4d2c6dfc5ac42aedull, 0x53380d139d95b3dfull, + 0x650a73548baf63deull, 0x766a0abb3c77b2a8ull, 0x81c2c92e47edaee6ull, + 0x92722c851482353bull, 0xa2bfe8a14cf10364ull, 0xa81a664bbc423001ull, + 0xc24b8b70d0f89791ull, 0xc76c51a30654be30ull, 0xd192e819d6ef5218ull, + 0xd69906245565a910ull, 0xf40e35855771202aull, 0x106aa07032bbd1b8ull, + 0x19a4c116b8d2d0c8ull, 0x1e376c085141ab53ull, 0x2748774cdf8eeb99ull, + 0x34b0bcb5e19b48a8ull, 0x391c0cb3c5c95a63ull, 0x4ed8aa4ae3418acbull, + 0x5b9cca4f7763e373ull, 0x682e6ff3d6b2b8a3ull, 0x748f82ee5defb2fcull, + 0x78a5636f43172f60ull, 0x84c87814a1f0ab72ull, 0x8cc702081a6439ecull, + 0x90befffa23631e28ull, 0xa4506cebde82bde9ull, 0xbef9a3f7b2c67915ull, + 0xc67178f2e372532bull, 0xca273eceea26619cull, 0xd186b8c721c0c207ull, + 0xeada7dd6cde0eb1eull, 0xf57d4f7fee6ed178ull, 0x06f067aa72176fbaull, + 0x0a637dc5a2c898a6ull, 0x113f9804bef90daeull, 0x1b710b35131c471bull, + 0x28db77f523047d84ull, 0x32caab7b40c72493ull, 0x3c9ebe0a15c9bebcull, + 0x431d67c49c100d4cull, 0x4cc5d4becb3e42b6ull, 0x597f299cfc657e2aull, + 0x5fcb6fab3ad6faecull, 0x6c44198c4a475817ull +#else + 0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, + 0xe9b5dba58189dbbc, 0x3956c25bf348b538, 0x59f111f1b605d019, + 0x923f82a4af194f9b, 0xab1c5ed5da6d8118, 0xd807aa98a3030242, + 0x12835b0145706fbe, 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2, + 0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, + 0xc19bf174cf692694, 0xe49b69c19ef14ad2, 0xefbe4786384f25e3, + 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65, 0x2de92c6f592b0275, + 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5, + 0x983e5152ee66dfab, 0xa831c66d2db43210, 0xb00327c898fb213f, + 0xbf597fc7beef0ee4, 0xc6e00bf33da88fc2, 0xd5a79147930aa725, + 0x06ca6351e003826f, 0x142929670a0e6e70, 0x27b70a8546d22ffc, + 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df, + 0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, + 0x92722c851482353b, 0xa2bfe8a14cf10364, 0xa81a664bbc423001, + 0xc24b8b70d0f89791, 0xc76c51a30654be30, 0xd192e819d6ef5218, + 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8, + 0x19a4c116b8d2d0c8, 0x1e376c085141ab53, 0x2748774cdf8eeb99, + 0x34b0bcb5e19b48a8, 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, + 0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3, 0x748f82ee5defb2fc, + 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec, + 0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, + 0xc67178f2e372532b, 0xca273eceea26619c, 0xd186b8c721c0c207, + 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178, 0x06f067aa72176fba, + 0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b, + 0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, + 0x431d67c49c100d4c, 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, + 0x5fcb6fab3ad6faec, 0x6c44198c4a475817 +#endif +}; + +static void mg_sha384_transform(mg_sha384_ctx *ctx, const uint8_t data[]) { + uint64_t m[80]; + uint64_t a, b, c, d, e, f, g, h; + int i, j; + + for (i = 0, j = 0; i < 16; ++i, j += 8) + m[i] = ((uint64_t) data[j] << 56) | ((uint64_t) data[j + 1] << 48) | + ((uint64_t) data[j + 2] << 40) | ((uint64_t) data[j + 3] << 32) | + ((uint64_t) data[j + 4] << 24) | ((uint64_t) data[j + 5] << 16) | + ((uint64_t) data[j + 6] << 8) | ((uint64_t) data[j + 7]); + for (; i < 80; ++i) + m[i] = sig164(m[i - 2]) + m[i - 7] + sig064(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 < 80; ++i) { + uint64_t t1 = h + ep164(e) + ch(e, f, g) + mg_sha256_k2[i] + m[i]; + uint64_t t2 = ep064(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_sha384_init(mg_sha384_ctx *ctx) { + ctx->datalen = 0; + ctx->bitlen[0] = 0; + ctx->bitlen[1] = 0; +#if defined(__DCC__) + ctx->state[0] = 0xcbbb9d5dc1059ed8ull; + ctx->state[1] = 0x629a292a367cd507ull; + ctx->state[2] = 0x9159015a3070dd17ull; + ctx->state[3] = 0x152fecd8f70e5939ull; + ctx->state[4] = 0x67332667ffc00b31ull; + ctx->state[5] = 0x8eb44a8768581511ull; + ctx->state[6] = 0xdb0c2e0d64f98fa7ull; + ctx->state[7] = 0x47b5481dbefa4fa4ull; +#else + ctx->state[0] = 0xcbbb9d5dc1059ed8; + ctx->state[1] = 0x629a292a367cd507; + ctx->state[2] = 0x9159015a3070dd17; + ctx->state[3] = 0x152fecd8f70e5939; + ctx->state[4] = 0x67332667ffc00b31; + ctx->state[5] = 0x8eb44a8768581511; + ctx->state[6] = 0xdb0c2e0d64f98fa7; + ctx->state[7] = 0x47b5481dbefa4fa4; +#endif +} + +void mg_sha384_update(mg_sha384_ctx *ctx, const uint8_t *data, size_t len) { + size_t i; + for (i = 0; i < len; ++i) { + ctx->buffer[ctx->datalen] = data[i]; + ctx->datalen++; + if (ctx->datalen == 128) { + mg_sha384_transform(ctx, ctx->buffer); + ctx->bitlen[1] += 1024; + if (ctx->bitlen[1] < 1024) ctx->bitlen[0]++; + ctx->datalen = 0; + } + } +} + +void mg_sha384_final(uint8_t hash[48], mg_sha384_ctx *ctx) { + size_t i = ctx->datalen; + + if (ctx->datalen < 112) { + ctx->buffer[i++] = 0x80; + while (i < 112) ctx->buffer[i++] = 0x00; + } else { + ctx->buffer[i++] = 0x80; + while (i < 128) ctx->buffer[i++] = 0x00; + mg_sha384_transform(ctx, ctx->buffer); + memset(ctx->buffer, 0, 112); + } + + ctx->bitlen[1] += ctx->datalen * 8; + if (ctx->bitlen[1] < ctx->datalen * 8) ctx->bitlen[0]++; + ctx->buffer[127] = (uint8_t) (ctx->bitlen[1]); + ctx->buffer[126] = (uint8_t) (ctx->bitlen[1] >> 8); + ctx->buffer[125] = (uint8_t) (ctx->bitlen[1] >> 16); + ctx->buffer[124] = (uint8_t) (ctx->bitlen[1] >> 24); + ctx->buffer[123] = (uint8_t) (ctx->bitlen[1] >> 32); + ctx->buffer[122] = (uint8_t) (ctx->bitlen[1] >> 40); + ctx->buffer[121] = (uint8_t) (ctx->bitlen[1] >> 48); + ctx->buffer[120] = (uint8_t) (ctx->bitlen[1] >> 56); + ctx->buffer[119] = (uint8_t) (ctx->bitlen[0]); + ctx->buffer[118] = (uint8_t) (ctx->bitlen[0] >> 8); + ctx->buffer[117] = (uint8_t) (ctx->bitlen[0] >> 16); + ctx->buffer[116] = (uint8_t) (ctx->bitlen[0] >> 24); + ctx->buffer[115] = (uint8_t) (ctx->bitlen[0] >> 32); + ctx->buffer[114] = (uint8_t) (ctx->bitlen[0] >> 40); + ctx->buffer[113] = (uint8_t) (ctx->bitlen[0] >> 48); + ctx->buffer[112] = (uint8_t) (ctx->bitlen[0] >> 56); + mg_sha384_transform(ctx, ctx->buffer); + + for (i = 0; i < 6; ++i) { + hash[i * 8] = (uint8_t) ((ctx->state[i] >> 56) & 0xff); + hash[i * 8 + 1] = (uint8_t) ((ctx->state[i] >> 48) & 0xff); + hash[i * 8 + 2] = (uint8_t) ((ctx->state[i] >> 40) & 0xff); + hash[i * 8 + 3] = (uint8_t) ((ctx->state[i] >> 32) & 0xff); + hash[i * 8 + 4] = (uint8_t) ((ctx->state[i] >> 24) & 0xff); + hash[i * 8 + 5] = (uint8_t) ((ctx->state[i] >> 16) & 0xff); + hash[i * 8 + 6] = (uint8_t) ((ctx->state[i] >> 8) & 0xff); + hash[i * 8 + 7] = (uint8_t) (ctx->state[i] & 0xff); + } +} + +void mg_sha384(uint8_t dst[48], uint8_t *data, size_t datasz) { + mg_sha384_ctx ctx; + mg_sha384_init(&ctx); + mg_sha384_update(&ctx, data, datasz); + mg_sha384_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 + +uint64_t mg_boot_timestamp_ms = 0; // Updated by SNTP + +uint64_t mg_now(void) { + return mg_millis() + mg_boot_timestamp_ms; +} + +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 epoch_milliseconds = -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 origin_time = gettimestamp((uint32_t *) &buf[24]); + int64_t receive_time = gettimestamp((uint32_t *) &buf[32]); + int64_t transmit_time = gettimestamp((uint32_t *) &buf[40]); + int64_t now = (int64_t) mg_millis(); + int64_t latency = (now - origin_time) - (transmit_time - receive_time); + epoch_milliseconds = transmit_time + latency / 2; + mg_boot_timestamp_ms = (uint64_t) (epoch_milliseconds - now); + } else { + MG_ERROR(("unexpected version: %d", version)); + } + return epoch_milliseconds; +} + +static void sntp_cb(struct mg_connection *c, int ev, void *ev_data) { + uint64_t *expiration_time = (uint64_t *) c->data; + if (ev == MG_EV_OPEN) { + *expiration_time = mg_millis() + 3000; // Store expiration time in 3s + } else if (ev == MG_EV_CONNECT) { + mg_sntp_request(c); + } else if (ev == MG_EV_READ) { + int64_t milliseconds = mg_sntp_parse(c->recv.buf, c->recv.len); + if (milliseconds > 0) { + mg_boot_timestamp_ms = (uint64_t) milliseconds - mg_millis(); + mg_call(c, MG_EV_SNTP_TIME, (uint64_t *) &milliseconds); + MG_DEBUG(("%lu got time: %lld ms from epoch", c->id, milliseconds)); + } + // mg_iobuf_del(&c->recv, 0, c->recv.len); // Free receive buffer + c->is_closing = 1; + } else if (ev == MG_EV_POLL) { + if (mg_millis() > *expiration_time) c->is_closing = 1; + } 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 *fn_data) { + if (url == NULL) url = "udp://time.google.com:123"; + return mg_connect_svc(mgr, url, fn, fn_data, sntp_cb, NULL); +} + + +#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->addr.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->addr.ip, sizeof(a->addr.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; +#if MG_ENABLE_IPV6 + if (is_ip6) { + memcpy(a->addr.ip, &usa->sin6.sin6_addr, sizeof(a->addr.ip)); + a->port = usa->sin6.sin6_port; + a->scope_id = (uint8_t) usa->sin6.sin6_scope_id; + } else +#endif + { + a->port = usa->sin.sin_port; + memcpy(&a->addr.ip, &usa->sin.sin_addr, sizeof(uint32_t)); + } +} + +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)); + } +} + +// Get the local 'addr' the stack will use to connect to 'to' +void mg_getlocaddr(struct mg_connection *c, struct mg_addr *to, + struct mg_addr *addr); +void mg_getlocaddr(struct mg_connection *c, struct mg_addr *to, + struct mg_addr *addr) { + union usa usa; + socklen_t slen; + MG_SOCKET_TYPE fd; + int rc, af = to->is_ip6 ? AF_INET6 : AF_INET; + fd = socket(af, SOCK_DGRAM, IPPROTO_UDP); + if (fd == MG_INVALID_SOCKET) { + mg_error(c, "socket(): %d", MG_SOCK_ERR(-1)); + return; + } + // NOTE(): TI-RTOS NDK may require binding + slen = tousa(to, &usa); + if ((rc = connect(fd, &usa.sa, slen)) != 0) { + mg_error(c, "connect: %d", MG_SOCK_ERR(rc)); + } else { + setlocaddr(fd, addr); + } + closesocket(fd); +} + +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); + } + } +} + +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); + } + 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_ERR; // See #1507, #3031 + 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 %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 { + return len == 0 || mg_iobuf_add(&c->send, c->send.len, buf, len) > 0; + // returning 0 means an OOM condition (iobuf couldn't resize), yet this is + // so far recoverable, let the caller decide + } +} + +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_THREADX + // NetxDuo fails to send large blocks of data to the non-blocking sockets + (void) fd; + // 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 +} + +void mg_multicast_add(struct mg_connection *c, char *ip); +void mg_multicast_add(struct mg_connection *c, char *ip) { +#if MG_ENABLE_RL + MG_ERROR(("unsupported")); +#elif MG_ENABLE_FREERTOS_TCP + // TODO(): prvAllowIPPacketIPv4() +#else + // lwIP, Unix, Windows, Zephyr 4+(, AzureRTOS ?) +#if MG_ENABLE_LWIP && !LWIP_IGMP + MG_ERROR(("LWIP_IGMP not defined, no multicast support")); +#else +#if defined(__ZEPHYR__) && ZEPHYR_VERSION_CODE < 0x40000 + MG_ERROR(("struct ip_mreq not defined")); +#else + struct ip_mreq mreq; + mreq.imr_multiaddr.s_addr = inet_addr(ip); + mreq.imr_interface.s_addr = mg_htonl(INADDR_ANY); + setsockopt(FD(c), IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *) &mreq, + sizeof(mreq)); +#endif // !Zephyr +#endif // !lwIP +#endif +} + +bool mg_open_listener(struct mg_connection *c, const char *url) { + MG_SOCKET_TYPE fd = MG_INVALID_SOCKET; + bool success = false; + c->loc.port = mg_htons(mg_url_port(url)); + if (!mg_aton(mg_url_host(url), &c->loc)) { + MG_ERROR(("invalid listening URL: %s", url)); + } else { + union usa usa; + socklen_t slen = tousa(&c->loc, &usa); + int rc, on = 1, af = c->loc.is_ip6 ? AF_INET6 : AF_INET; + int type = strncmp(url, "udp:", 4) == 0 ? SOCK_DGRAM : SOCK_STREAM; + int proto = type == SOCK_DGRAM ? IPPROTO_UDP : IPPROTO_TCP; + (void) on; + + if ((fd = socket(af, type, proto)) == MG_INVALID_SOCKET) { + MG_ERROR(("socket: %d", MG_SOCK_ERR(-1))); +#if defined(SO_EXCLUSIVEADDRUSE) + } else if ((rc = setsockopt(fd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, + (char *) &on, sizeof(on))) != 0) { + // "Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE" + MG_ERROR(("setsockopt(SO_EXCLUSIVEADDRUSE): %d %d", on, MG_SOCK_ERR(rc))); +#elif defined(SO_REUSEADDR) && (!defined(LWIP_SOCKET) || SO_REUSE) + } else if ((rc = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, + sizeof(on))) != 0) { + // 1. SO_REUSEADDR semantics on UNIX and Windows is different. On + // Windows, SO_REUSEADDR allows to bind a socket to a port without error + // even if the port is already open by another program. This is not the + // behavior SO_REUSEADDR was designed for, and leads to hard-to-track + // failure scenarios. + // + // 2. For LWIP, SO_REUSEADDR should be explicitly enabled by defining + // SO_REUSE = 1 in lwipopts.h, otherwise the code below will compile but + // won't work! (setsockopt will return EINVAL) + MG_ERROR(("setsockopt(SO_REUSEADDR): %d", MG_SOCK_ERR(rc))); +#endif +#if 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 { + 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; +} + +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 { + 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_ERR; // See #1507, #3031 + if (n <= 0) return MG_IO_ERR; + return n; +} + +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; +} + +// 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 = MG_IO_WAIT; + if (ioalloc(c, &c->recv) == false) return; // Oopsie poopsie, OOM + if (c->is_tls) { + size_t rtls = c->rtls.len, pending = mg_tls_pending(c); + long m = MG_IO_WAIT; + if (c->rtls.len < 16 * 1024 + 40) { // TLS record, header, MAC, padding + 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 > 0) c->rtls.len += (size_t) n; + } + // There can still be > 16K from last iteration, always mg_tls_recv() + if (c->is_tls_hs) { + if (n != MG_IO_ERR || c->rtls.len > 0 || pending > 0) { + mg_tls_handshake(c); + } + } else { + m = mg_tls_recv(c, &c->recv.buf[c->recv.len], c->recv.size - c->recv.len); + } + if (n == MG_IO_ERR && + (m == MG_IO_ERR || (c->rtls.len == 0 && mg_tls_pending(c) == 0) || + (c->is_tls_hs && c->rtls.len == rtls && + mg_tls_pending(c) == pending))) { + // Close only when we have fully drained both rtls and TLS buffers + c->is_closing = 1; // or there's nothing we can do about it. + if (m < 0) m = MG_IO_ERR; // but return last record data, see #3104 + } + n = m; + } else { + n = recv_raw(c, &c->recv.buf[c->recv.len], c->recv.size - c->recv.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 *) &c->recv.buf[c->recv.len], n, true); +} + +static void write_conn(struct mg_connection *c) { + char *buf = (char *) c->send.buf; + size_t len = c->send.len; + long n = c->is_tls ? mg_tls_send(c, buf, len) : mg_io_send(c, buf, len); + // TODO(): mg_tls_send() may return 0 forever on steady OOM + 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); +} + +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); + if (!c->is_tls_hs) c->is_tls = 0; // user did not call mg_tls_init() + } else { + mg_error(c, "socket error"); + } +} + +static void setsockopts(struct mg_connection *c) { +#if MG_ENABLE_FREERTOS_TCP || MG_ARCH == MG_ARCH_THREADX || \ + 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 proto = type == SOCK_DGRAM ? IPPROTO_UDP : IPPROTO_TCP; + int rc, af = c->rem.is_ip6 ? AF_INET6 : AF_INET; // c->rem has resolved IP + c->fd = S2PTR(socket(af, type, proto)); // 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 + if (!c->is_tls_hs) c->is_tls = 0; // user did not call mg_tls_init() + } 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 { + mg_error(c, "connect: %d", MG_SOCK_ERR(rc)); + } + } +} + +static MG_SOCKET_TYPE raccept(MG_SOCKET_TYPE sock, union usa *usa, + socklen_t *len) { + MG_SOCKET_TYPE fd = MG_INVALID_SOCKET; + do { + memset(usa, 0, sizeof(*usa)); + fd = accept(sock, &usa->sa, len); + } while (MG_SOCK_INTR(fd)); + return fd; +} + +static void accept_conn(struct mg_mgr *mgr, struct mg_connection *lsn) { + struct mg_connection *c = NULL; + union usa usa; + socklen_t sa_len = sizeof(usa); + MG_SOCKET_TYPE fd = raccept(FD(lsn), &usa, &sa_len); + if (fd == MG_INVALID_SOCKET) { +#if MG_ARCH == MG_ARCH_THREADX || defined(__ECOS) + // NetxDuo, in non-block socket mode can mark listening socket readable + // even it is not. See comment for 'select' func implementation in + // nx_bsd.c That's not an error, just should try later + if (errno != EAGAIN) +#endif + MG_ERROR(("%lu accept failed, errno %d", lsn->id, MG_SOCK_ERR(-1))); +#if (MG_ARCH != MG_ARCH_WIN32) && !MG_ENABLE_FREERTOS_TCP && \ + (MG_ARCH != MG_ARCH_TIRTOS) && !MG_ENABLE_POLL && !MG_ENABLE_EPOLL + } else if ((long) fd >= FD_SETSIZE) { + MG_ERROR(("%ld > %ld", (long) fd, (long) FD_SETSIZE)); + closesocket(fd); +#endif + } else if ((c = mg_alloc_conn(mgr)) == NULL) { + MG_ERROR(("%lu OOM", lsn->id)); + closesocket(fd); + } else { + tomgaddr(&usa, &c->rem, sa_len != sizeof(usa.sin)); + LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); + c->fd = S2PTR(fd); + MG_EPOLL_ADD(c); + mg_set_non_blocking_mode(FD(c)); + setsockopts(c); + c->is_accepted = 1; + c->is_hexdumping = lsn->is_hexdumping; + setlocaddr(fd, &c->loc); // set local addr to where the client connected to + c->pfn = lsn->pfn; + c->pfn_data = lsn->pfn_data; + c->fn = lsn->fn; + c->fn_data = lsn->fn_data; + c->is_tls = lsn->is_tls; + 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); + if (!c->is_tls_hs) c->is_tls = 0; // user did not call mg_tls_init() + } +} + +static bool can_read(const struct mg_connection *c) { + return c->is_full == false; +} + +static bool can_write(const struct mg_connection *c) { + return c->is_connecting || (c->send.len > 0 && c->is_tls_hs == 0); +} + +static bool skip_iotest(const struct mg_connection *c) { + return (c->is_closing || c->is_resolving || FD(c) == MG_INVALID_SOCKET) || + (can_read(c) == false && can_write(c) == false); +} + +static void mg_iotest(struct mg_mgr *mgr, int ms) { +#if MG_ENABLE_FREERTOS_TCP + struct mg_connection *c; + for (c = mgr->conns; c != NULL; c = c->next) { + c->is_readable = c->is_writable = 0; + if (skip_iotest(c)) continue; + if (can_read(c)) + FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_READ | eSELECT_EXCEPT); + if (can_write(c)) FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_WRITE); + if (c->is_closing) ms = 1; + } + 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 (c->is_closing) ms = 1; + if (skip_iotest(c)) { + // Socket not valid, ignore + } else { + // Don't wait if TLS is ready + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) ms = 1; + fds[n].fd = FD(c); + if (can_read(c)) fds[n].events |= POLLIN; + if (can_write(c)) fds[n].events |= POLLOUT; + n++; + } + } + + // MG_INFO(("poll n=%d ms=%d", (int) n, ms)); + if (poll(fds, n, ms) < 0) { +#if MG_ARCH == MG_ARCH_WIN32 + if (n == 0) Sleep(ms); // On Windows, poll fails if no sockets +#endif + memset(fds, 0, n * sizeof(fds[0])); + } + n = 0; + for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) { + if (skip_iotest(c)) { + // Socket not valid, ignore + } else { + if (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_1ms = {0, 1000}, *tvp; + struct mg_connection *c; + fd_set rset, wset, eset; + MG_SOCKET_TYPE maxfd = 0; + int rc; + + FD_ZERO(&rset); + FD_ZERO(&wset); + FD_ZERO(&eset); + tvp = ms < 0 ? NULL : &tv; + for (c = mgr->conns; c != NULL; c = c->next) { + c->is_readable = c->is_writable = 0; + if (skip_iotest(c)) continue; + FD_SET(FD(c), &eset); + if (can_read(c)) FD_SET(FD(c), &rset); + if (can_write(c)) FD_SET(FD(c), &wset); + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) tvp = &tv_1ms; + if (FD(c) > maxfd) maxfd = FD(c); + if (c->is_closing) tvp = &tv_1ms; + } + + 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 + if (rc < 0) MG_ERROR(("select: %d %d", rc, MG_SOCK_ERR(rc))); +#endif + FD_ZERO(&rset); + FD_ZERO(&wset); + FD_ZERO(&eset); + } + + for (c = mgr->conns; c != NULL; c = c->next) { + if (FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &eset)) { +#if MG_ARCH == MG_ARCH_THREADX + // NetxDuo stack returns exceptions for listening connection after accept + if (c->is_listening == 0) mg_error(c, "socket error"); +#else + mg_error(c, "socket error"); +#endif + } 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]; + + if ((sp[0] = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) != MG_INVALID_SOCKET && + (sp[1] = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) != 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; +} + +// 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.fd); // When we're closing, close the other + c->mgr->pipe.fd = MG_INVALID_SOCKET; // side of the socketpair, too + } + (void) ev_data; +} + +bool mg_wakeup_init(struct mg_mgr *mgr) { + bool ok = false; + if (mgr->pipe.fd == 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 %ld pipe %ld", c->id, c->fd, (long) sp[0])); + mgr->pipe.fd = sp[0]; + ok = true; + } + } + return ok; +} + +bool mg_wakeup(struct mg_mgr *mgr, unsigned long conn_id, const void *buf, + size_t len) { + if (mgr->pipe.fd != 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.fd, extended_buf, len + sizeof(conn_id), MSG_NONBLOCKING); + return true; + } + return false; +} + +void mg_mgr_poll(struct mg_mgr *mgr, int ms) { + struct mg_connection *c, *tmp; + uint64_t now; + + mg_iotest(mgr, ms); + now = mg_millis(); + mg_timer_poll(&mgr->timers, now); + mg_ota_poll(mgr); + + 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_readable) read_conn(c); + if (c->is_writable) write_conn(c); + if (c->is_tls && !c->is_tls_hs && c->send.len == 0) mg_tls_flush(c); + } + + if (c->is_draining && c->send.len == 0) c->is_closing = 1; + if (c->is_closing) close_conn(c); + } +} +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/ssi.c" +#endif + + + + + +#ifndef MG_MAX_SSI_DEPTH +#define MG_MAX_SSI_DEPTH 5 +#endif + +#ifndef MG_SSI_BUFSIZ +#define MG_SSI_BUFSIZ 1024 +#endif + +#if MG_ENABLE_SSI +static char *mg_ssi(const char *path, const char *root, int depth) { + struct mg_iobuf b = {NULL, 0, 0, MG_IO_SIZE}; + FILE *fp = fopen(path, "rb"); + if (fp != NULL) { + char buf[MG_SSI_BUFSIZ], arg[sizeof(buf)]; + int ch, intag = 0; + size_t len = 0; + buf[0] = arg[0] = '\0'; + while ((ch = fgetc(fp)) != EOF) { + if (intag && ch == '>' && len >= 2 && buf[len - 1] == '-' && buf[len - 2] == '-') { + buf[len++] = (char) (ch & 0xff); + buf[len] = '\0'; + if (sscanf(buf, " %#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 mg_tcpip_driver mg_tcpip_driver_same54 = { + mg_tcpip_driver_same54_init, mg_tcpip_driver_same54_tx, NULL, + mg_tcpip_driver_same54_poll}; +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/drivers/sdio.c" +#endif + + +#if MG_ENABLE_TCPIP && \ + (defined(MG_ENABLE_DRIVER_CYW_SDIO) && MG_ENABLE_DRIVER_CYW_SDIO) + +// SDIO 6.9 Table 6-1 CCCR (Common Card Control Registers) +#define MG_SDIO_CCCR_SDIOREV 0x000 +#define MG_SDIO_CCCR_SDREV 0x001 +#define MG_SDIO_CCCR_IOEN 0x002 +#define MG_SDIO_CCCR_IORDY 0x003 +#define MG_SDIO_CCCR_INTEN 0x004 +#define MG_SDIO_CCCR_BIC 0x007 +#define MG_SDIO_CCCR_CCAP 0x008 +#define MG_SDIO_CCCR_CCIS 0x009 // 3 registers +#define MG_SDIO_CCCR_F0BLKSZ 0x010 // 2 registers +#define MG_SDIO_CCCR_HISPD 0x013 +// SDIO 6.10 Table 6-3 FBR (Function Basic Registers) +#define MG_SDIO_FBR_FnBLKSZ(n) (((n) &7) * 0x100 + 0x10) // 2 registers + +// SDIO 5.1 IO_RW_DIRECT Command (CMD52) +#define MG_SDIO_DATA(x) ((x) &0xFF) // bits 0-7 +#define MG_SDIO_ADDR(x) (((x) &0x1FFFF) << 9) // bits 9-25 +#define MG_SDIO_FUNC(x) (((x) &3) << 28) // bits 28-30 (30 unused here) +#define MG_SDIO_WR MG_BIT(31) + +// SDIO 5.3 IO_RW_EXTENDED Command (CMD53) +#define MG_SDIO_LEN(x) ((x) &0x1FF) // bits 0-8 +#define MG_SDIO_OPINC MG_BIT(26) +#define MG_SDIO_BLKMODE MG_BIT(27) + +// - Drivers set blocksize, drivers request transfers. Requesting a read +// transfer > blocksize means block transfer will be used. +// - To simplify the use of DMA transfers and avoid intermediate buffers, +// drivers must have room to accomodate a whole block transfer, e.g.: blocksize +// = 64, read 65 => 2 blocks = 128 bytes +// - Transfers of more than 1 byte assume (uint32_t *) data. 1-byte transfers +// use (uint8_t *) data +// - 'len' is the number of _bytes_ to transfer +bool mg_sdio_transfer(struct mg_tcpip_sdio *sdio, bool write, unsigned int f, + uint32_t addr, void *data, uint32_t len) { + uint32_t arg, val = 0; + unsigned int blksz = 64; // TODO(): mg_sdio_set_blksz() stores in an array, + // index on f, skip if 0 + if (len == 1) { + arg = (write ? MG_SDIO_WR : 0) | MG_SDIO_FUNC(f) | MG_SDIO_ADDR(addr) | + (write ? MG_SDIO_DATA(*(uint8_t *) data) : 0); + bool res = sdio->txn(sdio, 52, arg, &val); // IO_RW_DIRECT + if (!write) *(uint8_t *) data = (uint8_t) val; + return res; + } + // IO_RW_EXTENDED + arg = (write ? MG_SDIO_WR : 0) | MG_SDIO_OPINC | MG_SDIO_FUNC(f) | + MG_SDIO_ADDR(addr); + if (len > 512 || (blksz != 0 && len > blksz)) { // SDIO 5.3 512 -> len=0 + unsigned int blkcnt; + if (blksz == 0) return false; // > 512 requires block size set + blkcnt = (len + blksz - 1) / blksz; + if (blkcnt > 511) return false; // we don't support "infinite" blocks + arg |= MG_SDIO_BLKMODE | MG_SDIO_LEN(blkcnt); // block transfer + len = blksz * blkcnt; + } else { + arg |= MG_SDIO_LEN(len); // multi-byte transfer + } + return sdio->xfr(sdio, write, arg, + (arg & MG_SDIO_BLKMODE) ? (uint16_t) blksz : 0, + (uint32_t *) data, len, &val); +} + +bool mg_sdio_set_blksz(struct mg_tcpip_sdio *sdio, unsigned int f, + uint16_t blksz) { + uint32_t val = blksz & 0xff; + if (!mg_sdio_transfer(sdio, true, 0, MG_SDIO_FBR_FnBLKSZ(f), &val, 1)) + return false; + val = (blksz >> 8) & 0x0f; // SDIO 6.10 Table 6-4, max 2048 + if (!mg_sdio_transfer(sdio, true, 0, MG_SDIO_FBR_FnBLKSZ(f) + 1, &val, 1)) + return false; + // TODO(): store in an array, index on f. Static 8-element array + MG_VERBOSE(("F%c block size set", (f & 7) + '0')); + return true; +} + +// Enable Fx +bool mg_sdio_enable_f(struct mg_tcpip_sdio *sdio, unsigned int f) { + uint8_t bit = 1U << (f & 7), bits; + uint32_t val = 0; + if (!mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_IOEN, &val, 1)) + return false; + bits = (uint8_t) val | bit; + unsigned int times = 501; + while (times--) { + val = bits; /* IOEf */ + ; + if (!mg_sdio_transfer(sdio, true, 0, MG_SDIO_CCCR_IOEN, &val, 1)) + return false; + mg_delayms(1); + val = 0; + if (!mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_IOEN, &val, 1)) + return false; + if (val & bit) break; + } + if (times == (unsigned int) ~0) return false; + MG_VERBOSE(("F%c enabled", (f & 7) + '0')); + return true; +} + +// Wait for Fx to be ready +bool mg_sdio_waitready_f(struct mg_tcpip_sdio *sdio, unsigned int f) { + uint8_t bit = 1U << (f & 7); + unsigned int times = 501; + while (times--) { + uint32_t val; + if (!mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_IORDY, &val, 1)) + return false; + if (val & bit) break; // IORf + mg_delayms(1); + } + if (times == (unsigned int) ~0) return false; + MG_VERBOSE(("F%c ready", (f & 7) + '0')); + return true; +} + +// SDIO 6.14 Bus State Diagram +bool mg_sdio_init(struct mg_tcpip_sdio *sdio) { + uint32_t val = 0; + if (!sdio->txn(sdio, 0, 0, NULL)) return false; // GO_IDLE_STATE + sdio->txn(sdio, 5, 0, &val); // IO_SEND_OP_COND, no CRC + MG_VERBOSE(("IO Functions: %u, Memory: %c", 1 + ((val >> 28) & 7), + (val & MG_BIT(27)) ? 'Y' : 'N')); + if (!sdio->txn(sdio, 3, 0, &val)) return false; // SEND_RELATIVE_ADDR + val = ((uint32_t) val) >> 16; // RCA + if (!sdio->txn(sdio, 7, val << 16, &val)) + return false; // SELECT/DESELECT_CARD + mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_SDIOREV, &val, 1); + MG_DEBUG(("CCCR: %u.%u, SDIO: %u.%u", 1 + ((val >> 2) & 3), (val >> 0) & 3, + 1 + ((val >> 6) & 3), (val >> 4) & 3)); + mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_SDREV, &val, 1); + MG_VERBOSE(("SD: %u.%u", 1 + ((val >> 2) & 3), (val >> 0) & 3)); + mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_BIC, &val, 1); + MG_SET_BITS(val, 3, + MG_BIT(7) | MG_BIT(1)); // SDIO 6.9 Tables 6-1 6-2, 4-bit bus + mg_sdio_transfer(sdio, true, 0, MG_SDIO_CCCR_BIC, &val, 1); + // All Full-Speed SDIO cards support a 4-bit bus. Skip for Low-Speed SDIO + // cards, we don't provide separate low-level functions for width and speed + sdio->cfg(sdio, 0); // set DS; + if (!mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_HISPD, &val, 1)) + return false; + if (val & MG_BIT(0) /* SHS */) { + val = MG_BIT(1); /* EHS */ + if (!mg_sdio_transfer(sdio, true, 0, MG_SDIO_CCCR_HISPD, &val, 1)) + return false; + sdio->cfg(sdio, 1); // set HS; + MG_VERBOSE(("Bus set to 4-bit @50MHz")); + } else { + MG_VERBOSE(("Bus set to 4-bit @25MHz")); + } + return true; +} + +// - 6.11 Card Information Structure (CIS): 0x0001000-0x017FF; for card common +// and all functions +// - 16.5 SDIO Card Metaformat +// - 16.7.2 CISTPL_FUNCE (0x22): Function Extension Tuple, provides standard +// information about the card (common) and each individual function. One +// CISTPL_FUNCE in each function’s CIS, immediately following the CISTPL_FUNCID +// tuple +// - 16.7.3 CISTPL_FUNCE Tuple for Function 0 (common) +// - 16.7.4 CISTPL_FUNCE Tuple for Function 1-7 + +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/drivers/st67w6.c" +#endif + + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_ST67W6) && \ + MG_ENABLE_DRIVER_ST67W6 + +static struct mg_tcpip_if *s_ifp; +static uint32_t s_ip, s_mask; +static bool s_link = false, s_connecting = false; + +static void wifi_cb(struct mg_tcpip_if *ifp, int ev, void *ev_data) { + struct mg_wifi_data *wifi = + &((struct mg_tcpip_driver_st67w6_data *) ifp->driver_data)->wifi; + if (wifi->apmode && ev == MG_TCPIP_EV_STATE_CHANGE && + *(uint8_t *) ev_data == MG_TCPIP_STATE_UP) { + MG_DEBUG(("Access Point started")); + s_ip = ifp->ip, ifp->ip = wifi->apip; + s_mask = ifp->mask, ifp->mask = wifi->apmask; + ifp->enable_dhcp_client = false; + ifp->enable_dhcp_server = true; + } +} + +static bool st67w6_init(uint8_t *mac); +static void st67w6_poll(bool is_at); + +static bool mg_tcpip_driver_st67w6_init(struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_st67w6_data *d = + (struct mg_tcpip_driver_st67w6_data *) ifp->driver_data; + struct mg_wifi_data *wifi = &d->wifi; + if (MG_BIG_ENDIAN) { + MG_ERROR(("Big-endian host")); + return false; + } + if (d->is_ready == NULL) return false; + s_ifp = ifp; + s_ip = ifp->ip; + s_mask = ifp->mask; + s_link = false; + ifp->pfn = wifi_cb; + if (!st67w6_init(ifp->mac)) return false; + + if (d->send_queue.size == 0) d->send_queue.size = 8192; + d->send_queue.buf = (char *) mg_calloc(1, d->send_queue.size); + if (d->send_queue.buf == NULL) { + MG_ERROR(("OOM")); + return false; + } + + if (wifi->apmode) { + return mg_wifi_ap_start(wifi); + } else if (wifi->ssid != NULL && wifi->pass != NULL) { + return mg_wifi_connect(wifi); + } + return true; +} + +// Decouple; module access depends on it being RDY. See st67w6_poll() +size_t mg_tcpip_driver_st67w6_output(const void *buf, size_t len, + struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_st67w6_data *d = + (struct mg_tcpip_driver_st67w6_data *) ifp->driver_data; + char *p; + if (mg_queue_book(&d->send_queue, &p, len) < len) return 0; + memcpy(p, buf, len); + mg_queue_add(&d->send_queue, len); + return len; +} + +static bool mg_tcpip_driver_st67w6_poll(struct mg_tcpip_if *ifp, bool s1) { + struct mg_tcpip_driver_st67w6_data *d = + (struct mg_tcpip_driver_st67w6_data *) ifp->driver_data; + if (d->is_ready == NULL) return false; + st67w6_poll(false); + if (!s1) return false; + return s_link; +} + +struct mg_tcpip_driver mg_tcpip_driver_st67w6 = { + mg_tcpip_driver_st67w6_init, mg_tcpip_driver_st67w6_output, NULL, + mg_tcpip_driver_st67w6_poll}; + +// AT | STA | AP | HCI | OT +// -------------------------- +// framing <-- includes rx stall indication +// -------------------------- +// SPI <-- padded to 32-bit +// +// Transactions take place when the module signals RDY, 'tx' and 'write' +// functions actually just write to memory +// - AT: handles configuration and events (unsolicited +foo:). Responses may +// come as: +// - 1 frame: just OK or ERROR +// - 2 frames: 1 text line each, a response and OK +// - 3 frames: 2 text lines: 1 frame with text and no CR/LF, 1 frame with +// binary and CR/LF, 1 text line with OK +// - STA and AP contain plain Ethernet frames to/from the STA and AP networks, +// respectively +// - HCI: BLE stuff +// - OT: ? + +#pragma pack(push, 1) +// little endian + +struct spi_hdr { + uint16_t magic; + uint16_t len; + uint8_t vflags; // version :2, rx_stall :1, flags :5 + uint8_t type; + uint16_t reserved; +}; + +#define ST67W6_SPI_TYPE_AT 0 +#define ST67W6_SPI_TYPE_STA 1 +#define ST67W6_SPI_TYPE_AP 2 +#define ST67W6_SPI_TYPE_HCI 3 +#define ST67W6_SPI_TYPE_OT 4 + +#pragma pack(pop) + +static uint32_t txdata[2048 / 4], rxdata[2048 / 4]; +static uint8_t at_resp[300]; + +static bool s_at_ok, s_at_err; +static size_t s_at_resp_len; +static void st67w6_handle_wifi_evnt(char *, size_t len); +static void st67w6_handle_scan_result(char *, size_t len); + +static size_t st67w6_spi_poll(uint8_t *write, uint8_t *read); +static void st67w6_write(unsigned int f, void *data, uint16_t len); +static void st67w6_update_hash_table(void); + +// High-level comm stuff + +static void st67w6_poll(bool is_at) { + struct mg_tcpip_driver_st67w6_data *d = + (struct mg_tcpip_driver_st67w6_data *) s_ifp->driver_data; + struct spi_hdr *h = (struct spi_hdr *) rxdata; + unsigned int type; + uint8_t *txsource = (uint8_t *) txdata; + if (s_ifp->update_mac_hash_table) { + // first call to _poll() is after _init(), so this is safe + st67w6_update_hash_table(); + s_ifp->update_mac_hash_table = false; + } + if (!is_at) { // send outstanding WLAN frames in the queue + char *buf; + size_t len; + // NOTE(): have traffic-dependent queues or queue traffic type with data + if ((len = mg_queue_next(&d->send_queue, &buf)) > 0) { + st67w6_write(d->wifi.apmode ? ST67W6_SPI_TYPE_AP : ST67W6_SPI_TYPE_STA, + buf, (uint16_t) len); + mg_queue_del(&d->send_queue, len); + } else { // nothing to send + txsource = NULL; + } + } + if (st67w6_spi_poll(txsource, (uint8_t *) rxdata) == 0) return; + if (h->len == 0) return; + type = h->type; + if (type == ST67W6_SPI_TYPE_AT) { + char *p = (char *) (h + 1); + size_t len = h->len; + if (len > 7 && strncmp(p, "+CWLAP:", 7) == 0) { // scan result + st67w6_handle_scan_result(p + 7, len - 7); + } else if (len > 4 && strncmp(p, "+CW:", 4) == 0) { + st67w6_handle_wifi_evnt(p + 4, len - 4); + } else { + MG_VERBOSE(("AT partial: %.*s", (int) len, p)); + if (s_at_resp_len + len >= sizeof(at_resp)) + s_at_resp_len = 0; // truncate response, error will be caught later + memcpy(at_resp + s_at_resp_len, p, len); + s_at_resp_len += len; + if (mg_match(mg_str_n((char *) at_resp, s_at_resp_len), + mg_str_n("*ERROR*", 7), NULL)) { + s_at_err = true; + } else if (mg_match(mg_str_n((char *) at_resp, s_at_resp_len), + mg_str_n("*OK*", 4), NULL)) { + s_at_ok = true; + } + } + } else if (type == ST67W6_SPI_TYPE_STA || type == ST67W6_SPI_TYPE_AP) { + // WLAN frame reception + mg_tcpip_qwrite(h + 1, h->len, s_ifp); + } // else silently discard +} + +// WLAN event handling + +// - Do not call any AT functions here, otherwise revise st67w6_at_wait() +// - The module likes to send ERROR along with other events +static void st67w6_handle_wifi_evnt(char *p, size_t len) { + struct mg_str data[2]; + MG_VERBOSE(("event: %.*s", (int) len, p)); + if (len > 9 && strncmp(p, "CONNECTED", 9) == 0) { + s_link = true; + s_connecting = false; + } else if (len > 12 && strncmp(p, "DISCONNECTED", 12) == 0) { + s_link = false; + s_connecting = false; // should not be needed + } else if (s_connecting && len > 6 && + mg_match(mg_str_n(p, len), mg_str_n("ERROR,*\r\n*", 10), data)) { + size_t reason = 0; + bool ok = mg_to_size_t(data[0], &reason); + s_connecting = false; + MG_ERROR(("CONNECT FAILED")); + mg_tcpip_call(s_ifp, MG_TCPIP_EV_WIFI_CONNECT_ERR, ok ? &reason : NULL); + } else if (len > 9 && strncmp(p, "SCAN_DONE", 9) == 0) { + MG_VERBOSE(("scan complete")); + mg_tcpip_call(s_ifp, MG_TCPIP_EV_WIFI_SCAN_END, NULL); + } // else silently discard: CONNECTING; STA_CONNECTED,"MAC"; + // STA_DISCONNECTED,"MAC"; DIST_STA_IP,"MAC","IP" +} + +static bool st67w6_at_cmd(char *cmd, size_t len); + +// Wi-Fi network stuff + +static bool st67w6_wifi_connect(char *ssid, char *pass) { + char cmd[90]; // ssid + pass + AT + size_t cmd_len; + if (!st67w6_at_cmd("AT+CWMODE=1,0\r\n", 15)) return false; + cmd_len = mg_snprintf(cmd, sizeof(cmd), "AT+CWJAP=\"%s\",\"%s\",,0\r\n", ssid, + pass); // takes >700ms + if (!st67w6_at_cmd(cmd, cmd_len)) return false; + st67w6_at_cmd("AT+CWRECONNCFG=0,0\r\n", 20); // disregard error, connecting + s_connecting = true; + return true; +} + +static bool st67w6_wifi_disconnect(void) { + s_connecting = false; + if (!st67w6_at_cmd("AT+CWQAP=0\r\n", 12)) return false; // takes >800ms + return st67w6_at_cmd("AT+CWMODE=0,0\r\n", 15); // takes >550ms +} + +static bool st67w6_wifi_ap_start(char *ssid, char *pass, unsigned int channel) { + char cmd[90]; // ssid + pass + AT + size_t cmd_len; + if (!st67w6_at_cmd("AT+CWMODE=2,0\r\n", 15)) return false; // takes >800ms + cmd_len = mg_snprintf( + cmd, sizeof(cmd), "AT+CWSAP=\"%s\",\"%s\",%u,3,2,0\r\n", ssid, pass, + channel); // 3: WPA2_PSK; 2: max stations // takes >350ms + s_link = true; + return st67w6_at_cmd(cmd, cmd_len); +} + +static bool st67w6_wifi_ap_stop(void) { + s_link = false; + return st67w6_at_cmd("AT+CWMODE=0,0\r\n", 15); // takes >550ms +} + +// WLAN scan handling + +// +CWLAP:(security,"SSID",RSSI,"BSSID",channel,cipher,proto,wps)\r\n +// security: OPEN, WEP, WPA, WPA2, WPA-WPA2, WPA-EAP, WPA3-SAE, WPA2-WPA3-SAE +// cipher: NONE, WEP, AES/CCMP, TKIP, TKIP and AES/CCMP +// proto: 4-bit bitmap AX,N,G,B; all set from right to left + +static bool st67w6_wifi_scan(void) { + return st67w6_at_cmd("AT+CWLAPOPT=1,1695,-100,255,50\r\n", 32) && + st67w6_at_cmd("AT+CWLAP=0,,,0\r\n", 16); +} + +static void st67w6_handle_scan_result(char *data, size_t len) { + struct mg_wifi_scan_bss_data bss; + struct mg_str fields[2]; + char mac[6]; + uint8_t val; + unsigned int i; + MG_VERBOSE(("scan result event: %.*s", (int) len, data)); + ++data, --len; // skip '(' + if (!mg_span(mg_str_n(data, len), &fields[0], &fields[1], ',') || + !mg_str_to_num(fields[0], 10, &val, 1)) + return; + bss.security = + (val == 0) ? MG_WIFI_SECURITY_OPEN : (uint8_t) MG_WIFI_SECURITY_WEP; + if (val == 2 || val == 4) bss.security |= MG_WIFI_SECURITY_WPA; + if (val == 3 || val == 4 || val == 7) bss.security |= MG_WIFI_SECURITY_WPA2; + if (val == 6 || val == 7) bss.security |= MG_WIFI_SECURITY_WPA3; + if (val == 5) bss.security |= MG_WIFI_SECURITY_WPA_ENTERPRISE; + if (!mg_span(fields[1], &fields[0], &fields[1], ',')) return; + bss.SSID.buf = fields[0].buf + 1, bss.SSID.len = fields[0].len - 2; + if (!mg_span(fields[1], &fields[0], &fields[1], ',')) return; + while (fields[0].buf[0] == ' ') ++fields[0].buf, --fields[0].len; + if (fields[0].buf[0] != '-') return; // positive RSSI would be great + ++fields[0].buf, --fields[0].len; + if (!mg_str_to_num(fields[0], 10, &val, 1)) return; + bss.RSSI = (int8_t) - (int8_t) val; + if (!mg_span(fields[1], &fields[0], &fields[1], ',')) return; + if (fields[0].len < 19) return; + ++fields[0].buf, --fields[0].len; // skip '"' + for (i = 0; i < 6; i++) { + struct mg_str str; + str.buf = fields[0].buf + 3 * i; + str.len = 2; + if (!mg_str_to_num(str, 16, &mac[i], 1)) return; + } + bss.BSSID = mac; + if (!mg_span(fields[1], &fields[0], &fields[1], ',') || + !mg_str_to_num(fields[0], 10, &bss.channel, 1)) + return; + if (!mg_span(fields[1], &fields[0], &fields[1], ',') || + !mg_str_to_num(fields[0], 10, &val, 1)) + return; + // ignore cypher + if (!mg_span(fields[1], &fields[0], &fields[1], ',') || + !mg_str_to_num(fields[0], 10, &val, 1)) + return; + bss.has_n = (val & 4) != 0; + bss.has_ax = (val & 8) != 0; + bss.band = MG_WIFI_BAND_2G; // NOT INFORMED with default options, no docs + MG_VERBOSE(("BSS: %.*s (%u) (%M) %d dBm %u", bss.SSID.len, bss.SSID.buf, + bss.channel, mg_print_mac, bss.BSSID, (int) bss.RSSI, + bss.security)); + mg_tcpip_call(s_ifp, MG_TCPIP_EV_WIFI_SCAN_RESULT, &bss); +} + +// AT stuff + +static inline bool delayms(unsigned int ms) { + mg_delayms(ms); + return true; +} + +// send AT command, wait for a response or timeout, meanwhile delivering +// received frames and events + +static bool st67w6_at_cmd(char *cmd, size_t len) { + bool is_at = true; + unsigned int times = 1000; + s_at_resp_len = 0; + st67w6_write(ST67W6_SPI_TYPE_AT, cmd, (uint16_t) len); + s_at_ok = false, s_at_err = false; + do { // AT response processing does not call any other AT function + st67w6_poll(is_at); // otherwise we can't allow them to pile up here + is_at = false; // avoid repeating, allow queued WLAN frames to be sent + // network frames will be pushed to the queue so that is safe + } while (!s_at_ok && !s_at_err && times-- > 0 && delayms(1)); + MG_VERBOSE(("AT response:\n%.*s", s_at_resp_len, at_resp)); + MG_VERBOSE(("ok: %c, err: %c, times: %d", s_at_ok ? '1' : '0', + s_at_err ? '1' : '0', (int) times)); + return s_at_ok; +} + +static bool st67w6_spi_init(void); +bool mg_to_size_t(struct mg_str str, size_t *val); + +static bool st67w6_init(uint8_t *mac) { + // struct mg_tcpip_driver_st67w6_data *d = (struct + // mg_tcpip_driver_st67w6_data *) s_ifp->driver_data; + struct mg_str data[3]; + size_t val; + bool is_b = false; + if (!st67w6_spi_init()) return false; + if (!st67w6_at_cmd("AT\r\n", 4)) return false; + if (!st67w6_at_cmd("AT+CWNETMODE?\r\n", 15) || + !mg_match(mg_str_n((char *) at_resp, s_at_resp_len), + mg_str_n("*:*\r\n*", 6), data) || + !mg_to_size_t(data[1], &val)) + return false; + if (val != 0) { + MG_ERROR(("Wrong firmware, T02 is needed")); + return false; + } + // set clock, who cares ??? + if (!st67w6_at_cmd("AT+GET_CLOCK\r\n", 14)) return false; + MG_DEBUG(("%.*s", s_at_resp_len, at_resp)); // TODO(scaprile): --> VERBOSE + // BT-ENABLED DEPENDENCY + + // MODULE DEPENDENCY + if (!st67w6_at_cmd("AT+EFUSE-R=24,\"0x100\"\r\n", 23) || + !mg_match(mg_str_n((char *) at_resp, s_at_resp_len), mg_str_n("*,*", 3), + data)) + return false; + if (data[1].buf[0] == 'C' && data[1].buf[1] == '6') is_b = true; + MG_DEBUG(("WLAN module is %sB type", is_b ? "" : "not")); // --> VERBOSE + if (is_b) { + // Disable the antenna diversity pin + if (!st67w6_at_cmd("AT+IORST=0\r\n", 12)) return false; + // Apparently they intend to disable some antenna ... + if (!st67w6_at_cmd("AT+CWANTENABLE?\r\n", 17)) return false; + MG_DEBUG(("%.*s", s_at_resp_len, at_resp)); // --> VERBOSE + } + + // Do not set wake-up pin (AT+SLWKIO) + // Disable power save mode + // NOTE(scaprile): (no response if in hibernate mode, though I guess we + // wouldn't have reached this point in that case either) + if (!st67w6_at_cmd("AT+PWR=0\r\n", 12)) return false; + // set Wi-Fi + // set country code + if (!st67w6_at_cmd("AT+CWCOUNTRY=0,\"00\"\r\n", 21)) return false; +#if 0 + // set DTIM + if (!st67w6_at_cmd("AT+SLWKDTIM=1\r\n", 15)) return false; +#endif + // Read only default MAC, ignore set bit count (data[6] & 0x3F). Custom MACs + // reside at @0x64 and 0x70 + if (st67w6_at_cmd("AT+EFUSE-R=7,\"0x014\"\r\n", 22) && + mg_match(mg_str_n((char *) at_resp, s_at_resp_len), mg_str_n("*,*", 3), + data)) { + int i; + for (i = 0; i < 6; i++) mac[i] = data[1].buf[5 - i]; + MG_DEBUG(("MAC: %M", mg_print_mac, mac)); + } else { + MG_ERROR(("read MAC failed")); + } + return true; +} + +static void st67w6_update_hash_table(void) { + // TODO(): read database, rebuild hash table + // uint32_t val = 0; + // val = 1; + // st67w6_at_iovar_set2_(0, "mcast_list", (uint8_t *) &val, sizeof(val), + // (uint8_t *) mcast_addr, sizeof(mcast_addr)); mg_delayms(50); +} + +// SPI specifics + +#define ST67W6_SPI_MAGIC 0x55AA +// #define ST67W6_SPI_VERSION(x), IS_STALL, FLAGS(x), ... + +static const uint8_t idlehdr[sizeof(struct spi_hdr)] = {0xaa, 0x55, 0, 0, + 0, 0, 0, 0}; + +static void st67w6_write(unsigned int f, void *data, uint16_t len) { + struct spi_hdr *h = (struct spi_hdr *) txdata; + h->magic = ST67W6_SPI_MAGIC; + h->type = (uint8_t) f; + h->vflags = 0; + h->len = len; + h->reserved = 0; + memmove(h + 1, data, len); +} + +static size_t st67w6_spi_poll(uint8_t *write, uint8_t *read) { + struct mg_tcpip_driver_st67w6_data *d = + (struct mg_tcpip_driver_st67w6_data *) s_ifp->driver_data; + struct spi_hdr *th, *rh = (struct spi_hdr *) read; + struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) d->spi; + size_t padded; + unsigned int times; + + th = (write != NULL) ? (struct spi_hdr *) write : (struct spi_hdr *) idlehdr; + s->begin(s->spi); + times = 50; + while (times--) { + if (d->is_ready()) break; + if (times == 0) { + MG_ERROR(("RDY TIMEOUT")); + s->end(s->spi); + return 0; + } + mg_delayms(1); + } + + padded = (th->len + 3) & ~3; + s->txn(s->spi, (uint8_t *) th, (uint8_t *) rh, sizeof(*th) + padded); + if (rh->magic == ST67W6_SPI_MAGIC && rh->len > padded) { + size_t remaining_padded = (rh->len - padded + 3) & ~3; + if (remaining_padded > (2048 - sizeof(*rh) - padded)) + remaining_padded = 2048 - sizeof(*rh) - padded; + s->txn(s->spi, NULL, read + sizeof(*rh) + padded, remaining_padded); + } + times = 50; + while (times--) { + if (!d->is_ready()) break; + if (times == 0) { + MG_ERROR(("!RDY TIMEOUT")); + break; + } + mg_delayms(1); + } + s->end(s->spi); + return (size_t) rh->len; +} + +static bool st67w6_spi_init(void) { + struct mg_tcpip_driver_st67w6_data *d = + (struct mg_tcpip_driver_st67w6_data *) s_ifp->driver_data; + size_t len; + unsigned int times = 1000; + while (times--) { + if (d->is_ready()) break; + if (times == 0) return false; + mg_delayms(1); + } + if (((len = st67w6_spi_poll(NULL, (uint8_t *) rxdata)) == 0) || + !mg_match(mg_str_n(((char *) rxdata) + sizeof(struct spi_hdr), len), + mg_str_n("*ready*", 7), NULL)) + return false; + return true; +} + +// Mongoose Wi-Fi API functions + +bool mg_wifi_scan(void) { + return st67w6_wifi_scan(); +} + +bool mg_wifi_connect(struct mg_wifi_data *wifi) { + s_ifp->ip = s_ip; + s_ifp->mask = s_mask; + if (s_ifp->ip == 0) s_ifp->enable_dhcp_client = true; + s_ifp->enable_dhcp_server = false; + MG_DEBUG(("Connecting to '%s'", wifi->ssid)); + return st67w6_wifi_connect(wifi->ssid, wifi->pass); +} + +bool mg_wifi_disconnect(void) { + return st67w6_wifi_disconnect(); +} + +bool mg_wifi_ap_start(struct mg_wifi_data *wifi) { + MG_DEBUG(("Starting AP '%s' (%u)", wifi->apssid, wifi->apchannel)); + return st67w6_wifi_ap_start(wifi->apssid, wifi->appass, wifi->apchannel); +} + +bool mg_wifi_ap_stop(void) { + return st67w6_wifi_ap_stop(); +} + +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/drivers/stm32f.c" +#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) + +#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] MG_ETH_RAM; // RX descriptors +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS] MG_ETH_RAM; // TX descriptors +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_ETH_RAM; // RX ethernet buffers +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_ETH_RAM; // TX ethernet buffers +static uint8_t s_txno; // Current TX descriptor +static uint8_t s_rxno; // Current RX descriptor + +static struct mg_tcpip_if *s_ifp; // MIP interface + +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; +} + +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; +} + +static uint32_t get_hclk(void) { + struct rcc { + volatile uint32_t CR, PLLCFGR, CFGR; + } *rcc = (struct rcc *) 0x40023800; + uint32_t clk = 0, hsi = 16000000 /* 16 MHz */, hse = 8000000 /* 8MHz */; + + if (rcc->CFGR & (1 << 2)) { + clk = hse; + } else if (rcc->CFGR & (1 << 3)) { + uint32_t vco, m, n, p; + m = (rcc->PLLCFGR & (0x3f << 0)) >> 0; + n = (rcc->PLLCFGR & (0x1ff << 6)) >> 6; + p = (((rcc->PLLCFGR & (3 << 16)) >> 16) + 1) * 2; + clk = (rcc->PLLCFGR & (1 << 22)) ? hse : hsi; + vco = (uint32_t) ((uint64_t) clk * n / m); + clk = vco / p; + } else { + clk = hsi; + } + uint32_t hpre = (rcc->CFGR & (15 << 4)) >> 4; + if (hpre < 8) return clk; + + uint8_t ahbptab[8] = {1, 2, 3, 4, 6, 7, 8, 9}; // log2(div) + return ((uint32_t) clk) >> ahbptab[hpre - 8]; +} + +// Guess CR from HCLK. MDC clock is generated from HCLK (AHB); as per 802.3, +// it must not exceed 2.5MHz As the AHB clock can be (and usually is) derived +// from the HSI (internal RC), and it can go above specs, the datasheets +// specify a range of frequencies and activate one of a series of dividers to +// keep the MDC clock safely below 2.5MHz. We guess a divider setting based on +// HCLK with a +5% drift. If the user uses a different clock from our +// defaults, needs to set the macros on top Valid for STM32F74xxx/75xxx +// (38.8.1) and STM32F42xxx/43xxx (33.8.1) (both 4.5% worst case drift) +static int guess_mdc_cr(void) { + uint8_t crs[] = {2, 3, 0, 1, 4, 5}; // ETH->MACMIIAR::CR values + uint8_t div[] = {16, 26, 42, 62, 102, 124}; // Respective HCLK dividers + uint32_t hclk = get_hclk(); // Guess system HCLK + int result = -1; // Invalid CR value + if (hclk < 25000000) { + MG_ERROR(("HCLK too low")); + } else { + for (int i = 0; i < 6; i++) { + if (hclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) { + result = crs[i]; + break; + } + } + if (result < 0) MG_ERROR(("HCLK too high")); + } + MG_DEBUG(("HCLK: %u, CR: %d", hclk, result)); + return result; +} + +static bool mg_tcpip_driver_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; + + // 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 + } + + // Init TX descriptors + for (int i = 0; i < ETH_DESC_CNT; i++) { + s_txdesc[i][2] = (uint32_t) (uintptr_t) s_txbuf[i]; // Buf pointer + s_txdesc[i][3] = + (uint32_t) (uintptr_t) s_txdesc[(i + 1) % ETH_DESC_CNT]; // Chain + } + + ETH->DMABMR |= MG_BIT(0); // Software reset + while ((ETH->DMABMR & MG_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 = 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(10); // Perfect filtering + 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 + + // MAC address filtering + ETH->MACA0HR = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4]; + ETH->MACA0LR = (uint32_t) (ifp->mac[3] << 24) | + ((uint32_t) ifp->mac[2] << 16) | + ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0]; + return true; +} + +static size_t mg_tcpip_driver_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 void mg_tcpip_driver_stm32f_update_hash_table(struct mg_tcpip_if *ifp) { + // TODO(): read database, rebuild hash table + ETH->MACA1LR = (uint32_t) mcast_addr[3] << 24 | + (uint32_t) mcast_addr[2] << 16 | + (uint32_t) mcast_addr[1] << 8 | (uint32_t) mcast_addr[0]; + ETH->MACA1HR = (uint32_t) mcast_addr[5] << 8 | (uint32_t) mcast_addr[4]; + ETH->MACA1HR |= MG_BIT(31); // AE + (void) ifp; +} + +static bool mg_tcpip_driver_stm32f_poll(struct mg_tcpip_if *ifp, bool s1) { + if (ifp->update_mac_hash_table) { + mg_tcpip_driver_stm32f_update_hash_table(ifp); + ifp->update_mac_hash_table = false; + } + if (!s1) return false; + 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 up; +} + +#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_poll}; +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/drivers/stm32h.c" +#endif + + +#if MG_ENABLE_TCPIP && (MG_ENABLE_DRIVER_STM32H || MG_ENABLE_DRIVER_MCXN || \ + MG_ENABLE_DRIVER_STM32N) +// STM32H: vendor modded single-queue Synopsys v4.2 +// STM32N: dual-queue GbE Synopsys v5.2 with no hash table option, 64-bit AXI +// MCXNx4x: dual-queue Synopsys v5.2 with no hash table option +// RT1170: ENET_QOS: quad-queue Synopsys v5.1 +#if MG_ENABLE_DRIVER_STM32H +#define SYNOPSYS_ENET_V5 0 +#define SYNOPSYS_ENET_SINGLEQ 1 +#define SYNOPSYS_ENET_NOHASHTABLE 0 +#define SYNOPSYS_ENET_GbE 0 +#elif MG_ENABLE_DRIVER_STM32N +#define SYNOPSYS_ENET_V5 1 +#define SYNOPSYS_ENET_SINGLEQ 0 +#define SYNOPSYS_ENET_NOHASHTABLE 1 +#define SYNOPSYS_ENET_GbE 1 +#elif MG_ENABLE_DRIVER_MCXN +#define SYNOPSYS_ENET_V5 1 +#define SYNOPSYS_ENET_SINGLEQ 0 +#define SYNOPSYS_ENET_NOHASHTABLE 1 +#define SYNOPSYS_ENET_GbE 0 +#endif + +struct synopsys_enet_qos { + 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 +#if MG_ENABLE_DRIVER_STM32H +#define ETH ((struct synopsys_enet_qos *) (uintptr_t) 0x40028000UL) +#elif MG_ENABLE_DRIVER_STM32N +#define ETH ((struct synopsys_enet_qos *) (uintptr_t) 0x48036000UL) +#elif MG_ENABLE_DRIVER_MCXN +#define ETH ((struct synopsys_enet_qos *) (uintptr_t) 0x40100000UL) +#endif + +#define ETH_PKT_SIZE 1540 // Max frame size +#define ETH_DESC_CNT 4 // Descriptors count +#define ETH_DS 4 // Descriptor size (words) + +#if MG_ENABLE_DRIVER_STM32H || MG_ENABLE_DRIVER_STM32N +#define CACHE_LINESZ 32 // must be a whole number of (d)words (see DESC_SZW) +#ifndef SCB +struct m7_scb { + volatile uint32_t CPUID, RESERVED1[4], CCR, RESERVED2[145], DCIMVAC, DCISW, + DCCMVAU, DCCMVAC, DCCSW, DCCIMVAC, DCCISW, RESERVED3[10], CACR, + RESERVED4[3]; +}; +#define SCB ((struct m7_scb *) (uintptr_t) 0xE000ED00UL) +#endif +// ending ISB is not needed because we don't cache instructions in data space +static inline void MG_CACHE_INVAL(uint8_t *addr, int32_t len) { +#if MG_ENABLE_DRIVER_STM32H + if ((SCB->CPUID & 0xfff0) != 0xc270) return; // not a Cortex-M7 => not an H7 +#endif + if ((SCB->CCR & MG_BIT(16)) == 0) return; // cache not enabled + MG_DSB(); + while (len > 0) { + SCB->DCIMVAC = (uint32_t) addr; + addr += CACHE_LINESZ; + len -= CACHE_LINESZ; + } + MG_DSB(); +} +static inline void MG_CACHE_FLUSH(uint8_t *addr, int32_t len) { +#if MG_ENABLE_DRIVER_STM32H + if ((SCB->CPUID & 0xfff0) != 0xc270) return; // not a Cortex-M7 => not an H7 +#endif + if ((SCB->CCR & MG_BIT(16)) == 0) return; // cache not enabled + MG_DSB(); + while (len > 0) { + SCB->DCCMVAC = (uint32_t) addr; + addr += CACHE_LINESZ; + len -= CACHE_LINESZ; + } + MG_DSB(); +} +#define ETH_RAM_ALIGNED MG_32BYTE_ALIGNED // depends on CACHE_LINESZ and ETH +#define CACHE_ALIGN(x) \ + ((((size_t) (x)) + CACHE_LINESZ - 1) & ~(CACHE_LINESZ - 1)) +#define DESC_SZ CACHE_ALIGN(4 * ETH_DS) // grow descriptors to fit a line +#define DESC_SZW (DESC_SZ / 4) +#define BUFF_SZ CACHE_ALIGN(ETH_PKT_SIZE) // grow buffers to fit n lines +#if MG_ENABLE_DRIVER_STM32H +#define DESC_SKIPW (DESC_SZW - ETH_DS) // tell DMA the descriptor size, words +#else +// MG_ENABLE_DRIVER_STM32N, DMA is AXI and specs skip in 64-bit double-words +#define DESC_SKIPW ((DESC_SZW - ETH_DS) / 2) +#endif +#else +#define MG_CACHE_FLUSH(a, b) +#define MG_CACHE_INVAL(a, b) +#define ETH_RAM_ALIGNED MG_8BYTE_ALIGNED // depends on ETH DMA alone +#define DESC_SZ 0 +#define DESC_SZW ETH_DS +#define BUFF_SZ ETH_PKT_SIZE +#define DESC_SKIPW 0 // no need to skip, as we're not aligning to cache lines +#endif + +// array[rows][cols] = coldata coldata ... for all rows, keeps alignment +static volatile uint32_t s_rxdesc[ETH_DESC_CNT][DESC_SZW] MG_ETH_RAM + ETH_RAM_ALIGNED; +static volatile uint32_t s_txdesc[ETH_DESC_CNT][DESC_SZW] MG_ETH_RAM + ETH_RAM_ALIGNED; +static uint8_t s_rxbuf[ETH_DESC_CNT][BUFF_SZ] MG_ETH_RAM ETH_RAM_ALIGNED; +static uint8_t s_txbuf[ETH_DESC_CNT][BUFF_SZ] MG_ETH_RAM ETH_RAM_ALIGNED; + +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 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 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; + + // Init RX descriptors + memset((char *) s_rxdesc, 0, sizeof(s_rxdesc)); // manual init + 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 + } + MG_CACHE_FLUSH((uint8_t *) s_rxdesc, sizeof(s_rxdesc)); + + // Init TX descriptors + memset((char *) s_txdesc, 0, sizeof(s_txdesc)); // manual init + for (int i = 0; i < ETH_DESC_CNT; i++) { + s_txdesc[i][0] = (uint32_t) (uintptr_t) s_txbuf[i]; // Buf pointer + } + MG_CACHE_FLUSH((uint8_t *) s_txdesc, sizeof(s_txdesc)); + + ETH->DMAMR |= MG_BIT(0); // Software reset + for (int i = 0; i < 4; i++) + (void) 0; // wait at least 4 clocks before reading + while ((ETH->DMAMR & MG_BIT(0)) != 0) (void) 0; // Wait until done + + // Set MDC clock divider. Get user value, else, assume max freq + int cr = (d == NULL || d->mdc_cr < 0) ? 7 : 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 +#if !SYNOPSYS_ENET_V5 + ETH->MACPFR = MG_BIT(10); // Perfect filtering +#endif + 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 = DESC_SKIPW << 18; // DSL (contiguous/sparse descriptor table) +#if SYNOPSYS_ENET_V5 + MG_SET_BITS(ETH->DMACTCR, 0x3F << 16, MG_BIT(16)); + MG_SET_BITS(ETH->DMACRCR, 0x3F << 16, MG_BIT(16)); +#endif + 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, (10/100)/Reserved +#if SYNOPSYS_ENET_SINGLEQ + ETH->MTLTQOMR |= MG_BIT(1); // TSF + ETH->MTLRQOMR |= MG_BIT(5); // RSF +#else + ETH->MTLTQOMR |= (7 << 16) | MG_BIT(3) | MG_BIT(1); // 2KB Q0, TSF + ETH->MTLRQOMR |= (7 << 20) | MG_BIT(5); // 2KB Q, RSF + MG_SET_BITS(ETH->RESERVED6[3], 3, 2); // Enable RxQ0 (MAC_RXQ_CTRL0) +#endif + ETH->DMACTCR |= MG_BIT(0); // ST + ETH->DMACRCR |= MG_BIT(0); // SR + + // MAC address filtering + ETH->MACA0HR = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4]; + ETH->MACA0LR = (uint32_t) (ifp->mac[3] << 24) | + ((uint32_t) ifp->mac[2] << 16) | + ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0]; + return true; +} + +static uint32_t s_txno; +static size_t mg_tcpip_driver_stm32h_tx(const void *buf, size_t len, + struct mg_tcpip_if *ifp) { + if (len > sizeof(s_txbuf[s_txno])) { + MG_ERROR(("Frame too big, %ld", (long) len)); + return 0; // Frame is too big + } + MG_CACHE_INVAL((uint8_t *) &s_txdesc[s_txno], DESC_SZ); + 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)); + MG_CACHE_INVAL((uint8_t *) s_txdesc, sizeof(s_txdesc)); + 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 + MG_CACHE_FLUSH((uint8_t *) &s_txbuf[s_txno], BUFF_SZ); + 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 + MG_CACHE_FLUSH((uint8_t *) &s_txdesc[s_txno], DESC_SZ); + 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 mg_tcpip_driver_stm32h_update_hash_table(struct mg_tcpip_if *ifp) { +#if SYNOPSYS_ENET_NOHASHTABLE + ETH->MACPFR = MG_BIT(4); // Pass Multicast (pass all multicast frames) +#else + // TODO(): read database, rebuild hash table + // add mDNS / DNS-SD multicast address + ETH->MACA1LR = (uint32_t) mcast_addr[3] << 24 | + (uint32_t) mcast_addr[2] << 16 | + (uint32_t) mcast_addr[1] << 8 | (uint32_t) mcast_addr[0]; + ETH->MACA1HR = (uint32_t) mcast_addr[5] << 8 | (uint32_t) mcast_addr[4]; + ETH->MACA1HR |= MG_BIT(31); // AE +#endif + (void) ifp; +} + +static bool mg_tcpip_driver_stm32h_poll(struct mg_tcpip_if *ifp, bool s1) { + if (ifp->update_mac_hash_table) { + mg_tcpip_driver_stm32h_update_hash_table(ifp); + ifp->update_mac_hash_table = false; + } + if (!s1) return false; + 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 SYNOPSYS_ENET_GbE + if (speed == MG_PHY_SPEED_1000M) maccr &= ~MG_BIT(15); // 1000M +#endif + 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 uint32_t s_rxno; +#if MG_ENABLE_DRIVER_MCXN +void ETHERNET_IRQHandler(void); +void ETHERNET_IRQHandler(void) { +#elif MG_ENABLE_DRIVER_STM32H +void ETH_IRQHandler(void); +void ETH_IRQHandler(void) { +#else +void ETH1_IRQHandler(void); +void ETH1_IRQHandler(void) { +#endif + 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 + MG_CACHE_INVAL((uint8_t *) &s_rxdesc[s_rxno], DESC_SZ); + 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_CACHE_INVAL((uint8_t *) &s_rxbuf[s_rxno], BUFF_SZ); + 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 + MG_CACHE_FLUSH((uint8_t *) &s_rxdesc[s_rxno], DESC_SZ); + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0; + } + } + 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 +} + +struct mg_tcpip_driver mg_tcpip_driver_stm32h = { + mg_tcpip_driver_stm32h_init, mg_tcpip_driver_stm32h_tx, NULL, + mg_tcpip_driver_stm32h_poll}; +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/drivers/tm4c.c" +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_TM4C) && MG_ENABLE_DRIVER_TM4C +struct tm4c_emac { + volatile uint32_t EMACCFG, EMACFRAMEFLTR, EMACHASHTBLH, EMACHASHTBLL, + EMACMIIADDR, EMACMIIDATA, EMACFLOWCTL, EMACVLANTG, RESERVED0, EMACSTATUS, + EMACRWUFF, EMACPMTCTLSTAT, RESERVED1[2], EMACRIS, EMACIM, EMACADDR0H, + EMACADDR0L, EMACADDR1H, EMACADDR1L, EMACADDR2H, EMACADDR2L, EMACADDR3H, + EMACADDR3L, RESERVED2[31], EMACWDOGTO, RESERVED3[8], EMACMMCCTRL, + EMACMMCRXRIS, EMACMMCTXRIS, EMACMMCRXIM, EMACMMCTXIM, RESERVED4, + EMACTXCNTGB, RESERVED5[12], EMACTXCNTSCOL, EMACTXCNTMCOL, RESERVED6[4], + EMACTXOCTCNTG, RESERVED7[6], EMACRXCNTGB, RESERVED8[4], EMACRXCNTCRCERR, + EMACRXCNTALGNERR, RESERVED9[10], EMACRXCNTGUNI, RESERVED10[239], + EMACVLNINCREP, EMACVLANHASH, RESERVED11[93], EMACTIMSTCTRL, EMACSUBSECINC, + EMACTIMSEC, EMACTIMNANO, EMACTIMSECU, EMACTIMNANOU, EMACTIMADD, + EMACTARGSEC, EMACTARGNANO, EMACHWORDSEC, EMACTIMSTAT, EMACPPSCTRL, + RESERVED12[12], EMACPPS0INTVL, EMACPPS0WIDTH, RESERVED13[294], + EMACDMABUSMOD, EMACTXPOLLD, EMACRXPOLLD, EMACRXDLADDR, EMACTXDLADDR, + EMACDMARIS, EMACDMAOPMODE, EMACDMAIM, EMACMFBOC, EMACRXINTWDT, + RESERVED14[8], EMACHOSTXDESC, EMACHOSRXDESC, EMACHOSTXBA, EMACHOSRXBA, + RESERVED15[218], EMACPP, EMACPC, EMACCC, RESERVED16, EMACEPHYRIS, + EMACEPHYIM, EMACEPHYIMSC; +}; +#undef EMAC +#define EMAC ((struct tm4c_emac *) (uintptr_t) 0x400EC000) + +#define ETH_PKT_SIZE 1540 // Max frame size +#define ETH_DESC_CNT 4 // Descriptors count +#define ETH_DS 4 // Descriptor size (words) + +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // RX ethernet buffers +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // TX ethernet buffers +static struct mg_tcpip_if *s_ifp; // MIP interface +enum { + EPHY_ADDR = 0, + EPHYBMCR = 0, + EPHYBMSR = 1, + EPHYSTS = 16 +}; // PHY constants + +static inline void tm4cspin(volatile uint32_t count) { + while (count--) (void) 0; +} + +static uint32_t emac_read_phy(uint8_t addr, uint8_t reg) { + EMAC->EMACMIIADDR &= (0xf << 2); + EMAC->EMACMIIADDR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6); + EMAC->EMACMIIADDR |= MG_BIT(0); + while (EMAC->EMACMIIADDR & MG_BIT(0)) tm4cspin(1); + return EMAC->EMACMIIDATA; +} + +static void emac_write_phy(uint8_t addr, uint8_t reg, uint32_t val) { + EMAC->EMACMIIDATA = val; + EMAC->EMACMIIADDR &= (0xf << 2); + EMAC->EMACMIIADDR |= + ((uint32_t) addr << 11) | ((uint32_t) reg << 6) | MG_BIT(1); + EMAC->EMACMIIADDR |= MG_BIT(0); + while (EMAC->EMACMIIADDR & MG_BIT(0)) tm4cspin(1); +} + +static uint32_t get_sysclk(void) { + struct sysctl { + volatile uint32_t DONTCARE0[44], RSCLKCFG, DONTCARE1[43], PLLFREQ0, + PLLFREQ1; + } *sysctl = (struct sysctl *) 0x400FE000; + uint32_t clk = 0, piosc = 16000000 /* 16 MHz */, mosc = 25000000 /* 25MHz */; + if (sysctl->RSCLKCFG & (1 << 28)) { // USEPLL + uint32_t fin, vco, mdiv, n, q, psysdiv; + uint32_t pllsrc = (sysctl->RSCLKCFG & (0xf << 24)) >> 24; + if (pllsrc == 0) { + clk = piosc; + } else if (pllsrc == 3) { + clk = mosc; + } else { + MG_ERROR(("Unsupported clock source")); + } + q = (sysctl->PLLFREQ1 & (0x1f << 8)) >> 8; + n = (sysctl->PLLFREQ1 & (0x1f << 0)) >> 0; + fin = clk / ((q + 1) * (n + 1)); + mdiv = (sysctl->PLLFREQ0 & (0x3ff << 0)) >> + 0; // mint + (mfrac / 1024); MFRAC not supported + psysdiv = (sysctl->RSCLKCFG & (0x3f << 0)) >> 0; + vco = (uint32_t) ((uint64_t) fin * mdiv); + return vco / (psysdiv + 1); + } + uint32_t oscsrc = (sysctl->RSCLKCFG & (0xf << 20)) >> 20; + if (oscsrc == 0) { + clk = piosc; + } else if (oscsrc == 3) { + clk = mosc; + } else { + MG_ERROR(("Unsupported clock source")); + } + uint32_t osysdiv = (sysctl->RSCLKCFG & (0xf << 16)) >> 16; + return clk / (osysdiv + 1); +} + +// Guess CR from SYSCLK. MDC clock is generated from SYSCLK (AHB); as per +// 802.3, it must not exceed 2.5MHz (also 20.4.2.6) As the AHB clock can be +// derived from the PIOSC (internal RC), and it can go above specs, the +// datasheets specify a range of frequencies and activate one of a series of +// dividers to keep the MDC clock safely below 2.5MHz. We guess a divider +// setting based on SYSCLK with a +5% drift. If the user uses a different clock +// from our defaults, needs to set the macros on top Valid for TM4C129x (20.7) +// (4.5% worst case drift) +// The PHY receives the main oscillator (MOSC) (20.3.1) +static int guess_mdc_cr(void) { + uint8_t crs[] = {2, 3, 0, 1}; // EMAC->MACMIIAR::CR values + uint8_t div[] = {16, 26, 42, 62}; // Respective HCLK dividers + uint32_t sysclk = get_sysclk(); // Guess system SYSCLK + int i, result = -1; // Invalid CR value + if (sysclk < 25000000) { + MG_ERROR(("SYSCLK too low")); + } else { + for (i = 0; i < 4; i++) { + if (sysclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) { + result = crs[i]; + break; + } + } + if (result < 0) MG_ERROR(("SYSCLK too high")); + } + MG_DEBUG(("SYSCLK: %u, CR: %d", sysclk, result)); + return result; +} + +static bool mg_tcpip_driver_tm4c_init(struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_tm4c_data *d = + (struct mg_tcpip_driver_tm4c_data *) ifp->driver_data; + int i; + s_ifp = ifp; + + // Init RX descriptors + for (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])); + } + + // Init TX descriptors + for (i = 0; i < ETH_DESC_CNT; i++) { + s_txdesc[i][2] = (uint32_t) (uintptr_t) s_txbuf[i]; // Buf pointer + s_txdesc[i][3] = + (uint32_t) (uintptr_t) s_txdesc[(i + 1) % ETH_DESC_CNT]; // Chain + } + + EMAC->EMACDMABUSMOD |= 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(10); // Perfect filtering + // 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]; + return true; +} + +static uint32_t s_txno; +static size_t mg_tcpip_driver_tm4c_tx(const void *buf, size_t len, + struct mg_tcpip_if *ifp) { + if (len > sizeof(s_txbuf[s_txno])) { + MG_ERROR(("Frame too big, %ld", (long) len)); + len = 0; // fail + } else if ((s_txdesc[s_txno][0] & 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 { + 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; + } + EMAC->EMACDMARIS = MG_BIT(2) | MG_BIT(5); // Clear any prior TU/UNF + EMAC->EMACTXPOLLD = 0; // and resume + return len; +} + +static void mg_tcpip_driver_tm4c_update_hash_table(struct mg_tcpip_if *ifp) { + // TODO(): read database, rebuild hash table + // add mDNS / DNS-SD multicast address + EMAC->EMACADDR1L = (uint32_t) mcast_addr[3] << 24 | + (uint32_t) mcast_addr[2] << 16 | + (uint32_t) mcast_addr[1] << 8 | (uint32_t) mcast_addr[0]; + EMAC->EMACADDR1H = (uint32_t) mcast_addr[5] << 8 | (uint32_t) mcast_addr[4]; + EMAC->EMACADDR1H |= MG_BIT(31); // AE + (void) ifp; +} + +static bool mg_tcpip_driver_tm4c_poll(struct mg_tcpip_if *ifp, bool s1) { + if (ifp->update_mac_hash_table) { + mg_tcpip_driver_tm4c_update_hash_table(ifp); + ifp->update_mac_hash_table = false; + } + if (!s1) return false; + 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; +} + +void EMAC0_IRQHandler(void); +static uint32_t s_rxno; +void EMAC0_IRQHandler(void) { + int i; + if (EMAC->EMACDMARIS & MG_BIT(6)) { // Frame received, loop + EMAC->EMACDMARIS = MG_BIT(16) | MG_BIT(6); // Clear flag + for (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); + } + 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 +} + +struct mg_tcpip_driver mg_tcpip_driver_tm4c = {mg_tcpip_driver_tm4c_init, + mg_tcpip_driver_tm4c_tx, NULL, + mg_tcpip_driver_tm4c_poll}; +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/drivers/tms570.c" +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_TMS570) && MG_ENABLE_DRIVER_TMS570 +struct tms570_emac_ctrl { + volatile uint32_t REVID, SOFTRESET, RESERVED1[1], INTCONTROL, C0RXTHRESHEN, + C0RXEN, C0TXEN, C0MISCEN, RESERVED2[8], + C0RXTHRESHSTAT, C0RXSTAT, C0TXSTAT, C0MISCSTAT, + RESERVED3[8], + C0RXIMAX, C0TXIMAX; +}; +struct tms570_emac { + volatile uint32_t TXREVID, TXCONTROL, TXTEARDOWN, RESERVED1[1], RXREVID, + RXCONTROL, RXTEARDOWN, RESERVED2[25], TXINTSTATRAW,TXINTSTATMASKED, + TXINTMASKSET, TXINTMASKCLEAR, MACINVECTOR, MACEOIVECTOR, RESERVED8[2], RXINTSTATRAW, + RXINTSTATMASKED, RXINTMASKSET, RXINTMASKCLEAR, MACINTSTATRAW, MACINTSTATMASKED, + MACINTMASKSET, MACINTMASKCLEAR, RESERVED3[16], RXMBPENABLE, RXUNICASTSET, + RXUNICASTCLEAR, RXMAXLEN, RXBUFFEROFFSET, RXFILTERLOWTHRESH, RESERVED9[2], RXFLOWTHRESH[8], + RXFREEBUFFER[8], MACCONTROL, MACSTATUS, EMCONTROL, FIFOCONTROL, MACCONFIG, + SOFTRESET, RESERVED4[22], MACSRCADDRLO, MACSRCADDRHI, MACHASH1, MACHASH2, + BOFFTEST, TPACETEST, RXPAUSE, TXPAUSE, RESERVED5[4], RXGOODFRAMES, RXBCASTFRAMES, + RXMCASTFRAMES, RXPAUSEFRAMES, RXCRCERRORS, RXALIGNCODEERRORS, RXOVERSIZED, + RXJABBER, RXUNDERSIZED, RXFRAGMENTS, RXFILTERED, RXQOSFILTERED, RXOCTETS, + TXGOODFRAMES, TXBCASTFRAMES, TXMCASTFRAMES, TXPAUSEFRAMES, TXDEFERRED, + TXCOLLISION, TXSINGLECOLL, TXMULTICOLL, TXEXCESSIVECOLL, TXLATECOLL, + TXUNDERRUN, TXCARRIERSENSE, TXOCTETS, FRAME64, FRAME65T127, FRAME128T255, + FRAME256T511, FRAME512T1023, FRAME1024TUP, NETOCTETS, RXSOFOVERRUNS, + RXMOFOVERRUNS, RXDMAOVERRUNS, RESERVED6[156], MACADDRLO, MACADDRHI, + MACINDEX, RESERVED7[61], TXHDP[8], RXHDP[8], TXCP[8], RXCP[8]; +}; +struct tms570_mdio { + volatile uint32_t REVID, CONTROL, ALIVE, LINK, LINKINTRAW, LINKINTMASKED, + RESERVED1[2], USERINTRAW, USERINTMASKED, USERINTMASKSET, USERINTMASKCLEAR, + RESERVED2[20], USERACCESS0, USERPHYSEL0, USERACCESS1, USERPHYSEL1; +}; +#define SWAP32(x) ( (((x) & 0x000000FF) << 24) | \ + (((x) & 0x0000FF00) << 8) | \ + (((x) & 0x00FF0000) >> 8) | \ + (((x) & 0xFF000000) >> 24) ) +#undef EMAC +#undef EMAC_CTRL +#undef MDIO +#define EMAC ((struct tms570_emac *) (uintptr_t) 0xFCF78000) +#define EMAC_CTRL ((struct tms570_emac_ctrl *) (uintptr_t) 0xFCF78800) +#define MDIO ((struct tms570_mdio *) (uintptr_t) 0xFCF78900) +#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_txdesc[ETH_DESC_CNT][ETH_DS] + __attribute__((section(".ETH_CPPI"), aligned(4))); // TX descriptors +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS] + __attribute__((section(".ETH_CPPI"), aligned(4))); // RX descriptors +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE] + __attribute__((aligned(4))); // RX ethernet buffers +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE] + __attribute__((aligned(4))); // TX ethernet buffers +static struct mg_tcpip_if *s_ifp; // MIP interface +static uint16_t emac_read_phy(uint8_t addr, uint8_t reg) { + while(MDIO->USERACCESS0 & MG_BIT(31)) (void) 0; + MDIO->USERACCESS0 = MG_BIT(31) | ((reg & 0x1f) << 21) | + ((addr & 0x1f) << 16); + while(MDIO->USERACCESS0 & MG_BIT(31)) (void) 0; + return MDIO->USERACCESS0 & 0xffff; +} +static void emac_write_phy(uint8_t addr, uint8_t reg, uint16_t val) { + while(MDIO->USERACCESS0 & MG_BIT(31)) (void) 0; + MDIO->USERACCESS0 = MG_BIT(31) | MG_BIT(30) | ((reg & 0x1f) << 21) | + ((addr & 0x1f) << 16) | (val & 0xffff); + while(MDIO->USERACCESS0 & MG_BIT(31)) (void) 0; +} +static bool mg_tcpip_driver_tms570_init(struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_tms570_data *d = + (struct mg_tcpip_driver_tms570_data *) ifp->driver_data; + s_ifp = ifp; + EMAC_CTRL->SOFTRESET = MG_BIT(0); // Reset the EMAC Control Module + while(EMAC_CTRL->SOFTRESET & MG_BIT(0)) (void) 0; // wait + EMAC->SOFTRESET = MG_BIT(0); // Reset the EMAC Module + while(EMAC->SOFTRESET & MG_BIT(0)) (void) 0; + EMAC->MACCONTROL = 0; + EMAC->RXCONTROL = 0; + EMAC->TXCONTROL = 0; + // Initialize all the header descriptor pointer registers + uint32_t i; + for(i = 0; i < ETH_DESC_CNT; i++) { + EMAC->RXHDP[i] = 0; + EMAC->TXHDP[i] = 0; + EMAC->RXCP[i] = 0; + EMAC->TXCP[i] = 0; + ///EMAC->RXFREEBUFFER[i] = 0xff; + } + // Clear the interrupt enable for all the channels + EMAC->TXINTMASKCLEAR = 0xff; + EMAC->RXINTMASKCLEAR = 0xff; + EMAC->MACHASH1 = 0; + EMAC->MACHASH2 = 0; + EMAC->RXBUFFEROFFSET = 0; + EMAC->RXUNICASTCLEAR = 0xff; + EMAC->RXUNICASTSET = 0; + EMAC->RXMBPENABLE = 0; + // init MDIO + // MDIO_CLK frequency = VCLK3/(CLKDIV + 1). (MDIO must be between 1.0 - 2.5Mhz) + uint32_t clkdiv = 75; // VCLK is configured to 75Mhz + // CLKDIV, ENABLE, PREAMBLE, FAULTENB + MDIO->CONTROL = (clkdiv - 1) | MG_BIT(30) | MG_BIT(20) | MG_BIT(18); + volatile int delay = 0xfff; + while (delay-- != 0) (void) 0; + struct mg_phy phy = {emac_read_phy, emac_write_phy}; + mg_phy_init(&phy, d->phy_addr, MG_PHY_CLOCKS_MAC); + uint32_t channel; + for (channel = 0; channel < 8; channel++) { + EMAC->MACINDEX = channel; + EMAC->MACADDRHI = ifp->mac[0] | (ifp->mac[1] << 8) | (ifp->mac[2] << 16) | + (ifp->mac[3] << 24); + EMAC->MACADDRLO = ifp->mac[4] | (ifp->mac[5] << 8) | MG_BIT(20) | + MG_BIT(19) | (channel << 16); + } + EMAC->RXUNICASTSET = 1; // accept unicast frames; + + EMAC->RXMBPENABLE |= MG_BIT(30) | MG_BIT(13); // CRC, broadcast + + // Initialize the descriptors + for (i = 0; i < ETH_DESC_CNT; i++) { + if (i < ETH_DESC_CNT - 1) { + s_txdesc[i][0] = 0; + s_rxdesc[i][0] = SWAP32(((uint32_t) &s_rxdesc[i + 1][0])); + } + s_txdesc[i][1] = SWAP32(((uint32_t) s_txbuf[i])); + s_rxdesc[i][1] = SWAP32(((uint32_t) s_rxbuf[i])); + s_txdesc[i][2] = 0; + s_rxdesc[i][2] = SWAP32(ETH_PKT_SIZE); + s_txdesc[i][3] = 0; + s_rxdesc[i][3] = SWAP32(MG_BIT(29)); // OWN + } + s_txdesc[ETH_DESC_CNT - 1][0] = 0; + s_rxdesc[ETH_DESC_CNT - 1][0] = 0; + + EMAC->MACCONTROL = MG_BIT(5) | MG_BIT(0); // Enable MII, Full-duplex + //EMAC->TXINTMASKSET = 1; // Enable TX interrupt + EMAC->RXINTMASKSET = 1; // Enable RX interrupt + //EMAC_CTRL->C0TXEN = 1; // TX completion interrupt + EMAC_CTRL->C0RXEN = 1; // RX completion interrupt + EMAC->TXCONTROL = 1; // TXEN + EMAC->RXCONTROL = 1; // RXEN + EMAC->RXHDP[0] = (uint32_t) &s_rxdesc[0][0]; + return true; +} +static uint32_t s_txno; +static size_t mg_tcpip_driver_tms570_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][3] & SWAP32(MG_BIT(29)))) { + ifp->nerr++; + MG_ERROR(("No descriptors available")); + len = 0; // fail + } else { + memcpy(s_txbuf[s_txno], buf, len); // Copy data + if (len < 128) { + memset(s_txbuf[s_txno] + len, 0, 128 - len); + len = 128; + } + s_txdesc[s_txno][2] = SWAP32((uint32_t) len); // Set data len + s_txdesc[s_txno][3] = + SWAP32(MG_BIT(31) | MG_BIT(30) | MG_BIT(29) | len); // SOP, EOP, OWN, length + + while(EMAC->TXHDP[0] != 0) (void) 0; + EMAC->TXHDP[0] = (uint32_t) &s_txdesc[s_txno][0]; + if(++s_txno == ETH_DESC_CNT) { + s_txno = 0; + } + } + return len; + (void) ifp; +} + +static void mg_tcpip_driver_tms570_update_hash_table(struct mg_tcpip_if *ifp) { + // TODO(): read database, rebuild hash table + // Setting Hash Index for 01:00:5e:00:00:fb (multicast) + // using TMS570 XOR method (32.5.37). + // computed hash is 55, which means bit 23 (55 - 32) in + // HASH2 register must be set + EMAC->MACHASH2 = MG_BIT(23); + EMAC->RXMBPENABLE = MG_BIT(5); // enable hash filtering + (void) ifp; +} + +static bool mg_tcpip_driver_tms570_poll(struct mg_tcpip_if *ifp, bool s1) { + if (ifp->update_mac_hash_table) { + mg_tcpip_driver_tms570_update_hash_table(ifp); + ifp->update_mac_hash_table = false; + } + if (!s1) return false; + struct mg_tcpip_driver_tms570_data *d = + (struct mg_tcpip_driver_tms570_data *) ifp->driver_data; + uint8_t speed = MG_PHY_SPEED_10M; + bool up = false, full_duplex = false; + struct mg_phy phy = {emac_read_phy, emac_write_phy}; + if (!s1) return false; + 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; +} + +#pragma CODE_STATE(EMAC_TX_IRQHandler, 32) +#pragma INTERRUPT(EMAC_TX_IRQHandler, IRQ) +void EMAC_TX_IRQHandler(void) { + uint32_t status = EMAC_CTRL->C0TXSTAT; + if (status & 1) { // interrupt caused on channel 0 + while(s_txdesc[s_txno][3] & SWAP32(MG_BIT(29))) (void) 0; + EMAC->TXCP[0] = (uint32_t) &s_txdesc[s_txno][0]; + } + //Write the DMA end of interrupt vector + EMAC->MACEOIVECTOR = 2; +} +static uint32_t s_rxno; +#pragma CODE_STATE(EMAC_RX_IRQHandler, 32) +#pragma INTERRUPT(EMAC_RX_IRQHandler, IRQ) +void EMAC_RX_IRQHandler(void) { + uint32_t status = EMAC_CTRL->C0RXSTAT; + if (status & 1) { // Frame received, loop + uint32_t i; + //MG_INFO(("RX interrupt")); + for (i = 0; i < 10; i++) { // read as they arrive but not forever + if (s_rxdesc[s_rxno][3] & SWAP32(MG_BIT(29))) break; + uint32_t len = SWAP32(s_rxdesc[s_rxno][3]) & 0xffff; + //MG_INFO(("recv len: %d", len)); + //mg_hexdump(s_rxbuf[s_rxno], len); + mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp); + uint32_t flags = s_rxdesc[s_rxno][3]; + s_rxdesc[s_rxno][3] = SWAP32(MG_BIT(29)); + s_rxdesc[s_rxno][2] = SWAP32(ETH_PKT_SIZE); + EMAC->RXCP[0] = (uint32_t) &s_rxdesc[s_rxno][0]; + if (flags & SWAP32(MG_BIT(28))) { + //MG_INFO(("EOQ detected")); + EMAC->RXHDP[0] = (uint32_t) &s_rxdesc[0][0]; + } + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0; + } + } + //Write the DMA end of interrupt vector + EMAC->MACEOIVECTOR = 1; +} +struct mg_tcpip_driver mg_tcpip_driver_tms570 = {mg_tcpip_driver_tms570_init, + mg_tcpip_driver_tms570_tx, NULL, + mg_tcpip_driver_tms570_poll}; +#endif + + +#ifdef MG_ENABLE_LINES +#line 1 "src/drivers/w5100.c" +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_W5100) && MG_ENABLE_DRIVER_W5100 + +static void w5100_txn(struct mg_tcpip_spi *s, uint16_t addr, bool wr, void *buf, + size_t len) { + uint8_t control = wr ? 0xF0 : 0x0F; + uint8_t cmd[] = {control, (uint8_t) (addr >> 8), (uint8_t) (addr & 255)}; + s->begin(s->spi); + s->txn(s->spi, cmd, NULL, sizeof(cmd)); + if (wr) s->txn(s->spi, (uint8_t *) buf, NULL, len); + if (!wr) s->txn(s->spi, NULL, (uint8_t *) buf, len); + s->end(s->spi); +} + +// clang-format off +static void w5100_wn(struct mg_tcpip_spi *s, uint16_t addr, void *buf, size_t len) { w5100_txn(s, addr, true, buf, len); } +static void w5100_w1(struct mg_tcpip_spi *s, uint16_t addr, uint8_t val) { w5100_wn(s, addr, &val, 1); } +static void w5100_w2(struct mg_tcpip_spi *s, uint16_t addr, uint16_t val) { uint8_t buf[2] = {(uint8_t) (val >> 8), (uint8_t) (val & 255)}; w5100_wn(s, addr, buf, sizeof(buf)); } +static void w5100_rn(struct mg_tcpip_spi *s, uint16_t addr, void *buf, size_t len) { w5100_txn(s, addr, false, buf, len); } +static uint8_t w5100_r1(struct mg_tcpip_spi *s, uint16_t addr) { uint8_t r = 0; w5100_rn(s, addr, &r, 1); return r; } +static uint16_t w5100_r2(struct mg_tcpip_spi *s, uint16_t addr) { uint8_t buf[2] = {0, 0}; w5100_rn(s, addr, buf, sizeof(buf)); return (uint16_t) ((buf[0] << 8) | buf[1]); } +// clang-format on + +static size_t w5100_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 = w5100_r2(s, 0x426)) > n) n = n2; // Until it is stable + if (n > 0) { + uint16_t ptr = w5100_r2(s, 0x428); // Get read pointer + if (n <= len + 2 && n > 1) { + r = (uint16_t) (n - 2); + } + uint16_t rxbuf_size = (1 << (w5100_r1(s, 0x1a) & 3)) * 1024; + uint16_t rxbuf_addr = 0x6000; + uint16_t ptr_ofs = (ptr + 2) & (rxbuf_size - 1); + if (ptr_ofs + r < rxbuf_size) { + w5100_rn(s, rxbuf_addr + ptr_ofs, buf, r); + } else { + uint16_t remaining_len = rxbuf_size - ptr_ofs; + w5100_rn(s, rxbuf_addr + ptr_ofs, buf, remaining_len); + w5100_rn(s, rxbuf_addr, buf + remaining_len, r - remaining_len); + } + w5100_w2(s, 0x428, (uint16_t) (ptr + n)); + w5100_w1(s, 0x401, 0x40); // Sock0 CR -> RECV + } + return r; +} + +static size_t w5100_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, n = 0, ptr = 0, len = (uint16_t) buflen; + while (n < len) n = w5100_r2(s, 0x420); // Wait for space + ptr = w5100_r2(s, 0x424); // Get write pointer + uint16_t txbuf_size = (1 << (w5100_r1(s, 0x1b) & 3)) * 1024; + uint16_t ptr_ofs = ptr & (txbuf_size - 1); + uint16_t txbuf_addr = 0x4000; + if (ptr_ofs + len > txbuf_size) { + uint16_t size = txbuf_size - ptr_ofs; + w5100_wn(s, txbuf_addr + ptr_ofs, (char *) buf, size); + w5100_wn(s, txbuf_addr, (char *) buf + size, len - size); + } else { + w5100_wn(s, txbuf_addr + ptr_ofs, (char *) buf, len); + } + w5100_w2(s, 0x424, (uint16_t) (ptr + len)); // Advance write pointer + w5100_w1(s, 0x401, 0x20); // Sock0 CR -> SEND + for (i = 0; i < 40; i++) { + uint8_t ir = w5100_r1(s, 0x402); // Read S0 IR + if (ir == 0) continue; + // printf("IR %d, len=%d, free=%d, ptr %d\n", ir, (int) len, (int) n, ptr); + w5100_w1(s, 0x402, ir); // Write S0 IR: clear it! + if (ir & 8) len = 0; // Timeout. Report error + if (ir & (16 | 8)) break; // Stop on SEND_OK or timeout + } + return len; +} + +static bool w5100_init(struct mg_tcpip_if *ifp) { + struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) ifp->driver_data; + s->end(s->spi); + w5100_w1(s, 0, 0x80); // Reset chip: CR -> 0x80 + w5100_w1(s, 0x72, 0x53); // CR PHYLCKR -> unlock PHY + w5100_w1(s, 0x46, 0); // CR PHYCR0 -> autonegotiation + w5100_w1(s, 0x47, 0); // CR PHYCR1 -> reset + w5100_w1(s, 0x72, 0x00); // CR PHYLCKR -> lock PHY + w5100_wn(s, 0x09, ifp->mac, 6); // SHAR + w5100_w1(s, 0x1a, 6); // Sock0 RX buf size - 4KB + w5100_w1(s, 0x1b, 6); // Sock0 TX buf size - 4KB + w5100_w1(s, 0x400, 0x44); // Sock0 MR -> MACRAW, MAC filter + w5100_w1(s, 0x401, 1); // Sock0 CR -> OPEN + return w5100_r1(s, 0x403) == 0x42; // Sock0 SR == MACRAW +} + +static bool w5100_poll(struct mg_tcpip_if *ifp, bool s1) { + struct mg_tcpip_spi *spi = (struct mg_tcpip_spi *) ifp->driver_data; + return s1 ? w5100_r1(spi, 0x3c /* PHYSR */) & 1 + : false; // Bit 0 of PHYSR is LNK (0 - down, 1 - up) +} + +struct mg_tcpip_driver mg_tcpip_driver_w5100 = {w5100_init, w5100_tx, w5100_rx, + w5100_poll}; +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/drivers/w5500.c" +#endif + + +#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) { + uint8_t cmd[] = {(uint8_t) (addr >> 8), (uint8_t) (addr & 255), + (uint8_t) ((block << 3) | (wr ? 4 : 0))}; + s->begin(s->spi); + s->txn(s->spi, cmd, NULL, sizeof(cmd)); + if (wr) s->txn(s->spi, (uint8_t *) buf, NULL, len); + if (!wr) s->txn(s->spi, NULL, (uint8_t *) buf, len); + s->end(s->spi); +} + +// clang-format off +static void w5500_wn(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr, void *buf, size_t len) { w5500_txn(s, block, addr, true, buf, len); } +static void w5500_w1(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr, uint8_t val) { w5500_wn(s, block, addr, &val, 1); } +static void w5500_w2(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr, uint16_t val) { uint8_t buf[2] = {(uint8_t) (val >> 8), (uint8_t) (val & 255)}; w5500_wn(s, block, addr, buf, sizeof(buf)); } +static void w5500_rn(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr, void *buf, size_t len) { w5500_txn(s, block, addr, false, buf, len); } +static uint8_t w5500_r1(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr) { uint8_t r = 0; w5500_rn(s, block, addr, &r, 1); return r; } +static uint16_t w5500_r2(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr) { uint8_t buf[2] = {0, 0}; w5500_rn(s, block, addr, buf, sizeof(buf)); return (uint16_t) ((buf[0] << 8) | buf[1]); } +// clang-format on + +static size_t w5500_rx(void *buf, size_t buflen, struct mg_tcpip_if *ifp) { + struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) ifp->driver_data; + uint16_t r = 0, n = 0, len = (uint16_t) buflen, n2; // Read recv len + while ((n2 = w5500_r2(s, W5500_S0, 0x26)) > n) n = n2; // Until it is stable + // printf("RSR: %d\n", (int) n); + if (n > 0) { + uint16_t ptr = w5500_r2(s, W5500_S0, 0x28); // Get read pointer + n = w5500_r2(s, W5500_RX0, ptr); // Read frame length + if (n <= len + 2 && n > 1) { + r = (uint16_t) (n - 2); + w5500_rn(s, W5500_RX0, (uint16_t) (ptr + 2), buf, r); + } + w5500_w2(s, W5500_S0, 0x28, (uint16_t) (ptr + n)); // Advance read pointer + w5500_w1(s, W5500_S0, 1, 0x40); // Sock0 CR -> RECV + // printf(" RX_RD: tot=%u n=%u r=%u\n", n2, n, r); + } + return r; +} + +static size_t w5500_tx(const void *buf, size_t buflen, + struct mg_tcpip_if *ifp) { + struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) ifp->driver_data; + uint16_t 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 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_poll(struct mg_tcpip_if *ifp, bool s1) { + struct mg_tcpip_spi *spi = (struct mg_tcpip_spi *) ifp->driver_data; + return s1 ? w5500_r1(spi, W5500_CR, 0x2e /* PHYCFGR */) & 1 + : false; // 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_poll}; +#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] MG_ETH_RAM; +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_ETH_RAM; +static uint32_t s_rxdesc[ETH_DESC_CNT] + [ETH_DS] MG_ETH_RAM; // RX descriptors +static uint32_t s_txdesc[ETH_DESC_CNT] + [ETH_DS] MG_ETH_RAM; // 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 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; + + // 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); // Perfect filter + // 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]; + } + } + ETH0->RECEIVE_DESCRIPTOR_LIST_ADDRESS = (uint32_t) &s_rxdesc[0][0]; + + // 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]; + + // Clear interrupts + ETH0->STATUS = 0xFFFFFFFF; + + // 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; +} + +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 { + 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; + } + + // Resume processing + ETH0->STATUS = MG_BIT(2); // clear Transmit unavailable + ETH0->TRANSMIT_POLL_DEMAND = 0; + return len; +} + +static void mg_tcpip_driver_xmc_update_hash_table(struct mg_tcpip_if *ifp) { + // TODO(): read database, rebuild hash table + // set the multicast address filter + ETH0->MAC_ADDRESS1_HIGH = + MG_U32(0, 0, mcast_addr[5], mcast_addr[4]) | MG_BIT(31); + ETH0->MAC_ADDRESS1_LOW = + MG_U32(mcast_addr[3], mcast_addr[2], mcast_addr[1], mcast_addr[0]); + (void) ifp; +} + +static bool mg_tcpip_driver_xmc_poll(struct mg_tcpip_if *ifp, bool s1) { + if (ifp->update_mac_hash_table) { + mg_tcpip_driver_xmc_update_hash_table(ifp); + ifp->update_mac_hash_table = false; + } + if (!s1) return false; + 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_0_IRQHandler(void); +void ETH0_0_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 < 10; i++) { // read as they arrive, but not forever + if (s_rxdesc[s_rxno][0] & MG_BIT(31)) break; + 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); + } + + // clear Successful transmission interrupt + if (irq_status & 1) { + ETH0->STATUS = 1; + } + + // clear normal interrupt + if (irq_status & MG_BIT(16)) { + ETH0->STATUS = MG_BIT(16); + } +} + +struct mg_tcpip_driver mg_tcpip_driver_xmc = {mg_tcpip_driver_xmc_init, + mg_tcpip_driver_xmc_tx, NULL, + mg_tcpip_driver_xmc_poll}; +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/drivers/xmc7.c" +#endif + + +#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; +}; + +#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] MG_ETH_RAM; +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_ETH_RAM; +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS] MG_ETH_RAM MG_8BYTE_ALIGNED; +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS] MG_ETH_RAM MG_8BYTE_ALIGNED; +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 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 uint32_t get_clock_rate(struct mg_tcpip_driver_xmc7_data *d) { + // see ETH0 -> NETWORK_CONFIG register + (void) d; + return 3; +} + +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) | (4 << 8) | 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(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 + } + } + 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 (RX complete) + ETH0->INT_ENABLE = 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; +} + +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 { + 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 + + ETH0->NETWORK_CONTROL |= MG_BIT(9); // enable transmission + if (++s_txno >= ETH_DESC_CNT) s_txno = 0; + } + + MG_DSB(); + ETH0->TRANSMIT_STATUS = ETH0->TRANSMIT_STATUS; + ETH0->NETWORK_CONTROL |= MG_BIT(9); // enable transmission + + return len; +} + +static void mg_tcpip_driver_xmc7_update_hash_table(struct mg_tcpip_if *ifp) { + // TODO(): read database, rebuild hash table + // set multicast MAC address + ETH0->SPEC_ADD2_BOTTOM = mcast_addr[3] << 24 | mcast_addr[2] << 16 | + mcast_addr[1] << 8 | mcast_addr[0]; + ETH0->SPEC_ADD2_TOP = mcast_addr[5] << 8 | mcast_addr[4]; + (void) ifp; +} + +static bool mg_tcpip_driver_xmc7_poll(struct mg_tcpip_if *ifp, bool s1) { + if (ifp->update_mac_hash_table) { + mg_tcpip_driver_xmc7_update_hash_table(ifp); + ifp->update_mac_hash_table = false; + } + if (!s1) return false; + 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 + // 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 netconf = ETH0->NETWORK_CONFIG; + MG_SET_BITS(netconf, MG_BIT(10), + MG_BIT(1) | MG_BIT(0)); // 100M, Full-duplex + uint32_t ctl = ETH0->CTL; + MG_SET_BITS(ctl, 0xFF00, 4 << 8); // /5 for 25M clock + if (speed == MG_PHY_SPEED_1000M) { + netconf |= MG_BIT(10); // 1000M + MG_SET_BITS(ctl, 0xFF00, 0); // /1 for 125M clock TODO() IS THIS NEEDED ? + } else if (speed == MG_PHY_SPEED_10M) { + netconf &= ~MG_BIT(0); // 10M + MG_SET_BITS(ctl, 0xFF00, 49); // /50 for 2.5M clock + } + if (full_duplex == false) netconf &= ~MG_BIT(1); // Half-duplex + ETH0->NETWORK_CONFIG = netconf; // IRQ handler does not fiddle with these + ETH0->CTL = ctl; + MG_DEBUG(("Link is %uM %s-duplex", + speed == MG_PHY_SPEED_10M + ? 10 + : (speed == MG_PHY_SPEED_100M ? 100 : 1000), + full_duplex ? "full" : "half")); + } + return up; +} + +void ETH_IRQHandler(void) { + uint32_t irq_status = ETH0->INT_STATUS; + if (irq_status & MG_BIT(1)) { + for (uint8_t i = 0; i < 10; i++) { // read as they arrive, but not forever + 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); // OWN bit: handle control to DMA + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0; + } + } + + ETH0->INT_STATUS = irq_status; +} + +struct mg_tcpip_driver mg_tcpip_driver_xmc7 = {mg_tcpip_driver_xmc7_init, + mg_tcpip_driver_xmc7_tx, NULL, + mg_tcpip_driver_xmc7_poll}; +#endif diff --git a/lib/mongoose/mongoose.h b/lib/mongoose/mongoose.h new file mode 100644 index 0000000..4c879fa --- /dev/null +++ b/lib/mongoose/mongoose.h @@ -0,0 +1,5746 @@ +// Copyright (c) 2004-2013 Sergey Lyubka +// Copyright (c) 2013-2025 Cesanta Software Limited +// All rights reserved +// +// This software is dual-licensed: you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. For the terms of this +// license, see http://www.gnu.org/licenses/ +// +// You are free to use this software under the terms of the GNU General +// Public License, but WITHOUT ANY WARRANTY; without even the implied +// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// Alternatively, you can license this software under a commercial +// license, as set out in https://www.mongoose.ws/licensing/ +// +// SPDX-License-Identifier: GPL-2.0-only or commercial + +#ifndef MONGOOSE_H +#define MONGOOSE_H + +#define MG_VERSION "7.22" + +#ifdef __cplusplus +extern "C" { +#endif + + +#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 <-- DEPRECATED !!! + +#define MG_ARCH_THREADX 6 // Eclipse ThreadX (former MS Azure RTOS) +#define MG_ARCH_ZEPHYR 7 // Zephyr RTOS +#define MG_ARCH_ARMGCC 8 // Plain ARM GCC +#define MG_ARCH_CMSIS_RTOS1 9 // CMSIS-RTOS API v1 (Keil RTX) +#define MG_ARCH_TIRTOS 10 // Texas Semi TI-RTOS +#define MG_ARCH_PICOSDK 11 // Raspberry Pi Pico-SDK (RP2040, RP2350) +#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 +#define MG_ARCH_ARMCGT 15 // Texas Semi ARM-CGT +#define MG_ARCH_CUBE 16 // STM32Cube environment + +#define MG_ARCH_NEWLIB MG_ARCH_ARMGCC // Alias, deprecate in 2025 + +#if !defined(MG_ARCH) +#if defined(__unix__) || defined(__APPLE__) +#define MG_ARCH MG_ARCH_UNIX +#elif defined(_WIN32) +#define MG_ARCH MG_ARCH_WIN32 +#endif +#endif // !defined(MG_ARCH) + +#if !defined(MG_ARCH) || (MG_ARCH == MG_ARCH_CUSTOM) +#include "mongoose_config.h" // keep this include +#endif + +#if !defined(MG_ARCH) +#error "MG_ARCH is not specified and we couldn't guess it. Define MG_ARCH=... in mongoose_config.h" +#elif MG_ARCH == MG_ARCH_FREERTOS +#error "MG_ARCH_FREERTOS has been deprecated, set MG_ARCH=your_build_environment and MG_ENABLE_FREERTOS=1 instead" +#undef MG_ARCH // avoid errors piling up, provide a clean environment so the error above is seen +#define MG_ARCH MG_ARCH_ARMGCC +#define MG_ENABLE_FREERTOS 1 +#endif + +// http://esr.ibiblio.org/?p=5095 +#define MG_BIG_ENDIAN (*(uint16_t *) "\0\xff" < 0x100) + + + + + + + + + + + + + + + + + + +#if MG_ARCH == MG_ARCH_ARMCGT + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MG_PATH_MAX 100 +#define MG_ENABLE_DIRLIST 0 + +#endif + + +#if MG_ARCH == MG_ARCH_ARMGCC +#define _POSIX_TIMERS + +#include +#if !defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MG_PATH_MAX 100 +#define MG_ENABLE_DIRLIST 0 + +#endif + + +#if MG_ARCH == MG_ARCH_CUBE + +#include +#if !defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Cube-generated header, includes ST Cube HAL +// NOTE: use angle brackets to prevent amalgamator ditching it +#include + +#ifndef MG_PATH_MAX +#define MG_PATH_MAX 100 +#endif + +#ifndef MG_ENABLE_DIRLIST +#define MG_ENABLE_DIRLIST 0 +#endif + + +#ifndef MG_ENABLE_TCPIP +#define MG_ENABLE_TCPIP 1 // Enable built-in TCP/IP stack +#endif + +#if MG_ENABLE_TCPIP && !defined(MG_ENABLE_DRIVER_STM32F) && \ + !defined(MG_ENABLE_DRIVER_STM32H) && !defined(MG_ENABLE_DRIVER_STM32N) && \ + !defined(MG_ENABLE_DRIVER_CYW) && !defined(MG_ENABLE_DRIVER_CYW_SDIO) && \ + !defined(MG_ENABLE_DRIVER_ST67W6) +#if defined(STM32F1) || defined(STM32F2) || defined(STM32F4) || defined(STM32F7) +#define MG_ENABLE_DRIVER_STM32F 1 +#elif defined(STM32H5) || defined(STM32H7) +#define MG_ENABLE_DRIVER_STM32H 1 +#elif defined(STM32N6) +#define MG_ENABLE_DRIVER_STM32N 1 +#else +#error Select a driver in mongoose_config.h +#endif +#endif + +#ifndef MG_TLS +#define MG_TLS MG_TLS_BUILTIN +#endif + +#if !defined(MG_OTA) && defined(STM32F1) || defined(STM32F2) || \ + defined(STM32F4) || defined(STM32F7) +#define MG_OTA MG_OTA_STM32F +#elif !defined(MG_OTA) && defined(STM32H5) +#define MG_OTA MG_OTA_STM32H5 +#elif !defined(MG_OTA) && defined(STM32H7) +#define MG_OTA MG_OTA_STM32H7 +#endif + +// OTA rollback timer via IWDG. Fixed 10 s hang-detection window (prescaler +// /1024, LSI ~32 kHz: reload = 10 * 32000 / 1024 = 312). mg_ota_poll() feeds +// the watchdog every 500 ms while in MG_OTA_TESTING state; if the event loop +// hangs for 10 s, the IWDG fires and the device rolls back. +// STM32H7 names the peripheral IWDG1; all others use IWDG. +// The total evaluation period is set by MG_OTA_ROLLBACK_TIMEOUT_SECONDS. +// STM32H7 names the watchdog peripheral IWDG1; all other families use IWDG. +#if defined(IWDG1) && !defined(IWDG) +#define MG_IWDG IWDG1 +#else +#define MG_IWDG IWDG +#endif +// PR=6 → /256 prescaler, RLR=1250 → ~10 s at LSI 32 kHz. +// Works on F4/F7/H5/H7; uses direct register writes so it is safe before HAL_Init(). +#ifndef MG_OTA_ROLLBACK_TIMER_START +#define MG_OTA_ROLLBACK_TIMER_START() \ + do { \ + MG_IWDG->KR = 0xCCCCU; /* start + LSI */ \ + MG_IWDG->KR = 0x5555U; /* unlock PR/RLR */ \ + MG_IWDG->PR = 6U; /* /256 */ \ + MG_IWDG->RLR = 1250U; /* 10 s */ \ + while (MG_IWDG->SR & (IWDG_SR_PVU | IWDG_SR_RVU)) (void) 0; \ + MG_IWDG->KR = 0xAAAAU; /* reload */ \ + } while (0) +#endif + +// Feed (reload) the IWDG. Called from mg_ota_poll() every 500 ms. +#ifndef MG_OTA_ROLLBACK_TIMER_FEED +#define MG_OTA_ROLLBACK_TIMER_FEED() (MG_IWDG->KR = 0xAAAAU) +#endif + +// OTA state in a backup register that survives warm resets but clears on POR. +// STATE_GET reads work without any setup. STATE_SET enables the RTC APB clock +// and unlocks backup domain write access via direct register writes, so it is +// safe to call from any context (before or after HAL_Init). +// H5 (TAMP->BKP0R), H7 (RTC->BKP0R), F4/F7 (RTC->BKP0R). +#ifndef MG_OTA_STATE_GET +#if defined(PWR_DBPCR_DBP) +#define MG_OTA_DBP_ENABLE() (PWR->DBPCR |= PWR_DBPCR_DBP) +#elif defined(PWR_CR1_DBP) +#define MG_OTA_DBP_ENABLE() (PWR->CR1 |= PWR_CR1_DBP) +#else +#define MG_OTA_DBP_ENABLE() (PWR->CR |= PWR_CR_DBP) +#endif + +#if defined(TAMP) +#define MG_OTA_STATE_GET() \ + (RCC->APB3ENR |= RCC_APB3ENR_RTCAPBEN, TAMP->BKP0R) +#define MG_OTA_STATE_SET(v) \ + (RCC->APB3ENR |= RCC_APB3ENR_RTCAPBEN, MG_OTA_DBP_ENABLE(), \ + (TAMP->BKP0R = (uint32_t) (v)), TAMP->BKP0R) +#elif defined(RCC_APB4ENR_RTCAPBEN) +#define MG_OTA_STATE_GET() (RTC->BKP0R) +#define MG_OTA_STATE_SET(v) \ + (RCC->APB4ENR |= RCC_APB4ENR_RTCAPBEN, MG_OTA_DBP_ENABLE(), \ + (RTC->BKP0R = (uint32_t) (v))) +#else +#define MG_OTA_STATE_GET() (RTC->BKP0R) +#define MG_OTA_STATE_SET(v) \ + (RCC->APB1ENR |= RCC_APB1ENR_PWREN, MG_OTA_DBP_ENABLE(), \ + (RTC->BKP0R = (uint32_t) (v))) +#endif +#endif + +// use HAL-defined execute-in-ram section +#ifndef MG_IRAM +#define MG_IRAM __attribute__((section(".RamFunc"))) +#endif + +#ifndef MG_ETH_RAM +#define MG_ETH_RAM __attribute__((section(".eth_ram"))) +#endif + +#ifndef HAL_ICACHE_MODULE_ENABLED +#define HAL_ICACHE_IsEnabled() 0 +#define HAL_ICACHE_Enable() (void) 0 +#define HAL_ICACHE_Disable() (void) 0 +#endif + +#ifndef MG_SET_MAC_ADDRESS +// Construct MAC address from UUID +#define MGUID ((uint32_t *) UID_BASE) // Unique 96-bit chip ID +#define MG_SET_MAC_ADDRESS(mac) \ + do { \ + int icache_enabled_ = HAL_ICACHE_IsEnabled(); \ + if (icache_enabled_) HAL_ICACHE_Disable(); \ + mac[0] = 42; \ + mac[1] = ((MGUID[0] >> 0) & 255) ^ ((MGUID[2] >> 19) & 255); \ + mac[2] = ((MGUID[0] >> 10) & 255) ^ ((MGUID[1] >> 10) & 255); \ + mac[3] = (MGUID[0] >> 19) & 255; \ + mac[4] = ((MGUID[1] >> 0) & 255) ^ ((MGUID[2] >> 10) & 255); \ + mac[5] = ((MGUID[2] >> 0) & 255) ^ ((MGUID[1] >> 19) & 255); \ + if (icache_enabled_) HAL_ICACHE_Enable(); \ + } while (0) +#endif + +#endif + + +#if MG_ARCH == MG_ARCH_ESP32 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include // Use angle brackets to avoid +#include // amalgamation ditching them +#include +#include + +#define MG_PATH_MAX 128 + +#ifndef MG_ENABLE_POSIX_FS +#define MG_ENABLE_POSIX_FS 1 +#endif + +#ifndef MG_ENABLE_DIRLIST +#define MG_ENABLE_DIRLIST 1 +#endif + +#endif + + +#if MG_ARCH == MG_ARCH_ESP8266 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define MG_PATH_MAX 128 + +#endif + + +#if MG_ARCH == MG_ARCH_PICOSDK +#if !defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +int mkdir(const char *, mode_t); + +#if MG_OTA == MG_OTA_PICOSDK +#include +#include +#include +#include + +#ifndef MG_OTA_WATCHDOG_MS +#if PICO_RP2040 +#define MG_OTA_WATCHDOG_MS 8000U // max delay on RP2040 is 8.3 seconds +#else +#define MG_OTA_WATCHDOG_MS 10000U // max delay on RP2350 is 16 seconds +#endif +#endif + +#ifndef MG_OTA_ROLLBACK_TIMER_START +#define MG_OTA_ROLLBACK_TIMER_START() watchdog_enable(MG_OTA_WATCHDOG_MS, false) +#endif + +#ifndef MG_OTA_ROLLBACK_TIMER_FEED +#define MG_OTA_ROLLBACK_TIMER_FEED() watchdog_update() +#endif + +#define MG_OTA_STATE_GET() ((uint32_t) watchdog_hw->scratch[0]) +#define MG_OTA_STATE_SET(v) \ + (watchdog_hw->scratch[0] = (uint32_t) (v), (uint32_t) watchdog_hw->scratch[0]) + +#if PICO_RP2040 +#define MG_OTA_ROLLBACK() \ + do { \ + mg_flash->swap_fn(); \ + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; \ + } while (0) +#else +#define MG_OTA_ROLLBACK() \ + do { \ + mg_flash->swap_fn(); \ + ((rom_reboot_fn) rom_func_lookup(ROM_FUNC_REBOOT))( \ + BOOT_TYPE_NORMAL | 0x100, 1, 0, 0); \ + } while (0) +#endif +#endif + +#endif + + +#if MG_ARCH == MG_ARCH_RTTHREAD + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if MG_ARCH == MG_ARCH_CMSIS_RTOS1 +#include "cmsis_os.h" // keep this include +// https://developer.arm.com/documentation/ka003821/latest +extern uint32_t rt_time_get(void); +#elif MG_ARCH == MG_ARCH_CMSIS_RTOS2 +#include "cmsis_os2.h" // keep this include +#endif + +#define strdup(s) ((char *) mg_strdup(mg_str(s)).buf) + +#if defined(__ARMCC_VERSION) +#define mode_t size_t +#if !defined(MG_ENABLE_POSIX_FS) || !MG_ENABLE_POSIX_FS +#else +#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 +#endif + +#if (MG_ARCH == MG_ARCH_CMSIS_RTOS1 || MG_ARCH == MG_ARCH_CMSIS_RTOS2) && \ + !defined MG_ENABLE_RL && (!defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP) && \ + (!defined(MG_ENABLE_TCPIP) || !MG_ENABLE_TCPIP) +#define MG_ENABLE_RL 1 +#ifndef MG_SOCK_LISTEN_BACKLOG_SIZE +#define MG_SOCK_LISTEN_BACKLOG_SIZE 3 +#endif +#endif + +#endif + + +#if MG_ARCH == MG_ARCH_THREADX + +#include +#include +#include +#include + +// Do not include time.h and stdlib.h, since they conflict with nxd_bsd.h +// extern time_t time(time_t *); +#include + +#define MG_DIRSEP '\\' +#undef FOPEN_MAX + +#ifndef MG_PATH_MAX +#define MG_PATH_MAX 32 +#endif + +#ifndef MG_SOCK_LISTEN_BACKLOG_SIZE +#define MG_SOCK_LISTEN_BACKLOG_SIZE 3 +#endif + +#ifndef MG_ENABLE_IPV6 +#define MG_ENABLE_IPV6 0 +#endif + +#define socklen_t int +#define closesocket(x) soc_close(x) + +// In order to enable BSD support in NetxDuo, do the following (assuming Cube): +// 1. Add nxd_bsd.h and nxd_bsd.c to the repo: +// https://github.com/eclipse-threadx/netxduo/blob/v6.1.12_rel/addons/BSD/nxd_bsd.c +// https://github.com/eclipse-threadx/netxduo/blob/v6.1.12_rel/addons/BSD/nxd_bsd.h +// 2. Add to tx_user.h +// #define TX_THREAD_USER_EXTENSION int bsd_errno; +// 3. Add to nx_user.h +// #define NX_ENABLE_EXTENDED_NOTIFY_SUPPORT +// 4. Add __CCRX__ build preprocessor constant +// Project -> Properties -> C/C++ -> Settings -> MCU Compiler -> Preprocessor + +#endif + + +#if MG_ARCH == MG_ARCH_TIRTOS + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#endif + + +#if MG_ARCH == MG_ARCH_UNIX + +#define _DARWIN_UNLIMITED_SELECT 1 // No limit on file descriptors + +#if defined(__APPLE__) +#include +#endif + +#if !defined(MG_ENABLE_EPOLL) && defined(__linux__) +#define MG_ENABLE_EPOLL 1 +#elif !defined(MG_ENABLE_POLL) +#define MG_ENABLE_POLL 1 +#endif + +#include +#include +#include +#if !defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(MG_ENABLE_EPOLL) && MG_ENABLE_EPOLL +#include +#elif defined(MG_ENABLE_POLL) && MG_ENABLE_POLL +#include +#else +#include +#endif + +#include +#include +#include +#include +#include +#include + +#ifndef MG_ENABLE_DIRLIST +#define MG_ENABLE_DIRLIST 1 +#endif + +#ifndef MG_PATH_MAX +#define MG_PATH_MAX FILENAME_MAX +#endif + +#ifndef MG_ENABLE_POSIX_FS +#define MG_ENABLE_POSIX_FS 1 +#endif + +#ifndef MG_IO_SIZE +#define MG_IO_SIZE 16384 +#endif + +#endif + + +#if MG_ARCH == MG_ARCH_WIN32 + +// Avoid name clashing; (macro expansion producing 'defined' has undefined +// behaviour). See config.h for user options +#ifndef MG_ENABLE_WINSOCK +#if (!defined(MG_ENABLE_TCPIP) || !MG_ENABLE_TCPIP) && \ + (!defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP) && \ + (!defined(MG_ENABLE_FREERTOS_TCP) || !MG_ENABLE_FREERTOS_TCP) +#define MG_ENABLE_WINSOCK 1 +#else +#define MG_ENABLE_WINSOCK 0 +#endif +#endif + +#ifndef _CRT_RAND_S +#define _CRT_RAND_S +#endif + +#ifndef _WIN32_WINNT +#if defined(_MSC_VER) && _MSC_VER < 1700 +#define _WIN32_WINNT 0x0400 // Let vc98 pick up wincrypt.h +#else +#define _WIN32_WINNT 0x0600 +#endif +#endif +#ifndef WINVER +#define WINVER _WIN32_WINNT +#endif + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif + +#ifndef _WINSOCK_DEPRECATED_NO_WARNINGS +#define _WINSOCK_DEPRECATED_NO_WARNINGS +#endif + +#include +#include +#if !defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include // fix missing macros and types + +#if defined(_MSC_VER) && _MSC_VER < 1700 +#define __func__ "" +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; +typedef unsigned char uint8_t; +typedef char int8_t; +typedef unsigned short uint16_t; +typedef short int16_t; +typedef unsigned int uint32_t; +typedef int int32_t; +typedef enum { false = 0, true = 1 } bool; +#define inline __inline +#else +#include +#include +#if MG_ENABLE_WINSOCK +#include +#endif +#endif + +#include +#include + +// For mg_random() +#if defined(_MSC_VER) && _MSC_VER < 1700 +#include +#pragma comment(lib, "advapi32.lib") +#endif + +#if defined(_MSC_VER) && _MSC_VER <= 1200 + #ifndef IPPROTO_IP + #define IPPROTO_IP 0 + #endif + + #ifndef IP_ADD_MEMBERSHIP + struct ip_mreq { + struct in_addr imr_multiaddr; + struct in_addr imr_interface; + }; + #define IP_ADD_MEMBERSHIP 12 + #endif +#endif + +// Protect from calls like std::snprintf in app code +// See https://github.com/cesanta/mongoose/issues/1047 +#ifndef __cplusplus +#define snprintf _snprintf +#define vsnprintf _vsnprintf +#ifndef strdup // For MSVC with _DEBUG, see #1359 +#define strdup(x) _strdup(x) +#endif +#endif + +#if defined(MG_ENABLE_POLL) && MG_ENABLE_POLL && (!defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP) +typedef unsigned long nfds_t; // see #3388 +#endif + +#if defined(_MSC_VER) +#if MG_ENABLE_WINSOCK +#pragma comment(lib, "ws2_32.lib") +#endif +#ifndef alloca +#define alloca(a) _alloca(a) +#endif +#endif + +#define MG_DIRSEP '\\' + +#ifndef MG_PATH_MAX +#define MG_PATH_MAX FILENAME_MAX +#endif + +#if MG_ENABLE_WINSOCK + +#define MG_INVALID_SOCKET INVALID_SOCKET +#define MG_SOCKET_TYPE SOCKET +#define poll(a, b, c) WSAPoll((a), (b), (c)) +#define closesocket(x) closesocket(x) +typedef int socklen_t; + +#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)) + +#endif // MG_ENABLE_WINSOCK + +#define realpath(a, b) _fullpath((b), (a), MG_PATH_MAX) +#define sleep(x) Sleep((x) *1000) +#define mkdir(a, b) _mkdir(a) +#define rmdir(a) _rmdir(a) +#define timegm(x) _mkgmtime(x) + +#ifndef S_ISDIR +#define S_ISDIR(x) (((x) &_S_IFMT) == _S_IFDIR) +#endif + +#ifndef MG_ENABLE_DIRLIST +#define MG_ENABLE_DIRLIST 1 +#endif + +#ifndef SIGPIPE +#define SIGPIPE 0 +#endif + +#ifndef MG_ENABLE_POSIX_FS +#define MG_ENABLE_POSIX_FS 1 +#endif + +#ifndef MG_IO_SIZE +#define MG_IO_SIZE 16384 +#endif + +#endif + + +#if MG_ARCH == MG_ARCH_ZEPHYR + +#include +#include +// #include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MG_PUTCHAR(x) printk("%c", x) +#ifndef strdup +#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); +int sscanf(const char *, const char *, ...); + +#endif + + +#if defined(MG_ENABLE_FREERTOS) && MG_ENABLE_FREERTOS + +#include +#include + +#define MG_ENABLE_CUSTOM_CALLOC 1 + +static inline void mg_free(void *ptr) { + vPortFree(ptr); +} + +// Re-route calloc/free to FreeRTOS functions, don't use stdlib +static inline void *mg_calloc(size_t cnt, size_t size) { + void *p = pvPortMalloc(cnt * size); + if (p != NULL) memset(p, 0, size * cnt); + return p; +} + +#endif // MG_ENABLE_FREERTOS + + +#if defined(MG_ENABLE_FREERTOS_TCP) && MG_ENABLE_FREERTOS_TCP + +#include +#include + +#include +#include + +#define MG_SOCKET_TYPE Socket_t +#define MG_INVALID_SOCKET FREERTOS_INVALID_SOCKET + +// Why FreeRTOS-TCP did not implement a clean BSD API, but its own thing +// with FreeRTOS_ prefix, is beyond me +#define IPPROTO_TCP FREERTOS_IPPROTO_TCP +#define IPPROTO_UDP FREERTOS_IPPROTO_UDP +#define AF_INET FREERTOS_AF_INET +#define SOCK_STREAM FREERTOS_SOCK_STREAM +#define SOCK_DGRAM FREERTOS_SOCK_DGRAM +#define SO_BROADCAST 0 +#define SO_ERROR 0 +#define SOL_SOCKET 0 +#define SO_REUSEADDR 0 + +#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? (errcode) : 0) + +#define MG_SOCK_PENDING(errcode) \ + ((errcode) == -pdFREERTOS_ERRNO_EWOULDBLOCK || \ + (errcode) == -pdFREERTOS_ERRNO_EISCONN || \ + (errcode) == -pdFREERTOS_ERRNO_EINPROGRESS || \ + (errcode) == -pdFREERTOS_ERRNO_EAGAIN) + +#define MG_SOCK_RESET(errcode) ((errcode) == -pdFREERTOS_ERRNO_ENOTCONN) + +// actually only if optional timeout is enabled +#define MG_SOCK_INTR(fd) (fd == NULL) + +#define sockaddr_in freertos_sockaddr +#define sockaddr freertos_sockaddr +#if ipFR_TCP_VERSION_MAJOR >= 4 +#define sin_addr sin_address.ulIP_IPv4 +#endif +#define accept(a, b, c) FreeRTOS_accept((a), (b), (c)) +#define connect(a, b, c) FreeRTOS_connect((a), (b), (c)) +#define bind(a, b, c) FreeRTOS_bind((a), (b), (c)) +#define listen(a, b) FreeRTOS_listen((a), (b)) +#define socket(a, b, c) FreeRTOS_socket((a), (b), (c)) +#define send(a, b, c, d) FreeRTOS_send((a), (b), (c), (d)) +#define recv(a, b, c, d) FreeRTOS_recv((a), (b), (c), (d)) +#define setsockopt(a, b, c, d, e) FreeRTOS_setsockopt((a), (b), (c), (d), (e)) +#define sendto(a, b, c, d, e, f) FreeRTOS_sendto((a), (b), (c), (d), (e), (f)) +#define recvfrom(a, b, c, d, e, f) \ + FreeRTOS_recvfrom((a), (b), (c), (d), (e), (f)) +#define closesocket(x) FreeRTOS_closesocket(x) +#define gethostbyname(x) FreeRTOS_gethostbyname(x) +#define getsockname(a, b, c) mg_getsockname((a), (b), (c)) +#define getpeername(a, b, c) mg_getpeername((a), (b), (c)) + +static inline int mg_getsockname(MG_SOCKET_TYPE fd, void *buf, socklen_t *len) { + (void) fd, (void) buf, (void) len; + return -1; +} + +static inline int mg_getpeername(MG_SOCKET_TYPE fd, void *buf, socklen_t *len) { + (void) fd, (void) buf, (void) len; + return 0; +} +#endif + + +#if defined(MG_ENABLE_LWIP) && MG_ENABLE_LWIP + +#if defined(__GNUC__) && !defined(__ARMCC_VERSION) +#include +#endif + +struct timeval; + +#include + +#if !LWIP_TIMEVAL_PRIVATE +#if defined(__GNUC__) && !defined(__ARMCC_VERSION) // armclang sets both +#include +#else +struct timeval { + time_t tv_sec; + long tv_usec; +}; +#endif +#endif + +#if LWIP_SOCKET != 1 +// Sockets support disabled in LWIP by default +#error Set LWIP_SOCKET variable to 1 (in lwipopts.h) +#endif +#endif + + +#if defined(MG_ENABLE_RL) && MG_ENABLE_RL +#include + +#define closesocket(x) closesocket(x) + +#define TCP_NODELAY SO_KEEPALIVE + +#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? (errcode) : 0) + +#define MG_SOCK_PENDING(errcode) \ + ((errcode) == BSD_EWOULDBLOCK || (errcode) == BSD_EALREADY || \ + (errcode) == BSD_EINPROGRESS) + +#define MG_SOCK_RESET(errcode) \ + ((errcode) == BSD_ECONNABORTED || (errcode) == BSD_ECONNRESET) + +// In blocking mode, which is enabled by default, accept() waits for a +// connection request. In non blocking mode, you must call accept() +// again if the error code BSD_EWOULDBLOCK is returned. +#define MG_SOCK_INTR(fd) (fd == BSD_EWOULDBLOCK) + +#define socklen_t int +#endif + + +#ifndef MG_ENABLE_LOG +#define MG_ENABLE_LOG 1 +#endif + +#ifndef MG_ENABLE_CUSTOM_CALLOC +#define MG_ENABLE_CUSTOM_CALLOC 0 +#endif + +#ifndef MG_ENABLE_CUSTOM_LOG +#define MG_ENABLE_CUSTOM_LOG 0 // Let user define their own MG_LOG +#endif + +#ifndef MG_ENABLE_FREERTOS +#define MG_ENABLE_FREERTOS 0 // FreeRTOS RTOS +#endif + +#ifndef MG_ENABLE_TCPIP +#define MG_ENABLE_TCPIP 0 // Mongoose built-in network stack +#endif + +#ifndef MG_ENABLE_BSD_SOCKETS +#define MG_ENABLE_BSD_SOCKETS 0 // BSD API support for built-in stack +#endif + +#ifndef MG_ENABLE_BSD_PROTOTYPES +#define MG_ENABLE_BSD_PROTOTYPES 1 +#endif + +#ifndef MG_ENABLE_LWIP +#define MG_ENABLE_LWIP 0 // lwIP network stack +#endif + +#ifndef MG_ENABLE_FREERTOS_TCP +#define MG_ENABLE_FREERTOS_TCP 0 // Amazon FreeRTOS-TCP network stack +#endif + +#ifndef MG_ENABLE_RL +#define MG_ENABLE_RL 0 // ARM MDK network stack +#endif + +#ifndef MG_ENABLE_SOCKET +#define MG_ENABLE_SOCKET !MG_ENABLE_TCPIP +#endif + +#ifndef MG_ENABLE_POLL +#define MG_ENABLE_POLL 0 +#endif + +#ifndef MG_ENABLE_EPOLL +#define MG_ENABLE_EPOLL 0 +#endif + +#ifndef MG_ENABLE_FATFS +#define MG_ENABLE_FATFS 0 +#endif + +#ifndef MG_ENABLE_LFS +#define MG_ENABLE_LFS 0 +#endif + +#ifndef MG_ENABLE_SSI +#define MG_ENABLE_SSI 0 +#endif + +#ifndef MG_ENABLE_IPV6 +#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 + +// Set MG_ENABLE_WINSOCK=0 for Win32 builds with other external IP stack not +// mentioned in arch_win32.h +#ifndef MG_ENABLE_WINSOCK +#define MG_ENABLE_WINSOCK 1 +#endif + +#ifndef MG_ENABLE_DIRLIST +#define MG_ENABLE_DIRLIST 0 +#endif + +#ifndef MG_ENABLE_CUSTOM_RANDOM +#define MG_ENABLE_CUSTOM_RANDOM 0 +#endif + +#ifndef MG_ENABLE_CUSTOM_MILLIS +#define MG_ENABLE_CUSTOM_MILLIS 0 +#endif + +#ifndef MG_ENABLE_ASSERT +#define MG_ENABLE_ASSERT 0 +#endif + +#ifndef MG_IO_SIZE +#define MG_IO_SIZE 512 // Granularity of the send/recv IO buffer growth +#endif + +#ifndef MG_MAX_RECV_SIZE +#define MG_MAX_RECV_SIZE (3UL * 1024UL * 1024UL) // Maximum recv IO buffer size +#endif + +#ifndef MG_DATA_SIZE +#define MG_DATA_SIZE 32 // struct mg_connection :: data size +#endif + +#ifndef MG_MAX_HTTP_HEADERS +#define MG_MAX_HTTP_HEADERS 30 +#endif + +#ifndef MG_HTTP_INDEX +#define MG_HTTP_INDEX "index.html" +#endif + +#ifndef MG_PATH_MAX +#ifdef PATH_MAX +#define MG_PATH_MAX PATH_MAX +#else +#define MG_PATH_MAX 128 +#endif +#endif + +#ifndef MG_SOCK_LISTEN_BACKLOG_SIZE +#define MG_SOCK_LISTEN_BACKLOG_SIZE 128 +#endif + +#ifndef MG_DIRSEP +#define MG_DIRSEP '/' +#endif + +#ifndef MG_ENABLE_POSIX_FS +#define MG_ENABLE_POSIX_FS 0 +#endif + +#ifndef MG_INVALID_SOCKET +#define MG_INVALID_SOCKET (-1) +#endif + +#ifndef MG_SOCKET_TYPE +#define MG_SOCKET_TYPE int +#endif + +#ifndef MG_SOCKET_ERRNO +#define MG_SOCKET_ERRNO errno +#endif + +#if MG_ENABLE_EPOLL +#define MG_EPOLL_ADD(c) \ + do { \ + struct epoll_event ev = {EPOLLIN | EPOLLERR | EPOLLHUP, {c}}; \ + epoll_ctl(c->mgr->epoll_fd, EPOLL_CTL_ADD, (int) (size_t) c->fd, &ev); \ + } while (0) +#define MG_EPOLL_MOD(c, wr) \ + do { \ + struct epoll_event ev = {EPOLLIN | EPOLLERR | EPOLLHUP, {c}}; \ + if (wr) ev.events |= EPOLLOUT; \ + epoll_ctl(c->mgr->epoll_fd, EPOLL_CTL_MOD, (int) (size_t) c->fd, &ev); \ + } while (0) +#else +#define MG_EPOLL_ADD(c) +#define MG_EPOLL_MOD(c, wr) +#endif + +#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 + +#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 + +#if MG_ENABLE_IPV6 + +#ifndef MG_TCPIP_GLOBAL +#define MG_TCPIP_GLOBAL MG_IPV6(0, 0, 0, 0, 0, 0, 0, 0) +#endif + +#ifndef MG_TCPIP_IPV6_LINKLOCAL +#define MG_TCPIP_IPV6_LINKLOCAL MG_IPV6(0, 0, 0, 0, 0, 0, 0, 0) +#endif + +#ifndef MG_TCPIP_PREFIX_LEN +#define MG_TCPIP_PREFIX_LEN 0 +#endif + +#ifndef MG_TCPIP_GW6 +#define MG_TCPIP_GW6 MG_IPV6(0, 0, 0, 0, 0, 0, 0, 0) +#endif + +#endif + +#ifndef MG_SET_MAC_ADDRESS +#define MG_SET_MAC_ADDRESS(mac) +#endif + +#ifndef MG_TCPIP_DHCPNAME_SIZE +#define MG_TCPIP_DHCPNAME_SIZE 18 // struct mg_tcpip_if :: dhcp_name size +#endif + +#ifndef MG_SET_WIFI_CONFIG +#define MG_SET_WIFI_CONFIG(ifp, driver_data) +#endif + +#ifndef MG_ENABLE_TCPIP_PRINT_DEBUG_STATS +#define MG_ENABLE_TCPIP_PRINT_DEBUG_STATS 0 +#endif + +#ifndef MG_ENABLE_CHACHA20 +#define MG_ENABLE_CHACHA20 1 // When set to 0, GCM is used. For MG_TLS_BUILTIN +#endif + + + + +// 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 tutorials/http/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) +}; + +#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 MG_PROF_INIT(c) +#define MG_PROF_FREE(c) +#define MG_PROF_ADD(c, name) +#define MG_PROF_DUMP(c) +#endif + + + + +// A non-owning view of an arbitrary byte buffer. Not NUL-terminated in general. +// Use mg_str() or mg_str_n() to construct; use mg_strdup() for an owned copy. +struct mg_str { + char *buf; // Pointer to the data (not necessarily NUL-terminated) + size_t len; // Number of bytes +}; + +// Using macro to avoid shadowing the C++ struct constructor, see #1298 +#define mg_str(s) mg_str_s(s) + +// Creates an mg_str from a NUL-terminated C string. Computes length via +// strlen. Returns {NULL, 0} when s is NULL. +struct mg_str mg_str(const char *s); + +// Creates an mg_str from a pointer and explicit byte count. s need not be +// NUL-terminated. Does not copy the data. +struct mg_str mg_str_n(const char *s, size_t n); + +// Case-insensitive strcmp for NUL-terminated C strings. +// Returns 0 if equal, <0 if s1 < s2, >0 if s1 > s2. +int mg_casecmp(const char *s1, const char *s2); + +// Compares two mg_str by content and length. Returns 0 if identical, +// -1 if str1 < str2, 1 if str1 > str2. Length is used as a tiebreaker +// when one string is a prefix of the other. +int mg_strcmp(const struct mg_str str1, const struct mg_str str2); + +// Case-insensitive mg_strcmp. +int mg_strcasecmp(const struct mg_str str1, const struct mg_str str2); + +// Returns a heap-allocated, NUL-terminated copy of s. +// Returns {NULL, 0} on OOM or if s.buf is NULL or s.len is 0. +// Caller must mg_free(result.buf) when done. +struct mg_str mg_strdup(const struct mg_str s); + +// Matches a string against a glob pattern. +// +// Returns: +// True if the whole str matches pattern. +// Example: +// if (mg_match(hm->uri, mg_str("/api/#"), NULL)) { ... } +// Full examples: +// tutorials/http/http-server, tutorials/http/link-checker, +// tutorials/websocket/websocket-server +// Related APIs: +// mg_str(), mg_str_n(), mg_span(), mg_strcmp(), mg_strcasecmp() +// Notes: +// Pattern wildcards: ? matches one character, * matches any sequence except +// '/', and # matches any sequence including '/'. If caps is not NULL, each +// wildcard capture is stored as a zero-copy mg_str slice into str. Pass +// caps=NULL when captures are not needed. The caps array size should be +// at least the number of wildcards in a pattern plus one. The last cap +// is initialised to an empty string. +// Attention: the pattern must consume the whole str in order to match. +bool mg_match(struct mg_str str, struct mg_str pattern, struct mg_str *caps); + +// Splits s at the first occurrence of sep. Sets *a to the part before sep +// and *b to the part after it (sep is not included in either). Both slices +// are zero-copy views into s. a or b may be NULL if that half is not needed. +// If sep is not found, *a is the whole string and *b is empty. +// Returns false if s is empty or s.buf is NULL. +// Example - scan comma-separated key=value pairs: +// ```c +// struct mg_str entry, key, val; +// struct mg_str s = mg_str("a=333,b=777,hello"); +// +// while (mg_span(s, &entry, &s, ',')) { +// mg_span(entry, &key, &val, '='); +// printf("key: %.*s, val: %.*s\n", (int) key.len, key.buf, (int) val.len, val.buf); +// } +// ``` +bool mg_span(struct mg_str s, struct mg_str *a, struct mg_str *b, char sep); + +// Parses str as an unsigned integer into the val_len-byte value at *val. +// val_len must be 1, 2, 4, or 8 (for uint8_t, uint16_t, uint32_t, uint64_t). +// base must be 2, 10, or 16; pass 0 to auto-detect from a "0b" or "0x" prefix. +// The entire string must consist of valid digits with no trailing characters. +// Returns false on overflow, invalid input, unsupported base, or unsupported val_len. +bool mg_str_to_num(struct mg_str str, int base, void *val, size_t val_len); + + + + +// Lock-free, single-producer / single-consumer (SPSC) message queue. +// Safe to write from an ISR and read from the main loop without locking, +// as long as only one context writes and one context reads at a time. +// Each message is stored with an internal 32-bit length prefix; the buf +// pointers returned by mg_queue_book() and mg_queue_next() already skip it. +// +// Producer pattern: +// char buf[1024]; +// if (mg_queue_book(&q, &buf, len) >= len) { +// memcpy(buf, data, len); +// mg_queue_add(&q, len); +// } +// +// Consumer pattern: +// char *buf; +// size_t len = mg_queue_next(&q, &buf); +// if (len > 0) { process(buf, len); mg_queue_del(&q, len); } + +struct mg_queue { + char *buf; // Caller-supplied backing buffer + size_t size; // Size of buf in bytes + volatile size_t tail; // Read offset; updated by the consumer + volatile size_t head; // Write offset; updated by the producer +}; + +// Initialises q to use the caller-supplied buf/size. buf must remain valid +// for the lifetime of the queue. +void mg_queue_init(struct mg_queue *q, char *buf, size_t size); + +// Reserves space for a message of len bytes. Sets *buf to the write location. +// Returns the number of bytes available (>= len means the reservation +// succeeded). Does NOT commit the message; call mg_queue_add() after writing. +size_t mg_queue_book(struct mg_queue *q, char **buf, size_t len); + +// Commits the most recently reserved message of len bytes. Must be called +// with the same len passed to the preceding mg_queue_book() call, after +// the data has been written into the buffer it returned. +void mg_queue_add(struct mg_queue *q, size_t len); + +// Returns the length of the oldest unconsumed message and sets *buf to its +// start. Returns 0 if the queue is empty. Does NOT remove the message; +// call mg_queue_del() after processing to advance the read position. +size_t mg_queue_next(struct mg_queue *q, char **buf); + +// Removes the oldest message of len bytes. Must be called with the same len +// returned by the preceding mg_queue_next() call. +void mg_queue_del(struct mg_queue *q, size_t len); + + + + +// Character output function. Called once per output character by mg_xprintf/mg_vxprintf. +// arg is an arbitrary user pointer passed through from the caller. +typedef void (*mg_pfn_t)(char, void *arg); + +// Custom %M/%m printer. Reads its own arguments from ap and writes via fn. +// Returns the number of characters printed (excluding the quotes added by %m). +typedef size_t (*mg_pm_t)(mg_pfn_t fn, void *arg, va_list *); + +// Formats fmt with printf-like specifiers, calling fn for each output character. +// Returns the total number of characters printed. Supported specifiers: +// - %c - expect char +// - %f, %g - expect double +// - %hhd, %hd, %d, %ld, %lld - for char, short, int, long, int64_t +// - %hhu, %hu, %u, %lu, %llu - same but for unsigned variants +// - %hhx, %hx, %x, %lx, %llx - same, unsigned and hex output +// - %p - expects any pointer, prints 0x..... hex value +// - %s - expects char * +// - %% - prints the % character itself +// - %X.Y - optional width and precision modifiers (e.g.: %1.2d) +// - %.* - optional precision modifier, expected as int argument (e.g.: %.*d) +// - %M - (EXTENSION) expects a pointer to a custom print function and its arguments +// - %m - (EXTENSION) exactly like %M but double-quotes the output +// - %M/%m (custom mg_pm_t printer; %m additionally wraps the output in quotes). +// +// There are higher level wrappers around these functions that use +// predefined output functions, and can print to char buffer, or connection +// send iobuffer, etc etc. - for example, mg_snrptinf(). The %M/%m non-standard +// specifier allows to use custom formtter. There are several pre-defined +// formatters: +// - mg_print_base64 - prints a buffer as a base64-encoded string +// - mg_print_esc - prints a JSON-escaped string +// - mg_print_hex - prints a buffer as a hex string +// - mg_print_ip - prints an IP address in a struct mg_str +// - mg_print_ip_port - prints IP address and port in a struct mg_str +// - mg_print_ip4 - prints an IPv4 address +// - mg_print_ip6 - prints an IPv6 address +// - mg_print_mac - prints a MAC address +// +// Usage example: +// +// ```c +// struct a { int a, b; }; +// +// size_t print_a(mg_pfn_t fn, void *arg, va_list *ap) { +// struct a *p = va_arg(*ap, struct a *); +// return mg_xprintf(fn, arg, "{%m:%d}", MG_ESC("a"), p->a); +// } +// +// struct a temp = { 42, 43 }; +// char buf[100]; +// +// mg_snprintf(buf, sizeof(buf), "%M", print_a, &temp); // {"a":42} +// ``` +size_t mg_vxprintf(mg_pfn_t fn, void *arg, const char *fmt, va_list *); +size_t mg_xprintf(mg_pfn_t fn, void *arg, const char *fmt, ...); + + + + + + + +// Formats data into a caller-supplied buffer. +// +// Returns: +// Number of bytes that would be written if buf were large enough, like +// snprintf(). +// Example: +// char buf[20]; +// mg_snprintf(buf, sizeof(buf), "{%m:%d}", MG_ESC("status"), 1); +// Full examples: +// tutorials/http/link-checker, tutorials/http/redirect-to-https, +// tutorials/mqtt/mqtt-dashboard/device +// Related APIs: +// mg_xprintf(), mg_printf(), mg_mprintf(), MG_ESC +// Notes: +// These functions are just wrappers around mg_xprintf(). +// Call with buf=NULL and len=0 to measure. NUL-terminates if the result fits +// (return value < len). Supports mg_xprintf specifiers, including custom +// %M/%m printers. Use MG_ESC when printing JSON strings. +size_t mg_vsnprintf(char *buf, size_t len, const char *fmt, va_list *ap); +size_t mg_snprintf(char *buf, size_t len, const char *fmt, ...); + +// Formats into a heap-allocated NUL-terminated string. Caller must mg_free() it. +char *mg_vmprintf(const char *fmt, va_list *ap); +char *mg_mprintf(const char *fmt, ...); + +// Formats into a mg_queue slot. Returns bytes written, or 0 if no space. +size_t mg_queue_printf(struct mg_queue *, const char *fmt, ...); + +// Built-in %M/%m printer functions. Each reads its argument(s) from ap. +size_t mg_print_base64(mg_pfn_t, void *arg, va_list *ap); // expects: const void *buf, size_t len +size_t mg_print_esc(mg_pfn_t, void *arg, va_list *ap); // expects: int len, const char *str -- use MG_ESC() +size_t mg_print_hex(mg_pfn_t, void *arg, va_list *ap); // expects: const void *buf, size_t len +size_t mg_print_ip(mg_pfn_t, void *arg, va_list *ap); // expects: const struct mg_addr * +size_t mg_print_ip_port(mg_pfn_t, void *arg, va_list *ap); // expects: const struct mg_addr * +size_t mg_print_ip4(mg_pfn_t, void *arg, va_list *ap); // expects: uint32_t *ipv4 +size_t mg_print_ip6(mg_pfn_t, void *arg, va_list *ap); // expects: uint8_t[16] ipv6 +size_t mg_print_mac(mg_pfn_t, void *arg, va_list *ap); // expects: uint8_t[6] mac +size_t mg_print_ieee64(mg_pfn_t, void *arg, va_list *ap); // expects: uint64_t +size_t mg_print_l2addr(mg_pfn_t, void *arg, va_list *ap); // expects: uint8_t l2, uint8_t[n] n-byte l2-dependent address +size_t mg_print_html_esc(mg_pfn_t, void *arg, va_list *ap); // expects: int len, const char *str -- use MG_ESC() + + +// Output functions for use as the fn argument to mg_xprintf/mg_vxprintf. +void mg_pfn_iobuf(char ch, void *param); // param: struct mg_iobuf * (resizes as needed) +void mg_pfn_iobuf_noresize(char ch, void *param); // param: struct mg_iobuf * (fixed buffer, no resize) +void mg_pfn_stdout(char c, void *param); // param: ignored, writes to stdout + +// Expands to the three arguments for %m to JSON-escape a string literal. +// Example: mg_snprintf(buf, len, "%m", MG_ESC("hello")) -> "\"hello\"" +#define MG_ESC(str) mg_print_esc, 0, (str) +// Logging +// +// Mongoose provides a set of functions and macros for logging. The application can +// use these functions for its own purposes as well as the rest of Mongoose API. + + + + + + + +// Log levels +enum { MG_LL_NONE, MG_LL_ERROR, MG_LL_INFO, MG_LL_DEBUG, MG_LL_VERBOSE }; + +// Set Mongoose logging level. Example: `mg_log_set(MG_LL_INFO);` +extern int mg_log_level; // Current log level, one of MG_LL_* +#define mg_log_set(level_) mg_log_level = (level_) + +void mg_log(const char *fmt, ...); +void mg_log_prefix(int ll, const char *file, int line, const char *fname); + +// Log a hex dump of binary data `buf`, `len`. +void mg_hexdump(const void *buf, size_t len); + +// Set log printer function which prints one byte. Example: +// +// ```c +// static void print_char(char ch, void *param) { +// hal_uart_write_char(param, ch); +// } +// ... +// mg_log_set_fn(print_char, USART3); +// ``` +void mg_log_set_fn(mg_pfn_t fn, void *param); + +#if MG_ENABLE_LOG +#if !defined(_MSC_VER) && \ + (!defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L) +#define MG___FUNC__ "" +#else +#define MG___FUNC__ __func__ // introduced in C99 +#endif +#define MG_LOG(level, args) \ + do { \ + if ((level) <= mg_log_level) { \ + mg_log_prefix((level), __FILE__, __LINE__, MG___FUNC__); \ + mg_log args; \ + } \ + } while (0) +#else +#define MG_LOG(level, args) \ + do { \ + if (0) mg_log args; \ + } while (0) +#endif + +// Logging macros. Note: the argument is exactly like for mg_snprintf(), +// and it should be enclosed in double parenthesis. Example: +// ```c +// MG_INFO(("Conn %lu, recv buf: %.*s", c->id, c->recv.len, c->recv.buf)); +// ``` +#define MG_ERROR(args) MG_LOG(MG_LL_ERROR, args) +#define MG_INFO(args) MG_LOG(MG_LL_INFO, args) +#define MG_DEBUG(args) MG_LOG(MG_LL_DEBUG, args) +#define MG_VERBOSE(args) MG_LOG(MG_LL_VERBOSE, args) + + + + +struct mg_timer { + uint64_t period_ms; // Timer period in milliseconds + uint64_t expire; // Expiration timestamp in milliseconds + unsigned flags; // Possible flags values below +#define MG_TIMER_ONCE 0 // Call function once +#define MG_TIMER_REPEAT 1 // Call function periodically +#define MG_TIMER_RUN_NOW 2 // Call immediately when timer is set +#define MG_TIMER_CALLED 4 // Timer function was called at least once +#define MG_TIMER_AUTODELETE 8 // mg_free() timer when done + void (*fn)(void *); // Function to call + void *arg; // Function argument + struct mg_timer *next; // Linkage +}; + +void mg_timer_init(struct mg_timer **head, struct mg_timer *timer, + uint64_t milliseconds, unsigned flags, void (*fn)(void *), + void *arg); +void mg_timer_free(struct mg_timer **head, struct mg_timer *); +void mg_timer_poll(struct mg_timer **head, uint64_t new_ms); + + + + + +// Flags returned by mg_fs.st() and passed to mg_fs.open(). +enum { MG_FS_READ = 1, MG_FS_WRITE = 2, MG_FS_DIR = 4, MG_FS_EXCL = 8 }; + +// Filesystem abstraction. Implement all function pointers to plug in a custom +// filesystem. Short UNIX-style names are used deliberately to avoid conflicts +// with libraries that define macros for stat(), read(), write(), etc. +struct mg_fs { + int (*st)(const char *path, size_t *size, time_t *mtime); // Stat: return MG_FS_* flags; size/mtime may be NULL + void (*ls)(const char *path, void (*fn)(const char *, void *), void *); // List dir: call fn(name, arg) per entry + void *(*op)(const char *path, int flags); // Open file; return opaque fd, or NULL on error + void (*cl)(void *fd); // Close file + size_t (*rd)(void *fd, void *buf, size_t len); // Read up to len bytes; return bytes read + size_t (*wr)(void *fd, const void *buf, size_t len); // Write len bytes; return bytes written + size_t (*sk)(void *fd, size_t offset); // Seek to offset from start; return new position + bool (*mv)(const char *from, const char *to); // Rename/move file + bool (*rm)(const char *path); // Delete file + bool (*mkd)(const char *path); // Create directory +}; + +extern struct mg_fs mg_fs_posix; // POSIX filesystem (open/close/read/write/seek) +extern struct mg_fs mg_fs_packed; // Read-only packed filesystem (see tutorials/core/embedded-filesystem) +extern struct mg_fs mg_fs_fat; // FAT filesystem + +// Open file descriptor returned by mg_fs_open(). Bundles the raw fd with its fs. +struct mg_fd { + void *fd; // Opaque file handle from fs->op() + struct mg_fs *fs; // Filesystem that owns fd +}; + +// Opens path with the given MG_FS_* flags. Returns NULL on error. +struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int flags); + +// Closes fd and frees the mg_fd struct. Safe to call with NULL. +void mg_fs_close(struct mg_fd *fd); + +// Sequential directory iterator. Call repeatedly with the same buf/len; +// each call fills buf with the next entry name. Returns false when done. +bool mg_fs_ls(struct mg_fs *fs, const char *path, char *buf, size_t len); + +// Reads the entire file into a heap-allocated buffer. The returned mg_str is +// NUL-terminated. Caller must mg_free(result.buf). Returns {NULL,0} on error. +struct mg_str mg_file_read(struct mg_fs *fs, const char *path); + +// Atomically writes buf/len to path (via a temp file + rename). Returns false on error. +bool mg_file_write(struct mg_fs *fs, const char *path, const void *buf, size_t len); + +// Formats a string and atomically writes it to path. Returns false on error. +bool mg_file_printf(struct mg_fs *fs, const char *path, const char *fmt, ...); + + +// A file stored in memory (flash or RAM), used by mg_fs_packed. +struct mg_mem_file { + const char *path; // Virtual file path, e.g. "/web_root/index.html" + const unsigned char *data; // Pointer to file content + size_t size; // Content size in bytes + time_t mtime; // Modification time (may be 0) +}; + +// Pointer to a NULL-path-terminated array of in-memory files for mg_fs_packed. +// Override to serve a custom set of files: +// static const struct mg_mem_file my_files[] = { +// {"/index.html", data, sizeof(data), 0}, +// {NULL, NULL, 0, 0}, +// }; +// mg_mem_files = my_files; +extern const struct mg_mem_file *mg_mem_files; + +// Looks up path in the packed file array. Returns an mg_str pointing directly +// into the stored data (zero-copy). Returns {NULL,0} if not found. +struct mg_str mg_unpacked(const char *path); + +// Array of packed files generated by the "pack" utility into packed_fs.c. +// Activate by assigning: mg_mem_files = mg_packed_files; +extern const struct mg_mem_file mg_packed_files[]; + + + + + + + + +#if MG_ENABLE_ASSERT +#include +#elif !defined(assert) +#define assert(x) +#endif + +// Wrappers around calloc/free. Override by defining MG_ENABLE_CUSTOM_CALLOC=1 +// and providing your own implementations. +void *mg_calloc(size_t count, size_t size); +void mg_free(void *ptr); + +// Zeroes len bytes at buf using a volatile write loop that the compiler cannot +// elide. Safe to call with buf=NULL. Use instead of memset() for clearing +// sensitive data (keys, passwords). +void mg_bzero(volatile unsigned char *buf, size_t len); + +// Fills buf with len cryptographically random bytes. Uses the best available +// hardware or OS source (hardware RNG, /dev/urandom, CryptGenRandom, etc.). +// Falls back to rand() with an error log if no strong source is available. +// Returns true when a strong source was used, false on fallback to rand(). +// Override by defining MG_ENABLE_CUSTOM_RANDOM=1 and providing your own impl. +bool mg_random(void *buf, size_t len); + +// Fills buf with len-1 random alphanumeric characters ([a-zA-Z0-9]) and +// NUL-terminates. Returns buf. +char *mg_random_str(char *buf, size_t len); + +// Computes CRC32 (polynomial 0xEDB88320) over buf/len. Pass crc=0 to start +// a new checksum; pass the result of a prior call to extend over more data. +uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len); + +// Returns true if path is safe to serve from the filesystem. Rejects paths +// that start with '~' or '..', or contain a '/../' component, to prevent +// directory traversal attacks. +bool mg_path_is_sane(const struct mg_str path); + +// Busy-waits for at least ms milliseconds using mg_millis(). Blocks the +// calling context; avoid in event handlers. +void mg_delayms(unsigned int ms); + +// Packs four byte values into a uint32_t in big-endian order. +// MG_U32(1, 2, 3, 4) == 0x01020304 +#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)) + +// Constructs an IPv4 address in network byte order from four decimal octets. +// Usage: uint32_t ip = MG_IPV4(192, 168, 1, 1); +#define MG_IPV4(a, b, c, d) mg_htonl(MG_U32(a, b, c, d)) + +// Expands to a brace-enclosed byte initialiser for a 16-byte IPv6 address. +// Arguments are the eight 16-bit groups in the address, e.g.: +// uint8_t ip6[16] = MG_IPV6(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1); +#define MG_IPV6(a, b, c, d, e, f, g ,h) \ + { (uint8_t)((a)>>8),(uint8_t)(a), \ + (uint8_t)((b)>>8),(uint8_t)(b), \ + (uint8_t)((c)>>8),(uint8_t)(c), \ + (uint8_t)((d)>>8),(uint8_t)(d), \ + (uint8_t)((e)>>8),(uint8_t)(e), \ + (uint8_t)((f)>>8),(uint8_t)(f), \ + (uint8_t)((g)>>8),(uint8_t)(g), \ + (uint8_t)((h)>>8),(uint8_t)(h) } + +// 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] + +// Read an unaligned big-endian value from byte pointer p into a native integer. +// Safe on architectures that forbid unaligned access (e.g. Cortex-M0). +#define MG_LOAD_BE16(p) \ + ((uint16_t) (((uint16_t) MG_U8P(p)[0] << 8U) | MG_U8P(p)[1])) +#define MG_LOAD_BE24(p) \ + ((uint32_t) (((uint32_t) MG_U8P(p)[0] << 16U) | \ + ((uint32_t) MG_U8P(p)[1] << 8U) | MG_U8P(p)[2])) +#define MG_LOAD_BE32(p) \ + ((uint32_t) (((uint32_t) MG_U8P(p)[0] << 24U) | \ + ((uint32_t) MG_U8P(p)[1] << 16U) | \ + ((uint32_t) MG_U8P(p)[2] << 8U) | MG_U8P(p)[3])) +#define MG_LOAD_BE64(p) \ + ((uint64_t) (((uint64_t) MG_U8P(p)[0] << 56U) | \ + ((uint64_t) MG_U8P(p)[1] << 48U) | \ + ((uint64_t) MG_U8P(p)[2] << 40U) | \ + ((uint64_t) MG_U8P(p)[3] << 32U) | \ + ((uint64_t) MG_U8P(p)[4] << 24U) | \ + ((uint64_t) MG_U8P(p)[5] << 16U) | \ + ((uint64_t) MG_U8P(p)[6] << 8U) | MG_U8P(p)[7])) + +// Write a native integer to byte pointer p in big-endian byte order. +// Safe on architectures that forbid unaligned access. +#define MG_STORE_BE16(p, n) \ + do { \ + MG_U8P(p)[0] = (uint8_t) ((n) >> 8U); \ + MG_U8P(p)[1] = (uint8_t) (n); \ + } while (0) +#define MG_STORE_BE24(p, n) \ + do { \ + MG_U8P(p)[0] = (uint8_t) ((n) >> 16U); \ + MG_U8P(p)[1] = (uint8_t) ((n) >> 8U); \ + MG_U8P(p)[2] = (uint8_t) (n); \ + } while (0) +#define MG_STORE_BE32(p, n) \ + do { \ + MG_U8P(p)[0] = (uint8_t) ((n) >> 24U); \ + MG_U8P(p)[1] = (uint8_t) ((n) >> 16U); \ + MG_U8P(p)[2] = (uint8_t) ((n) >> 8U); \ + MG_U8P(p)[3] = (uint8_t) (n); \ + } while (0) +#define MG_STORE_BE64(p, n) \ + do { \ + MG_U8P(p)[0] = (uint8_t) ((n) >> 56U); \ + MG_U8P(p)[1] = (uint8_t) ((n) >> 48U); \ + MG_U8P(p)[2] = (uint8_t) ((n) >> 40U); \ + MG_U8P(p)[3] = (uint8_t) ((n) >> 32U); \ + MG_U8P(p)[4] = (uint8_t) ((n) >> 24U); \ + MG_U8P(p)[5] = (uint8_t) ((n) >> 16U); \ + MG_U8P(p)[6] = (uint8_t) ((n) >> 8U); \ + MG_U8P(p)[7] = (uint8_t) (n); \ + } while (0) + +// Network / host byte-order conversion (big-endian <-> native). +// mg_htons/mg_htonl/mg_htonll are aliases for the same operation (symmetric). +uint16_t mg_ntohs(uint16_t net); +uint32_t mg_ntohl(uint32_t net); +uint64_t mg_ntohll(uint64_t net); +#define mg_htons(x) mg_ntohs(x) +#define mg_htonl(x) mg_ntohl(x) +#define mg_htonll(x) mg_ntohll(x) + +// Memory-mapped register access: reads/writes a volatile uint32_t at address x. +#define MG_REG(x) ((volatile uint32_t *) (x))[0] + +// Produces a uint32_t with bit x set. x must be 0-31. +#define MG_BIT(x) (((uint32_t) 1U) << (x)) + +// Clears bits in CLRMASK and sets bits in SETMASK in register R atomically +// (read-modify-write). Example: MG_SET_BITS(MG_REG(addr), 0xF, 0x3); +#define MG_SET_BITS(R, CLRMASK, SETMASK) (R) = ((R) & ~(CLRMASK)) | (SETMASK) + +// Round x up / down to the nearest multiple of a. Returns x when a is 0. +#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__) && defined(__arm__) +#ifdef __ZEPHYR__ +#define MG_ARM_DISABLE_IRQ() __asm__ __volatile__("cpsid i" : : : "memory") +#define MG_ARM_ENABLE_IRQ() __asm__ __volatile__("cpsie i" : : : "memory") +#else +#define MG_ARM_DISABLE_IRQ() asm volatile("cpsid i" : : : "memory") +#define MG_ARM_ENABLE_IRQ() asm volatile("cpsie i" : : : "memory") +#endif // !ZEPHYR +#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__) +#ifdef __ZEPHYR__ +#define MG_DSB() __asm__("DSB 0xf") +#else +#define MG_DSB() asm("DSB 0xf") +#endif // !ZEPHYR +#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_) \ + do { \ + (elem_)->next = (*head_); \ + *(head_) = (elem_); \ + } while (0) + +#define LIST_ADD_TAIL(type_, head_, elem_) \ + do { \ + type_ **h = head_; \ + while (*h != NULL) h = &(*h)->next; \ + *h = (elem_); \ + } while (0) + +#define LIST_DELETE(type_, head_, elem_) \ + do { \ + type_ **h = head_; \ + while (*h != (elem_)) h = &(*h)->next; \ + *h = (elem_)->next; \ + } while (0) + + + +// Returns the port from url. Uses scheme defaults when no port is explicit: +// http/ws=80, https/wss=443, mqtt=1883, mqtts=8883. Returns 0 if unknown. +unsigned short mg_url_port(const char *url); + +// Returns true if url uses a TLS scheme (https, wss, mqtts, ssl, tls, tcps). +bool mg_url_is_ssl(const char *url); + +// Returns the hostname from url as mg_str, without port or brackets. +// E.g. "http://foo.com:8080/x" -> "foo.com". Not NUL-terminated. +struct mg_str mg_url_host(const char *url); + +// Returns the username from url (user:pass@host form) as mg_str. +// Returns an empty mg_str if no credentials are present. Not NUL-terminated. +struct mg_str mg_url_user(const char *url); + +// Returns the password from url (user:pass@host form) as mg_str. +// Returns an empty mg_str if no credentials are present. Not NUL-terminated. +struct mg_str mg_url_pass(const char *url); + +// Returns a pointer to the URI path in url (e.g. "/api/v1"). +// Returns "/" if the url has no path. Points into the original url string. +const char *mg_url_uri(const char *url); + + + + +// Dynamically-sized I/O buffer. len <= size always holds. +struct mg_iobuf { + unsigned char *buf; // Heap-allocated data buffer + size_t size; // Allocated capacity (rounded up to align) + size_t len; // Number of bytes currently stored + size_t align; // Allocation granularity; capacity is always a multiple of this +}; + +// Initializes io to empty, then allocates size bytes with the given alignment. +// Returns false on allocation failure. +bool mg_iobuf_init(struct mg_iobuf *io, size_t size, size_t align); + +// Resizes the buffer to new_size (rounded up to io->align). new_size=0 frees +// the buffer. Uses mg_calloc+mg_free (not realloc) so old memory is zeroed on release. +// Returns false on allocation failure. +bool mg_iobuf_resize(struct mg_iobuf *io, size_t new_size); + +// Frees the buffer and zeroes the struct. Equivalent to mg_iobuf_resize(io, 0). +void mg_iobuf_free(struct mg_iobuf *io); + +// Inserts len bytes from buf at offset ofs, shifting existing data right. +// Pass buf=NULL to reserve space without writing. Returns bytes inserted, 0 on OOM. +size_t mg_iobuf_add(struct mg_iobuf *io, size_t ofs, const void *buf, size_t len); + +// Removes len bytes at ofs, shifting remaining data left. Clamps to available data. +// Returns bytes actually removed. +size_t mg_iobuf_del(struct mg_iobuf *io, size_t ofs, size_t len); + + +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); + + + + +typedef struct { + uint32_t buf[4]; + uint32_t bits[2]; + unsigned char in[64]; +} mg_md5_ctx; + +void mg_md5_init(mg_md5_ctx *c); +void mg_md5_update(mg_md5_ctx *c, const unsigned char *data, size_t len); +void mg_md5_final(mg_md5_ctx *c, unsigned char[16]); + + + + +typedef struct { + uint32_t state[5]; + uint32_t count[2]; + unsigned char buffer[64]; +} mg_sha1_ctx; + +void mg_sha1_init(mg_sha1_ctx *); +void mg_sha1_update(mg_sha1_ctx *, const unsigned char *data, size_t len); +void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx *); +// 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_sha256(uint8_t dst[32], uint8_t *data, size_t datasz); +void mg_hmac_sha256(uint8_t dst[32], uint8_t *key, size_t keysz, uint8_t *data, + size_t datasz); + +typedef struct { + uint64_t state[8]; + uint8_t buffer[128]; + uint64_t bitlen[2]; + uint32_t datalen; +} mg_sha384_ctx; +void mg_sha384_init(mg_sha384_ctx *ctx); +void mg_sha384_update(mg_sha384_ctx *ctx, const uint8_t *data, size_t len); +void mg_sha384_final(uint8_t digest[48], mg_sha384_ctx *ctx); +void mg_sha384(uint8_t dst[48], uint8_t *data, size_t datasz); + + + +struct mg_connection; + +// User-supplied event handler. ev is enum mg_event; ev_data type depends on +// the event (see enum below). c->fn_data is the user pointer passed to +// mg_listen(), mg_connect(), mg_*_listen(), mg_*_connect(). +typedef void (*mg_event_handler_t)(struct mg_connection *, int ev, + void *ev_data); + +// Fires ev on connection c, invoking c->fn and c->pfn with ev_data. +void mg_call(struct mg_connection *c, int ev, void *ev_data); + +// Sets c->is_closing and fires MG_EV_ERROR with a printf-formatted message. +void mg_error(struct mg_connection *c, const char *fmt, ...); + +// Event codes passed to mg_event_handler_t. Each entry shows the type of +// ev_data cast to use inside the handler, e.g.: char *msg = (char *) ev_data; +enum mg_event { + // Event Meaning ev_data type + 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_MDNS_REQ, // mDNS request struct mg_mdns_req * + MG_EV_MDNS_RESP, // mDNS response struct mg_mdns_resp * + MG_EV_MODBUS_REQ, // Modbus TCP request struct mg_modbus_req * + MG_EV_USER // Starting ID for user events +}; + + + + + + + + + + +// DNS server configuration used by struct mg_mgr. +struct mg_dns { + const char *url; // DNS server URL, e.g. "udp://8.8.8.8:53" + struct mg_connection *c; // Active DNS connection; NULL when idle +}; + +// Network address: IPv4 or IPv6 address plus port. All values in network byte order. +struct mg_addr { + union { // IP address bytes in network byte order + uint8_t ip[16]; // Raw 16-byte buffer; IPv4 uses the first 4 bytes + uint32_t ip4; // IPv4 address alias (same storage as ip[0..3]) + uint64_t ip6[2]; // IPv6 address as two 64-bit words + } addr; + uint16_t port; // TCP or UDP port in network byte order + uint8_t scope_id; // IPv6 scope ID (from the %N suffix in the address) + bool is_ip6; // True when this address holds an IPv6 address +}; + +union mg_pipe { + MG_SOCKET_TYPE fd; + void *q; +}; + +// Central event manager. Zero-initialise with mg_mgr_init() before use. +struct mg_mgr { + struct mg_connection *conns; // Linked list of all open connections + struct mg_dns dns4; // IPv4 DNS server (default: 8.8.8.8:53) + struct mg_dns dns6; // IPv6 DNS server (default: [2001:4860:4860::8888]:53) + struct mg_connection *mdns; // mDNS listener connection, or NULL + int dnstimeout; // DNS resolve timeout in ms (default: 3000) + bool use_dns6; // If true, prefer DNS6 for hostname resolution + unsigned long nextid; // Auto-incrementing counter for connection IDs + void *userdata; // Arbitrary user pointer + void *tls_ctx; // Shared TLS context for all TLS connections + uint16_t mqtt_id; // Packet ID counter for MQTT pub/sub + void *active_dns_requests; // Pending DNS queries (internal) + void *active_mdns_requests; // Pending mDNS resolver queries (internal) + struct mg_timer *timers; // Linked list of active timers + int epoll_fd; // epoll file descriptor; -1 when unused (MG_EPOLL_ENABLE=1) + struct mg_tcpip_if *ifp; // Builtin TCP/IP stack: network interface pointer + size_t extraconnsize; // Builtin TCP/IP stack: extra bytes allocated per connection + union mg_pipe pipe; // Socketpair write-end / queue, used by mg_wakeup() +#if MG_ENABLE_FREERTOS_TCP + SocketSet_t ss; // FreeRTOS-TCP socket set +#endif +}; + +// A single network connection. Do not allocate directly; use mg_connect() or +// mg_listen(). Each connection has an event handler fn called for every event. +struct mg_connection { + struct mg_connection *next; // Next connection in mgr->conns linked list + struct mg_mgr *mgr; // Manager that owns this connection + struct mg_addr loc; // Local address (IP + port) + struct mg_addr rem; // Remote address (IP + port) + void *fd; // OS socket handle or LWIP PCB pointer + unsigned long id; // Unique, auto-incrementing connection ID + struct mg_iobuf recv; // Received data; consume in MG_EV_READ handler + struct mg_iobuf send; // Data to send; append with mg_send() / mg_printf() + struct mg_iobuf prof; // Profiling data (MG_ENABLE_PROFILE only) + struct mg_iobuf rtls; // TLS only: encrypted data buffered before decryption + mg_event_handler_t fn; // User event handler + void *fn_data; // User-supplied argument passed to fn + mg_event_handler_t pfn; // Protocol-level handler (set by protocol modules) + void *pfn_data; // Protocol-level handler argument + char data[MG_DATA_SIZE]; // Scratch space for protocol state; freely readable + void *tls; // TLS state (internal) + unsigned is_listening : 1; // Listening connection; accepts inbound connections + unsigned is_client : 1; // Outbound connection created by mg_connect() + unsigned is_accepted : 1; // Inbound connection accepted from a listener + unsigned is_resolving : 1; // DNS resolution is in progress + unsigned is_arplooking : 1; // ARP resolution is in progress (builtin TCP/IP) + unsigned is_connecting : 1; // Non-blocking TCP connect is in progress + unsigned is_tls : 1; // TLS connection + unsigned is_tls_hs : 1; // TLS handshake is in progress + unsigned is_udp : 1; // UDP connection (not TCP) + unsigned is_websocket : 1; // WebSocket connection + unsigned is_mqtt5 : 1; // MQTT5 connection (set by mg_mqtt_login) + unsigned is_hexdumping : 1; // Log hex dump of all in/out traffic + unsigned is_draining : 1; // Flush send buffer, then close + unsigned is_closing : 1; // Close and free immediately on next poll + unsigned is_full : 1; // Pause incoming reads until cleared + unsigned is_tls_throttled : 1; // TLS write was throttled; retry pending + unsigned is_resp : 1; // HTTP: response is still being generated + unsigned is_readable : 1; // Socket is ready to read (epoll/select) + unsigned is_writable : 1; // Socket is ready to write (epoll/select) +}; + +// Runs one iteration of the event loop. +// +// Example: +// while (keep_running) mg_mgr_poll(&mgr, 50); +// Full examples: +// tutorials/http/http-server, tutorials/mqtt/mqtt-client, +// tutorials/websocket/websocket-server, tutorials/core/timers +// Related APIs: +// mg_mgr_init(), mg_mgr_free(), mg_timer_add(), mg_wakeup() +// Notes: +// Waits up to ms milliseconds for I/O events; use ms=0 to return +// immediately. Calls event handlers for ready connections and fires expired +// timers. Call repeatedly from the main loop or a dedicated network task. +void mg_mgr_poll(struct mg_mgr *, int ms); + +// Initialises an event manager before use. +// +// Example: +// struct mg_mgr mgr; +// mg_mgr_init(&mgr); +// Full examples: +// tutorials/http/http-server, tutorials/mqtt/mqtt-client, +// tutorials/websocket/websocket-server, tutorials/core/embedded-filesystem +// Related APIs: +// mg_mgr_poll(), mg_mgr_free(), mg_http_listen(), mg_connect() +// Notes: +// Sets safe defaults such as DNS servers, DNS timeout, epoll/SIGPIPE setup, +// and TLS context. Call before creating connections, listeners, or timers. +// Set mgr.userdata after this call if your application needs it. +// On embedded systems, if the built-in TCP/IP stack is enabled +// (MG_ENABLE_TCPIP=1) and one built-in driver is selected, e.g. +// MG_ENABLE_DRIVER_STM32H=1, mg_mgr_init() also calls mg_tcpip_init(). +// If no driver is selected, automatic driver init is disabled, or multiple +// interfaces are intended, call mg_tcpip_init() separately. +void mg_mgr_init(struct mg_mgr *); + +// Closes all connections, frees all timers, and releases the TLS context. +// Safe to call on a partially initialised mgr. +void mg_mgr_free(struct mg_mgr *); + +// Creates a listening connection on url (e.g. "tcp://0.0.0.0:8080"). +// Fires MG_EV_OPEN on the listener, then MG_EV_ACCEPT for each new client. +// Returns NULL on error. +struct mg_connection *mg_listen(struct mg_mgr *, const char *url, + mg_event_handler_t fn, void *fn_data); + +// Opens an outbound connection to url (e.g. "tcp://example.com:80"). +// DNS resolution is asynchronous. Fires MG_EV_RESOLVE, then MG_EV_CONNECT +// when the connection is established (or MG_EV_ERROR on failure). +// Returns NULL on error (OOM, network down, null url). +struct mg_connection *mg_connect(struct mg_mgr *, const char *url, + mg_event_handler_t fn, void *fn_data); + +// Wraps an existing OS file descriptor in a Mongoose connection. +// The connection is added to mgr and fires MG_EV_OPEN immediately. +// Useful for integrating pre-opened sockets (e.g. stdin, accept() fd). +struct mg_connection *mg_wrapfd(struct mg_mgr *mgr, int fd, + mg_event_handler_t fn, void *fn_data); + +// Called internally after DNS resolution completes to create the OS socket +// and initiate the TCP/IP connect. Normally not called by user code. +void mg_connect_resolved(struct mg_connection *); + +// Appends buf/len to c->send. Returns true on success, false on OOM +// (which also fires MG_EV_ERROR and schedules the connection for close). +// Data is sent asynchronously by the next mg_mgr_poll() call. +bool mg_send(struct mg_connection *, const void *, size_t); + +// Formats data and appends it to a connection send buffer. +// +// Returns: +// Number of bytes appended, or 0 on OOM. +// Example: +// mg_printf(c, "GET / HTTP/1.0\r\nHost: %.*s\r\n\r\n", +// (int) host.len, host.buf); +// Full examples: +// tutorials/http/http-client, tutorials/http/http-proxy-client, +// tutorials/http/http-restful-server +// Related APIs: +// mg_send(), mg_snprintf(), mg_http_reply(), mg_ws_printf() +// Notes: +// Data is sent asynchronously by a later mg_mgr_poll() call. Supports +// mg_xprintf specifiers, including custom %M/%m printers. See src/fmt.h for +// the full specifier list. +size_t mg_printf(struct mg_connection *, const char *fmt, ...); +size_t mg_vprintf(struct mg_connection *, const char *fmt, va_list *ap); + +// Parses an IP address string str into addr. Handles "localhost", plain IPv4 +// (e.g. "1.2.3.4"), IPv6 with optional brackets and scope ID, and +// IPv4-mapped IPv6 (::ffff:1.2.3.4). Does NOT parse the port. +// Returns true on success; addr->is_ip6 and addr->addr are set. +bool mg_aton(struct mg_str str, struct mg_addr *addr); + +// Low-level functions for integrating with custom network stacks. +// Allocates a new connection struct with mgr->extraconnsize extra bytes. +// Returns NULL on OOM. +struct mg_connection *mg_alloc_conn(struct mg_mgr *); + +// Fires MG_EV_CLOSE, frees all buffers (recv/send/rtls), removes c from +// mgr->conns, and calls mg_free(c). Do not use c after this call. +void mg_close_conn(struct mg_connection *c); + +// Creates and binds the OS socket for a listening connection c at url. +// Called internally by mg_listen(); expose for custom network stack use. +// Returns false on error. +bool mg_open_listener(struct mg_connection *c, const char *url); + +// Thread-safe wakeup: fires MG_EV_WAKEUP on the connection whose id matches, +// with ev_data pointing to an mg_str containing buf/len. +// Requires mg_wakeup_init() to have been called first. +// Returns false if the pipe is not initialised or conn_id is 0. +// Safe to call from any thread or interrupt context. +bool mg_wakeup(struct mg_mgr *, unsigned long id, const void *buf, size_t len); + +// Initialises the internal socketpair used by mg_wakeup(). Call once after +// mg_mgr_init() if you intend to wake the event loop from another thread. +// Returns false on failure (e.g. socketpair creation failed). +bool mg_wakeup_init(struct mg_mgr *); + +// Deprecated API, do not expose. Use mg_timer_expired instead +struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t milliseconds, + unsigned flags, void (*fn)(void *), void *arg); + +// Like mg_connect() but also sets a protocol-level handler pfn/pfn_data. +// Used internally by protocol modules (MQTT, WebSocket, etc.). +struct mg_connection *mg_connect_svc(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data, + mg_event_handler_t pfn, void *pfn_data); + +// Restores c->rem from a saved address buffer written by the caller, then +// maps the IP back through the builtin TCP/IP stack. Used after a UDP +// multicast send to restore the connection's original remote address. +void mg_multicast_restore(struct mg_connection *c, uint8_t *from); + + + + + + + + +// A single HTTP header name/value pair. +struct mg_http_header { + struct mg_str name; // Header name, e.g. "Content-Type" + struct mg_str value; // Header value, e.g. "text/html" +}; + +// Parsed HTTP request or response. +// Passed as ev_data in MG_EV_HTTP_MSG and MG_EV_HTTP_HDRS. +// For requests: method="GET", uri="/path", proto="a=1&b=2", proto="HTTP/1.1" +// For responses: method="HTTP/1.0", uri="200", query="", proto="OK" +struct mg_http_message { + struct mg_str method, uri, query, proto; // Request/response line + struct mg_http_header headers[MG_MAX_HTTP_HEADERS]; // Parsed headers array + struct mg_str body; // Request or response body + struct mg_str head; // Raw bytes: request/status line + headers, no body + struct mg_str message; // Raw bytes: head + body +}; + +// Options for mg_http_serve_dir() and mg_http_serve_file(). +struct mg_http_serve_opts { + const char *root_dir; // Root directory for serving files, e.g. "/web" + const char *ssi_pattern; // Pattern for Server-Side Include files, e.g. "#.shtml". NULL disables SSI + const char *extra_headers; // Extra response headers, e.g. "A: b\r\nC: d\r\n". NULL for none + const char *mime_types; // Additional MIME types: "ext1=type1,ext2=type2". NULL for defaults only + const char *page404; // Path to a custom 404 page, e.g. "/404.html". NULL for built-in + struct mg_fs *fs; // Filesystem to use. NULL defaults to POSIX +}; + +// A single part of a multipart/form-data body, filled by mg_http_next_multipart(). +struct mg_http_part { + struct mg_str name; // Form field name, from Content-Disposition: name="..." + struct mg_str filename; // Original filename for file uploads, empty for plain fields + struct mg_str body; // Part contents, points into the original message buffer +}; + +// Parses HTTP request or response in buffer s/len into hm. +// Returns number of bytes consumed, 0 if incomplete, -1 on error. +int mg_http_parse(const char *s, size_t len, struct mg_http_message *); + +// Returns the byte length of HTTP headers in buf (i.e. offset of the body). +// Returns 0 if headers are incomplete, -1 on error. +int mg_http_get_request_len(const unsigned char *buf, size_t buf_len); + +// Sends one chunk in HTTP chunked transfer encoding using printf-style fmt. +// Call with empty fmt ("") to send the terminating zero-length chunk. +void mg_http_printf_chunk(struct mg_connection *cnn, const char *fmt, ...); + +// Sends one chunk in HTTP chunked transfer encoding from a raw buffer. +// Call with len=0 to send the terminating zero-length chunk. +void mg_http_write_chunk(struct mg_connection *c, const char *buf, size_t len); + +// Creates an HTTP server on url, e.g. "http://0.0.0.0:8000". +// +// Returns: +// Listening connection, or NULL on error. +// Example: +// mg_http_listen(&mgr, "http://0.0.0.0:8000", fn, NULL); +// Full examples: +// tutorials/http/http-server, tutorials/http/* +// Related APIs: +// mg_http_reply(), mg_http_serve_dir(), mg_match() +// Notes: +// Call mg_mgr_poll() in the main loop. The user-supplied fn event handler +// receives normal connection events. It also receives MG_EV_HTTP_HDRS when +// headers are received and MG_EV_HTTP_MSG when the full request is received. +// ev_data for both HTTP events is struct mg_http_message *. +struct mg_connection *mg_http_listen(struct mg_mgr *, const char *url, + mg_event_handler_t fn, void *fn_data); + +// Opens an HTTP client connection to url, e.g. "http://example.org". +// +// Returns: +// Client connection, or NULL on error. +// Example: +// mg_http_connect(&mgr, "http://example.org", fn, NULL); +// Full examples: +// tutorials/http/http-client, tutorials/http/huge-response +// Related APIs: +// mg_printf(), mg_http_status(), mg_http_get_header() +// Notes: +// In the user-supplied fn event handler, send the HTTP request on +// MG_EV_CONNECT. The handler receives normal connection events. It also +// receives MG_EV_HTTP_HDRS when headers are received and MG_EV_HTTP_MSG when +// the full response is received. ev_data for both HTTP events is struct +// mg_http_message *. +struct mg_connection *mg_http_connect(struct mg_mgr *, const char *url, + mg_event_handler_t fn, void *fn_data); + +// Serves static files from a directory. +// +// Example: +// struct mg_http_serve_opts o = {.root_dir = "web_root", .fs = &mg_fs_posix}; +// mg_http_serve_dir(c, hm, &o); +// Full examples: +// tutorials/http/http-server, tutorials/core/embedded-filesystem +// Related APIs: +// mg_http_listen(), mg_http_serve_file(), mg_http_reply(), mg_match() +// Notes: +// Call from an MG_EV_HTTP_MSG handler. The uri in hm is mapped under +// opts->root_dir. Directory listing depends on MG_ENABLE_DIRLIST; SSI uses +// opts->ssi_pattern when configured. +void mg_http_serve_dir(struct mg_connection *, struct mg_http_message *hm, + const struct mg_http_serve_opts *); + +// Serves a single file at path. Call from an MG_EV_HTTP_MSG handler. +void mg_http_serve_file(struct mg_connection *, struct mg_http_message *hm, + const char *path, const struct mg_http_serve_opts *); + +// Sends a complete HTTP response with Content-Length. +// +// Example: +// mg_http_reply(c, 200, "Content-Type: application/json\r\n", +// "{%m:%d}", MG_ESC("temperature"), 123); +// Full examples: +// tutorials/http/http-server, tutorials/http/http-restful-server, +// tutorials/http/device-dashboard +// Related APIs: +// mg_http_listen(), mg_printf(), mg_http_printf_chunk(), MG_ESC +// Notes: +// headers must end with "\r\n"; pass "" or NULL for no extra headers. +// body_fmt is printf-style and supports %M/%m custom printers. Use MG_ESC +// when printing JSON strings. +void mg_http_reply(struct mg_connection *, int status_code, const char *headers, + const char *body_fmt, ...); + +// Looks up a request header by name (case-insensitive). +// Returns a pointer to the mg_str value, or NULL if not found. +struct mg_str *mg_http_get_header(struct mg_http_message *, const char *name); + +// Extracts a named variable from a query string or form-encoded body (buf). +// Returns the raw (URL-encoded) value as mg_str, or an empty mg_str if not found. +// Not NUL-terminated. Use mg_url_decode() to get a decoded string. +struct mg_str mg_http_var(struct mg_str buf, struct mg_str name); + +// URL-decodes variable name from query string or form body into a NUL-terminated buffer. +// Returns the length of the decoded value, or negative if not found / buffer too small. +int mg_http_get_var(const struct mg_str *, const char *name, char *, size_t); + +// URL-decodes s/n into to/to_len. Set form=1 to also decode '+' as space. +// Returns number of bytes written, or -1 if to buffer is too small. +int mg_url_decode(const char *s, size_t n, char *to, size_t to_len, int form); + +// URL-encodes s/n into buf/len. Returns number of bytes written. +size_t mg_url_encode(const char *s, size_t n, char *buf, size_t len); + +// Extracts HTTP credentials into caller-supplied buffers. +// For Basic auth: fills user (buf1) and password (buf2). +// For Bearer auth: fills buf1 with "" and buf2 with the token. +void mg_http_creds(struct mg_http_message *, char *, size_t, char *, size_t); + +// Deprecated API, do not expose +long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm, + struct mg_fs *fs, const char *dir, size_t max_size); + +// Streams the raw request body into dir/name via fs. Non-blocking: installs an +// internal handler and returns immediately. fn is called with NULL on success +// or an error string on failure. +void mg_http_start_upload(struct mg_connection *c, struct mg_http_message *hm, + struct mg_str name, struct mg_str dir, + struct mg_fs *fs, + void (*fn)(struct mg_connection *, const char *)); + +// Starts an OTA firmware update from an HTTP POST upload. +// Calls mg_ota_begin/write/end internally; fn is called with NULL on success, +// or an error string on failure. +void mg_http_start_ota(struct mg_connection *c, struct mg_http_message *hm, + void (*fn)(struct mg_connection *, const char *)); + +// Sends a 401 Unauthorized response with a Basic Auth WWW-Authenticate challenge. +void mg_http_bauth(struct mg_connection *, const char *user, const char *pass); + +// Extracts a named parameter from a header value string s. +// E.g. for s="multipart/form-data; boundary=abc", v="boundary" returns "abc". +struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v); + +// Iterates over parts of a multipart/form-data body. +// +// Returns: +// Offset for the next call, or 0 when there are no more parts. +// Example: +// size_t ofs = 0; +// struct mg_http_part part; +// while ((ofs = mg_http_next_multipart(hm->body, ofs, &part)) > 0) { ... } +// Full examples: +// tutorials/http/file-upload-html-form, tutorials/http/http-server +// Related APIs: +// mg_http_listen(), mg_http_start_upload() +// Notes: +// Call from an MG_EV_HTTP_MSG handler after the full request body is +// received. part.name, part.filename, and part.body are zero-copy slices into +// hm->body and are not NUL-terminated. +size_t mg_http_next_multipart(struct mg_str, size_t, struct mg_http_part *); + +// Returns the HTTP status code from a parsed response message (e.g. 200, 404). +int mg_http_status(const struct mg_http_message *hm); + + +void mg_http_serve_ssi(struct mg_connection *c, const char *root, + const char *fullpath); +// TLS / Security + + +// Available TLS backend libraries +#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 +// mongoose_config.h setting. Set MG_TLS to one of the MG_TLS_* values above to +// select a TLS backend. Defaults to MG_TLS_NONE (no TLS). +#define MG_TLS MG_TLS_NONE +#endif + + + + + +// TLS options structure passed to mg_tls_init(). All cert/key fields accept +// PEM strings or DER binary. +// +// One-way TLS: +// server sets `cert` + `key`, +// client sets `ca` + optionally `name` for hostname verification. +// +// Two-way (mutual) TLS: both sides set `ca` + `cert` + `key`. +// +// - `ca`: CA certificate. Verifies the peer's certificate. +// Set on clients to authenticate the server. Set on servers to require +// and verify a client certificate. If empty, peer is not verified. +// - `cert`: Our certificate. Required on servers. Also set on clients for +// mutual TLS. +// - `key`: Our private key. May equal `cert` when PEM bundles both. +// - `name`: Server name for SNI and hostname verification. Set on clients. +// Empty disables hostname verification. +// - `skip_verification`: Skip certificate and hostname verification. +// Useful during development; do not use in production. +struct mg_tls_opts { + struct mg_str ca; // CA certificate, PEM or DER + struct mg_str cert; // Our certificate, PEM or DER + struct mg_str key; // Our private key, PEM or DER + struct mg_str name; // Server name for SNI + hostname verification + bool skip_verification; // Skip certificate and hostname verification +}; + +// Initialises TLS on a connection. +// +// Example: +// ```c +// // Server: one-way TLS +// if (ev == MG_EV_ACCEPT) { +// struct mg_tls_opts opts = {.cert = mg_str(s_tls_cert), +// .key = mg_str(s_tls_key)}; +// mg_tls_init(c, &opts); +// } +// +// // Client: verify server certificate and hostname +// if (ev == MG_EV_CONNECT) { +// struct mg_tls_opts opts = {.ca = mg_str(s_tls_ca), +// .name = mg_str("hostname")}; +// mg_tls_init(c, &opts); +// } +// ``` +// Full examples: +// tutorials/http/http-server, tutorials/http/http-client, +// tutorials/mqtt/mqtt-client-aws-iot, tutorials/websocket/websocket-client +// Related APIs: +// mg_http_listen(), mg_http_connect(), mg_ws_connect(), mg_mqtt_connect() +// Notes: +// Call from the user-supplied event handler on MG_EV_ACCEPT for servers or +// MG_EV_CONNECT for clients, before application data is sent. Servers usually +// set cert and key. Clients usually set ca and name; name enables SNI and +// hostname verification. +void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts); + +// Private API, do not expose +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_flush(struct mg_connection *); +void mg_tls_handshake(struct mg_connection *); +void mg_tls_ctx_init(struct mg_mgr *); +void mg_tls_ctx_free(struct mg_mgr *); +#define MG_IS_DER(buf) (((uint8_t *) (buf))[0] == 0x30) // DER begins with 0x30 + +// Low-level IO primitives used by TLS layer +enum { MG_IO_ERR = -1, MG_IO_WAIT = -2 }; +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); +#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 + +/****************************************************************************** + * 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, unsigned char *aead, + size_t aead_len, const unsigned char *tag, + const size_t tag_len); + +#endif /* TLS_AES128_H */ + +// End of aes128 PD + + + +/* 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 +#ifndef MG_UECC_SUPPORTS_secp384r1 +#define MG_UECC_SUPPORTS_secp384r1 1 +#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 +#if MG_UECC_SUPPORTS_secp384r1 +MG_UECC_Curve mg_uecc_secp384r1(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_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_ */ + +/* 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_ */ + +// End of uecc BSD-2 +// portable8439 v1.0.1 +// Source: https://github.com/DavyLandman/portable8439 +// Licensed under CC0-1.0 +// Contains poly1305-donna e6ad6e091d30d7f4ec2d4f978be1fcfcbce72781 (Public +// Domain) + + + + + +#if MG_TLS == MG_TLS_BUILTIN +#ifndef __PORTABLE_8439_H +#define __PORTABLE_8439_H +#if defined(__cplusplus) +extern "C" { +#endif + +// provide your own decl specificier like -DPORTABLE_8439_DECL=ICACHE_RAM_ATTR +#ifndef PORTABLE_8439_DECL +#define PORTABLE_8439_DECL +#endif + +/* + This library implements RFC 8439 a.k.a. ChaCha20-Poly1305 AEAD + + You can use this library to avoid attackers mutating or reusing your + encrypted messages. This does assume you never reuse a nonce+key pair and, + if possible, carefully pick your associated data. +*/ + +/* Make sure we are either nested in C++ or running in a C99+ compiler +#if !defined(__cplusplus) && !defined(_MSC_VER) && \ + (!defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L) +#error "C99 or newer required" +#endif */ + +// #if CHAR_BIT > 8 +// # error "Systems without native octals not suppoted" +// #endif + +#if defined(_MSC_VER) || defined(__cplusplus) +// add restrict support is possible +#if (defined(_MSC_VER) && _MSC_VER >= 1900) || defined(__clang__) || \ + defined(__GNUC__) +#define restrict __restrict +#else +#define restrict +#endif +#endif + +#define RFC_8439_TAG_SIZE (16) +#define RFC_8439_KEY_SIZE (32) +#define RFC_8439_NONCE_SIZE (12) + +/* + Encrypt/Seal plain text bytes into a cipher text that can only be + decrypted by knowing the key, nonce and associated data. + + input: + - key: RFC_8439_KEY_SIZE bytes that all parties have agreed + upon beforehand + - nonce: RFC_8439_NONCE_SIZE bytes that should never be repeated + for the same key. A counter or a pseudo-random value are fine. + - ad: associated data to include with calculating the tag of the + cipher text. Can be null for empty. + - plain_text: data to be encrypted, pointer + size should not overlap + with cipher_text pointer + + output: + - cipher_text: encrypted plain_text with a tag appended. Make sure to + allocate at least plain_text_size + RFC_8439_TAG_SIZE + + returns: + - size of bytes written to cipher_text, can be -1 if overlapping + pointers are passed for plain_text and cipher_text +*/ +PORTABLE_8439_DECL size_t mg_chacha20_poly1305_encrypt( + uint8_t *restrict cipher_text, const uint8_t key[RFC_8439_KEY_SIZE], + const uint8_t nonce[RFC_8439_NONCE_SIZE], const uint8_t *restrict ad, + size_t ad_size, const uint8_t *restrict plain_text, size_t plain_text_size); + +/* + Decrypt/unseal cipher text given the right key, nonce, and additional data. + + input: + - key: RFC_8439_KEY_SIZE bytes that all parties have agreed + upon beforehand + - nonce: RFC_8439_NONCE_SIZE bytes that should never be repeated for + the same key. A counter or a pseudo-random value are fine. + - ad: associated data to include with calculating the tag of the + cipher text. Can be null for empty. + - cipher_text: encrypted message. + + output: + - plain_text: data to be encrypted, pointer + size should not overlap + with cipher_text pointer, leave at least enough room for + cipher_text_size - RFC_8439_TAG_SIZE + + returns: + - size of bytes written to plain_text, -1 signals either: + - incorrect key/nonce/ad + - corrupted cipher_text + - overlapping pointers are passed for plain_text and cipher_text +*/ +PORTABLE_8439_DECL size_t mg_chacha20_poly1305_decrypt( + uint8_t *restrict plain_text, const uint8_t key[RFC_8439_KEY_SIZE], + const uint8_t nonce[RFC_8439_NONCE_SIZE], + const uint8_t *restrict ad, size_t ad_size, + const uint8_t *restrict cipher_text, size_t cipher_text_size); +#if defined(__cplusplus) +} +#endif +#endif +#endif +#ifndef TLS_RSA_H +#define TLS_RSA_H + + +int mg_rsa_mod_pow(const uint8_t *mod, size_t modsz, const uint8_t *exp, size_t expsz, const uint8_t *msg, size_t msgsz, uint8_t *out, size_t outsz); +int mg_rsa_crt_sign(const uint8_t *em, size_t em_len, + const uint8_t *dP, size_t dP_len, + const uint8_t *dQ, size_t dQ_len, + const uint8_t *p, size_t p_len, + const uint8_t *q, size_t q_len, + const uint8_t *qInv, size_t qInv_len, + uint8_t *signature, size_t sig_len); +bool mg_rsa_verify(const uint8_t *em, size_t nlen, const uint8_t *mhash); +#endif // TLS_RSA_H + + + + + + + +#if MG_TLS == MG_TLS_MBED +#include +#include +#include +#include +#include + +struct mg_tls_ctx { + int dummy; +#ifdef MBEDTLS_SSL_SESSION_TICKETS + mbedtls_ssl_ticket_context tickets; +#endif +}; + +struct mg_tls { + 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 +#ifdef MBEDTLS_SSL_SESSION_TICKETS + mbedtls_ssl_ticket_context ticket; // Session tickets context +#endif + // https://github.com/Mbed-TLS/mbedtls/blob/3b3c652d/include/mbedtls/ssl.h#L5071C18-L5076C29 + unsigned char *throttled_buf; // see #3074 + size_t throttled_len; + bool check_name; // set when hostname was set, but no CA certificate given +}; +#endif + + +#if MG_TLS == MG_TLS_OPENSSL || MG_TLS == MG_TLS_WOLFSSL + +#include +#include +#include + +struct mg_tls { + BIO_METHOD *bm; + SSL_CTX *ctx; + SSL *ssl; + char *name; // matching hostname + bool check_name; // set when hostname was set, but no CA certificate given +}; +#endif + + +// WebSocket frame opcodes, used in mg_ws_send() op parameter and +// in the low 4 bits of mg_ws_message.flags. +#define WEBSOCKET_OP_CONTINUE 0 // Continuation frame (fragmented message) +#define WEBSOCKET_OP_TEXT 1 // Text (UTF-8) data frame +#define WEBSOCKET_OP_BINARY 2 // Binary data frame +#define WEBSOCKET_OP_CLOSE 8 // Connection close control frame +#define WEBSOCKET_OP_PING 9 // Ping control frame (auto-replied with PONG) +#define WEBSOCKET_OP_PONG 10 // Pong control frame + + + +// WebSocket message delivered as ev_data in MG_EV_WS_MSG and MG_EV_WS_CTL. +// data is a zero-copy slice into the receive buffer; valid until the next +// mg_mgr_poll() call. +struct mg_ws_message { + struct mg_str data; // Message payload + uint8_t flags; // First byte of the WS frame header: FIN (bit7) | opcode (bits3:0) +}; + +// Opens an outbound WebSocket connection. +// +// Returns: +// Client connection, or NULL on error. +// Example: +// mg_ws_connect(&mgr, "ws://localhost:8000/websocket", fn, NULL, NULL); +// Full examples: +// tutorials/websocket/websocket-client, tutorials/mqtt/mqtt-over-ws-client +// Related APIs: +// mg_ws_send(), mg_ws_printf(), mg_tls_init(), mg_ws_upgrade() +// Notes: +// url may use ws:// or wss://. Sends the HTTP Upgrade request immediately. +// fmt is a printf-style string for extra HTTP request headers; each header +// must end with "\r\n". The user-supplied fn event handler receives +// MG_EV_WS_OPEN on handshake success, MG_EV_WS_MSG for messages, and +// MG_EV_WS_CTL for control frames. +struct mg_connection *mg_ws_connect(struct mg_mgr *, const char *url, + mg_event_handler_t fn, void *fn_data, + const char *fmt, ...); + +// Upgrades a server-side HTTP connection to WebSocket. +// +// Example: +// if (mg_match(hm->uri, mg_str("/websocket"), NULL)) { +// mg_ws_upgrade(c, hm, NULL); +// } +// Full examples: +// tutorials/websocket/websocket-server, +// tutorials/websocket/json-rpc-over-websocket, tutorials/core/timers +// Related APIs: +// mg_http_listen(), mg_match(), mg_ws_send(), mg_ws_printf() +// Notes: +// Call from an MG_EV_HTTP_MSG handler and pass that event's hm. fmt is a +// printf-style string for extra response headers; each header must end with +// "\r\n". Fires MG_EV_WS_OPEN immediately on success. Sends HTTP 426 and +// drains the connection if the request lacks Sec-WebSocket-Key. +void mg_ws_upgrade(struct mg_connection *, struct mg_http_message *, + const char *fmt, ...); + +// Sends one WebSocket frame. +// +// Returns: +// Number of bytes appended to c->send; 0 if the frame header cannot be +// appended. +// Example: +// mg_ws_send(c, "hello", 5, WEBSOCKET_OP_TEXT); +// Full examples: +// tutorials/websocket/websocket-server, tutorials/websocket/websocket-client, +// tutorials/core/timers +// Related APIs: +// mg_ws_connect(), mg_ws_upgrade(), mg_ws_printf(), mg_ws_wrap() +// Notes: +// op is one of WEBSOCKET_OP_*. Client connections are automatically masked +// per RFC 6455. On OOM, the return value can be smaller than header + len. +// Data is appended to c->send and sent by a later mg_mgr_poll() call. +size_t mg_ws_send(struct mg_connection *, const void *buf, size_t len, int op); + +// Wraps the last len bytes already in c->send with a WebSocket frame header +// and opcode op. Used internally by mg_ws_printf(); call it directly when you +// have written data into c->send manually and need to frame it. +// Returns c->send.len (total buffer size after the operation). +size_t mg_ws_wrap(struct mg_connection *, size_t len, int op); + +// Formats a WebSocket message using printf-style fmt and sends it with +// opcode op (WEBSOCKET_OP_TEXT or WEBSOCKET_OP_BINARY). +// Returns the number of payload bytes written, or 0 on OOM. +size_t mg_ws_printf(struct mg_connection *c, int op, const char *fmt, ...); +size_t mg_ws_vprintf(struct mg_connection *c, int op, const char *fmt, + va_list *); +// Time / SNTP functions + + + + + +// Return milliseconds since boot (uptime). Never goes backwards. +// On RTOS targets (FreeRTOS, ThreadX, Zephyr, etc.) uses the RTOS tick count. +// On STM32/Cube uses HAL_GetTick(). On Arduino uses millis(). +// On Linux/macOS uses CLOCK_MONOTONIC_RAW or CLOCK_MONOTONIC; falls back to +// CLOCK_REALTIME if neither is available. +uint64_t mg_millis(void); + +// Boot timestamp in ms since Epoch. 0 until the first successful SNTP sync; +// updated automatically by mg_sntp_connect() on each successful response. +extern uint64_t mg_boot_timestamp_ms; + +// Return milliseconds since Epoch: mg_millis() + mg_boot_timestamp_ms. +// Until a successful SNTP request completes, this is identical to mg_millis(). +uint64_t mg_now(void); + +// Checks whether a periodic deadline has expired. +// +// Returns: +// True when the timer has fired; false otherwise. +// Example: +// uint64_t timer = 0; +// if (mg_timer_expired(&timer, 1000, mg_millis())) { +// MG_INFO(("every second")); +// } +// Full examples: +// tutorials/http/http-server/arduino/teensy41-http, +// tutorials/mqtt/mqtt-client +// Related APIs: +// mg_millis(), mg_now() +// Notes: +// Initialise *expiration to 0 before first use. On expiry, this function +// advances *expiration by period and handles time wrap-around. +bool mg_timer_expired(uint64_t *expiration, uint64_t period, uint64_t now); + +// Connect to an SNTP server and send a time request. +// `url` defaults to "udp://time.google.com:123" when NULL. +// On success the internal boot timestamp is updated so that mg_now() returns +// the correct wall-clock time, and MG_EV_SNTP_TIME is fired on `fn` (if not +// NULL) with ev_data pointing to a uint64_t containing the epoch in milliseconds. +// Pass fn == NULL and fn_data == NULL for a fire-and-forget sync that only +// updates mg_boot_timestamp_ms. Typical polling usage: +// +// ```c +// uint64_t timer = 0; +// for (;;) { +// uint64_t period = mg_boot_timestamp_ms == 0 ? 1000 : 3600 * 1000; +// if (mg_timer_expired(&timer, period, mg_millis())) { +// mg_sntp_connect(&mgr, NULL, NULL, NULL); +// } +// mg_mgr_poll(&mgr, 1000); +// } +// ``` +struct mg_connection *mg_sntp_connect(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data); + +// Private API, do not expose +void mg_sntp_request(struct mg_connection *c); +int64_t mg_sntp_parse(const unsigned char *buf, size_t len); + + + + + +// MQTT packet type codes. The received command is in mg_mqtt_message.cmd. +#define MQTT_CMD_CONNECT 1 +#define MQTT_CMD_CONNACK 2 +#define MQTT_CMD_PUBLISH 3 +#define MQTT_CMD_PUBACK 4 +#define MQTT_CMD_PUBREC 5 +#define MQTT_CMD_PUBREL 6 +#define MQTT_CMD_PUBCOMP 7 +#define MQTT_CMD_SUBSCRIBE 8 +#define MQTT_CMD_SUBACK 9 +#define MQTT_CMD_UNSUBSCRIBE 10 +#define MQTT_CMD_UNSUBACK 11 +#define MQTT_CMD_PINGREQ 12 +#define MQTT_CMD_PINGRESP 13 +#define MQTT_CMD_DISCONNECT 14 +#define MQTT_CMD_AUTH 15 + +// MQTT5 property IDs. Used in mg_mqtt_prop.id. +#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 + +// Property value types used internally to encode/decode MQTT5 properties. +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 +}; + +// Return codes from mg_mqtt_parse(). +enum { MQTT_OK, MQTT_INCOMPLETE, MQTT_MALFORMED }; + +// A single MQTT5 property. Use in mg_mqtt_opts.props / mg_mqtt_opts.will_props +// arrays, and iterate received properties with mg_mqtt_next_prop(). +struct mg_mqtt_prop { + uint8_t id; // Property ID, one of MQTT_PROP_* + uint32_t iv; // Integer value for BYTE, SHORT, INT, VARIABLE_INT types + struct mg_str key; // Property key string; only set for USER_PROPERTY pairs + struct mg_str val; // String/binary value; set for STRING, BINARY_DATA, and USER_PROPERTY +}; + +// Options passed to mg_mqtt_connect(), mg_mqtt_pub(), mg_mqtt_sub(), +// mg_mqtt_unsub(), and mg_mqtt_disconnect(). Zero-initialise and set only +// the fields relevant to the operation being called. +struct mg_mqtt_opts { + struct mg_str user; // CONNECT: username; empty = omit + struct mg_str pass; // CONNECT: password; empty = omit + struct mg_str client_id; // CONNECT: client ID; empty = auto-generate + struct mg_str topic; // CONNECT: will topic; PUBLISH/SUBSCRIBE/UNSUBSCRIBE: topic + struct mg_str message; // CONNECT: will payload; PUBLISH: message payload + uint8_t qos; // QoS level: 0, 1, or 2 + uint8_t version; // MQTT version: 4 (3.1.1) or 5; 0 defaults to 4 + uint16_t keepalive; // CONNECT: keepalive interval in seconds; 0 = no timeout + uint16_t retransmit_id; // PUBLISH: packet ID for retransmit; init to 0 for new message + bool retain; // PUBLISH/CONNECT will: set the retain flag + bool clean; // CONNECT: start a clean session + struct mg_mqtt_prop *props; // MQTT5: properties array; NULL if none + size_t num_props; // MQTT5: number of entries in props + struct mg_mqtt_prop *will_props; // MQTT5 CONNECT: will properties; NULL if none + size_t num_will_props; // MQTT5: number of entries in will_props +}; + +// Received MQTT message. Passed as ev_data for MG_EV_MQTT_MSG (PUBLISH), +// MG_EV_MQTT_CMD (all packets), and MG_EV_MQTT_OPEN (CONNACK). +// topic, data, and dgram are zero-copy slices into the receive buffer; they +// are only valid until the next mg_mgr_poll() call. +struct mg_mqtt_message { + struct mg_str topic; // PUBLISH: message topic; slice into dgram + struct mg_str data; // PUBLISH: message payload; slice into dgram + struct mg_str dgram; // Complete raw MQTT packet, including fixed header + uint16_t id; // Packet ID; set for QoS>0 PUBLISH, PUBACK, PUBREC, PUBREL, PUBCOMP, SUBACK, UNSUBACK + uint8_t cmd; // Packet type, one of MQTT_CMD_* + uint8_t qos; // QoS level extracted from the PUBLISH fixed header + uint8_t ack; // CONNACK return code; 0 = success + size_t props_start; // MQTT5: byte offset from dgram.buf to start of properties + size_t props_size; // MQTT5: byte length of the properties section +}; + +// Opens an MQTT client connection and sends a CONNECT packet. +// +// Returns: +// Client connection, or NULL on error. +// Example: +// struct mg_mqtt_opts opts = {.client_id = mg_str("device1")}; +// mg_mqtt_connect(&mgr, "mqtt://broker:1883", &opts, fn, NULL); +// Full examples: +// tutorials/mqtt/mqtt-client, tutorials/mqtt/mqtt-client-aws-iot, +// tutorials/mqtt/ota-over-mqtt +// Related APIs: +// mg_mqtt_pub(), mg_mqtt_sub(), mg_tls_init(), mg_mgr_poll() +// Notes: +// opts may be NULL to use MQTT 3.1.1 defaults with no auth and an auto client +// ID. The user-supplied fn event handler receives normal connection events, +// MG_EV_MQTT_OPEN when CONNACK is received, MG_EV_MQTT_MSG for incoming +// PUBLISH packets, and MG_EV_MQTT_CMD for every received MQTT packet. +struct mg_connection *mg_mqtt_connect(struct mg_mgr *, const char *url, + const struct mg_mqtt_opts *opts, + mg_event_handler_t fn, void *fn_data); + +// Creates an MQTT server listener on url. +// Fires MG_EV_MQTT_CMD for each complete packet received (ev_data: struct mg_mqtt_message *). +// Returns NULL on error. +struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data); + +// Sends a CONNECT packet on an already-open connection. +// Called automatically by mg_mqtt_connect(); exposed for advanced use such +// as re-authenticating on an existing connection. +void mg_mqtt_login(struct mg_connection *c, const struct mg_mqtt_opts *opts); + +// Sends an MQTT PUBLISH packet. +// +// Returns: +// 0 for QoS 0; assigned packet ID for QoS 1 or 2. +// Example: +// struct mg_mqtt_opts opts = { +// .topic = mg_str("device/status"), +// .message = mg_str("{\"ok\":true}"), +// .qos = 1, +// }; +// uint16_t id = mg_mqtt_pub(c, &opts); +// Full examples: +// tutorials/mqtt/mqtt-client, tutorials/mqtt/mqtt-client-aws-iot, +// tutorials/mqtt/ota-over-mqtt +// Related APIs: +// mg_mqtt_connect(), mg_mqtt_sub(), mg_mqtt_send_header() +// Notes: +// On success, opts.topic and opts.message are copied into c->send before the +// function returns. To retransmit a QoS message, set opts.retransmit_id to the +// packet ID returned by the previous call; use 0 for a new message. +uint16_t mg_mqtt_pub(struct mg_connection *c, const struct mg_mqtt_opts *opts); + +// Sends an MQTT SUBSCRIBE packet. +// +// Example: +// struct mg_mqtt_opts opts = {.topic = mg_str("device/rx"), .qos = 1}; +// mg_mqtt_sub(c, &opts); +// Full examples: +// tutorials/mqtt/mqtt-client, tutorials/mqtt/mqtt-client-aws-iot, +// tutorials/mqtt/ota-over-mqtt +// Related APIs: +// mg_mqtt_connect(), mg_mqtt_pub(), mg_mqtt_unsub() +// Notes: +// Send after MG_EV_MQTT_OPEN reports a successful CONNACK. Incoming PUBLISH +// messages are delivered to the user-supplied fn event handler as +// MG_EV_MQTT_MSG with ev_data pointing to struct mg_mqtt_message. +void mg_mqtt_sub(struct mg_connection *, const struct mg_mqtt_opts *opts); + +// Sends an UNSUBSCRIBE packet for opts.topic. +void mg_mqtt_unsub(struct mg_connection *c, const struct mg_mqtt_opts *opts); + +// Parses a raw MQTT packet in buf/len. version must be 4 (MQTT 3.1.1) or 5. +// On success, fills *m; topic, data, and dgram slice into buf (zero-copy). +// Returns MQTT_OK, MQTT_INCOMPLETE (need more data), or MQTT_MALFORMED. +int mg_mqtt_parse(const uint8_t *, size_t, uint8_t, struct mg_mqtt_message *); + +// Low-level helper: sends the MQTT fixed header (command byte and +// variable-length remaining-length field). Call before writing the PDU body. +// cmd: MQTT_CMD_*; flags: low 4 bits of the first byte; len: remaining length. +void mg_mqtt_send_header(struct mg_connection *, uint8_t cmd, uint8_t flags, + uint32_t len); + +// Sends a PINGREQ packet. +void mg_mqtt_ping(struct mg_connection *); + +// Sends a PINGRESP packet. +void mg_mqtt_pong(struct mg_connection *); + +// Sends a DISCONNECT packet. For MQTT5, opts.props can carry reason +// properties. opts may be NULL for a plain disconnect with no properties. +void mg_mqtt_disconnect(struct mg_connection *, const struct mg_mqtt_opts *); + +// Iterates MQTT5 properties in a received message. Start with ofs=0; pass +// the returned value back on each successive call. Returns 0 when done. +// Fills *prop with each property; key and val are zero-copy slices into msg->dgram. +size_t mg_mqtt_next_prop(struct mg_mqtt_message *, struct mg_mqtt_prop *, + size_t ofs); + + + + + +// DNS record types, used in mg_dns_rr.atype and mg_mdns_query(). +#define MG_DNS_RTYPE_A 1 // IPv4 address (4 bytes) +#define MG_DNS_RTYPE_PTR 12 // Domain name pointer, used for reverse lookups and service discovery +#define MG_DNS_RTYPE_TXT 16 // Arbitrary text, used by DNS-SD for service metadata +#define MG_DNS_RTYPE_AAAA 28 // IPv6 address (16 bytes) +#define MG_DNS_RTYPE_SRV 33 // Service location: priority, weight, port, target hostname + +// Mongoose sends DNS queries that contain only one question: +// either A (IPv4) or AAAA (IPv6) address lookup. +// Therefore, we expect zero or one answer. +// If `resolved` is true, then `addr` contains resolved IPv4 or IPV6 address. +struct mg_dns_message { + uint16_t txnid; // Transaction ID + bool resolved; // Resolve successful, addr is set + struct mg_addr addr; // Resolved address + char name[256]; // Host name +}; + +// DNS wire-format header (RFC 1035). All fields are in network byte order; +// use mg_ntohs() to read them. +struct mg_dns_header { + uint16_t txnid; // Transaction ID, matched between query and response + uint16_t flags; // Flags: bit15=QR (0=query,1=response), bit10=AA, bit8=RD + uint16_t num_questions; // Number of question records following the header + uint16_t num_answers; // Number of answer records in the response + uint16_t num_authority_prs; // Number of authority records + uint16_t num_other_prs; // Number of additional records +}; + +// Parsed DNS resource record fields, extracted from the wire format. +struct mg_dns_rr { + uint16_t nlen; // Byte length of the encoded name in the wire format + uint16_t atype; // Record type: MG_DNS_RTYPE_A=1, AAAA=28, PTR=12, SRV=33, TXT=16 + uint16_t aclass; // Record class: 1=INET; mDNS queries: bit15=QU (unicast request) + uint16_t alen; // Byte length of the RDATA section (e.g. 4 for A, 16 for AAAA) +}; + +// DNS-SD service description. Used by the user to describe a service to advertise. +struct mg_dnssd_record { + struct mg_str srvcproto; // Service and protocol label, e.g. "_http._tcp" + struct mg_str txt; // TXT record contents, verbatim + uint16_t port; // Port number for the SRV record +}; + +// ev_data for MG_EV_MDNS_REQ. The handler must set is_resp=true and fill r +// (and optionally respname/addr) to send a response; leave is_resp=false to ignore. +struct mg_mdns_req { + struct mg_dns_rr *rr; // Parsed resource record from the incoming query + struct mg_dnssd_record *r; // User-supplied service record to include in the response + struct mg_str reqname; // Queried hostname, without the .local suffix + struct mg_str respname; // Hostname to use in response; defaults to fn_data if empty + struct mg_addr *addr; // IP address for A record; uses local interface if NULL + bool is_listing; // True if this is a service-discovery listing (_services._dns-sd._udp) + bool is_resp; // Set to true in the handler to trigger a response + bool is_unicast; // True if the client requested a unicast (QU) response +}; + +// ev_data for MG_EV_MDNS_RESP, carrying the resolved address from an mDNS response. +struct mg_mdns_resp { + struct mg_dns_rr *rr; // Resource record from the response + struct mg_str name; // Resolved hostname, without the .local suffix + struct mg_addr addr; // Resolved IP address +}; + +// Parses a DNS query or response from buf/len into dm. Returns true on success. +// On a response, sets dm->resolved=true and fills dm->addr if an A/AAAA record +// is found. Always fills dm->txnid and dm->name. Rejects packets larger than 512 bytes. +bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *dm); + +// Parses one DNS record at offset ofs in buf/len into rr. Set is_question=true +// for question records (no RDATA), false for answer records. Returns the number +// of bytes consumed (use to advance ofs), or 0 on error. +size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs, + bool is_question, struct mg_dns_rr *rr); + +// Creates an mDNS listener on UDP multicast 224.0.0.251:5353. Registers it as +// the manager's mDNS resolver (mgr->mdns). Fires MG_EV_MDNS_REQ for incoming +// queries and MG_EV_MDNS_RESP for incoming responses. Returns NULL on error. +struct mg_connection *mg_mdns_listen(struct mg_mgr *mgr, mg_event_handler_t fn, + void *fn_data); + +// Sends an mDNS query on connection c (from mg_mdns_listen()) for name, with +// record type rtype (e.g. MG_DNS_RTYPE_A). Returns false on send error. +bool mg_mdns_query(struct mg_connection *c, const char *name, unsigned int rtype); + + +// Private API, do not expose or call in the user app +void mg_resolve(struct mg_connection *, const char *url); +void mg_resolve_cancel(struct mg_connection *); + + + + +// Modbus TCP function codes passed in mg_modbus_req.func. +#define MG_MODBUS_FUNC_READ_COILS 1 // Read 1-2000 discrete output coils +#define MG_MODBUS_FUNC_READ_DISCRETE_INPUTS 2 // Read 1-2000 discrete input coils +#define MG_MODBUS_FUNC_READ_HOLDING_REGISTERS 3 // Read 1-125 holding registers +#define MG_MODBUS_FUNC_READ_INPUT_REGISTERS 4 // Read 1-125 input registers +#define MG_MODBUS_FUNC_WRITE_SINGLE_COIL 5 // Write one output coil +#define MG_MODBUS_FUNC_WRITE_SINGLE_REGISTER 6 // Write one holding register +#define MG_MODBUS_FUNC_WRITE_MULTIPLE_COILS 15 // Write 1-1968 output coils +#define MG_MODBUS_FUNC_WRITE_MULTIPLE_REGISTERS 16 // Write 1-123 holding registers + +// Standard Modbus exception codes. Set mg_modbus_req.error to one of these +// in the MG_EV_MODBUS_REQ handler to return an exception response to the client. +#define MG_MODBUS_ERR_NONE 0 // No error; send a normal response +#define MG_MODBUS_ERR_ILLEGAL_FUNCTION 1 // Function code not supported +#define MG_MODBUS_ERR_ILLEGAL_ADDRESS 2 // Starting address or range out of bounds +#define MG_MODBUS_ERR_ILLEGAL_VALUE 3 // Data value not accepted +#define MG_MODBUS_ERR_DEVICE_FAILURE 4 // Unrecoverable device error + +// Modbus request passed as ev_data to the MG_EV_MODBUS_REQ event handler. +// +// For READ functions (READ_COILS, READ_DISCRETE_INPUTS, READ_HOLDING_REGISTERS, +// READ_INPUT_REGISTERS): u.bits / u.regs points to a zeroed, heap-allocated +// array of len entries. The handler fills in the values to return. +// +// For WRITE functions: u.bits / u.regs is pre-populated from the client +// request. len=1 for single-write functions. +// +// Do NOT free u.bits or u.regs - the library frees them after the handler +// returns and the response has been sent. +// +// Set error to a non-zero MG_MODBUS_ERR_* code to send an exception response +// instead of a data response. The response is sent automatically on return. +struct mg_modbus_req { + uint8_t func; // Function code, one of MG_MODBUS_FUNC_* + uint8_t error; // Exception code; set to MG_MODBUS_ERR_* to return an error + uint16_t addr; // Starting coil/register address from the request + union { + bool *bits; // Coil values; used by coil function codes + uint16_t *regs; // Register values; used by register function codes + } u; + uint16_t len; // Number of entries in u.bits or u.regs +}; + +// Creates a Modbus TCP listener on url. Fires MG_EV_MODBUS_REQ for each +// complete request; ev_data is struct mg_modbus_req *. Returns the listening +// connection, or NULL on error. +struct mg_connection *mg_modbus_listen(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data); + + + + + +// Maximum JSON nesting depth. mg_json_get() returns MG_JSON_TOO_DEEP if +// the path traverses more than this many nested objects/arrays. +#ifndef MG_JSON_MAX_DEPTH +#define MG_JSON_MAX_DEPTH 30 +#endif + +// Error codes returned by mg_json_get(). Successful returns are >= 0. +enum { MG_JSON_TOO_DEEP = -1, MG_JSON_INVALID = -2, MG_JSON_NOT_FOUND = -3 }; + +// Low-level token locator. Finds the JSON token at path inside json and +// returns its byte offset in json.buf. Sets *toklen to the token length. +// toklen may be NULL if only a presence check is needed. +// path must start with "$" (root). Use ".key" for object fields and "[N]" +// for array indices. Example: "$.items[0].name" +// Returns MG_JSON_NOT_FOUND, MG_JSON_INVALID, or MG_JSON_TOO_DEEP on error. +int mg_json_get(struct mg_str json, const char *path, int *toklen); + +// Returns the raw JSON token at path. +// +// Returns: +// Zero-copy mg_str slice into json.buf, or {NULL, 0} if not found or invalid. +// Example: +// struct mg_str tok = mg_json_get_tok(body, "$.device.name"); +// Full examples: +// src/dash.c +// Related APIs: +// mg_json_get(), mg_json_get_str(), mg_json_unescape() +// Notes: +// The returned token is not NUL-terminated. String tokens include the +// surrounding double quotes and escape sequences; use mg_json_unescape() or +// mg_json_get_str() when you need decoded string content. +struct mg_str mg_json_get_tok(struct mg_str json, const char *path); + +// Parses a numeric JSON value at path into *v. +// Returns true and sets *v on success. Returns false if not found or not a +// number. v may be NULL to check existence only. +bool mg_json_get_num(struct mg_str json, const char *path, double *v); + +// Parses a boolean JSON value at path into *v. +// Returns true and sets *v on success. Returns false if not found or not a +// boolean literal. v may be NULL to check existence only. +bool mg_json_get_bool(struct mg_str json, const char *path, bool *v); + +// Returns a JSON number at path as a long, or dflt if not found or not a +// number. Use mg_json_get_num() when you need to distinguish missing from 0. +long mg_json_get_long(struct mg_str json, const char *path, long dflt); + +// Returns a heap-allocated, NUL-terminated, JSON-unescaped copy of the string +// at path. Caller must mg_free() the result. Returns NULL if not found or the +// token is not a JSON string. +char *mg_json_get_str(struct mg_str json, const char *path); + +// Decodes a hex-encoded JSON string at path into a heap-allocated byte array. +// Sets *len to the decoded byte count. Caller must mg_free() the result. +// Returns NULL if not found or not a string. +char *mg_json_get_hex(struct mg_str json, const char *path, int *len); + +// Decodes a base64-encoded JSON string at path into a heap-allocated byte +// array. Sets *len to the decoded byte count. Caller must mg_free() the result. +// Returns NULL if not found or not a string. +char *mg_json_get_b64(struct mg_str json, const char *path, int *len); + +// Writes a JSON-unescaped string value at path into a caller-supplied buffer. +// +// Returns: +// Number of bytes written excluding the NUL, or 0 on error, not found, +// non-string token, too-small buffer, or an empty string. +// Example: +// char name[32]; +// mg_json_unescape(body, "$.device.name", name, sizeof(name)); +// Full examples: +// src/dash.c, src/ota.c +// Related APIs: +// mg_json_get_tok(), mg_json_get_str(), mg_json_get_num() +// Notes: +// On success, NUL-terminates when n > 0. The destination buffer is owned by +// the caller. Only string tokens are unescaped; other token types return 0. +size_t mg_json_unescape(struct mg_str json, const char *path, char *, size_t); + +// Sequential iterator over a JSON object or array. Start with ofs=0. +// Each call fills *key and *val with the next entry and returns the new ofs +// to pass on the next call. Returns 0 when there are no more entries. +// For arrays, *key is set to {NULL, 0}. For objects, *key is the raw +// quoted key (e.g. "\"name\""). *val is the raw unparsed token. +size_t mg_json_next(struct mg_str obj, size_t ofs, struct mg_str *key, + struct mg_str *val); + + + + +// In-flight JSON-RPC request. Populate and pass to mg_rpc_process() to +// dispatch, or receive one pre-filled in a handler registered with mg_rpc_add(). +struct mg_rpc_req { + struct mg_rpc **head; // Head of the handler list; allows handlers to call other RPCs + struct mg_rpc *rpc; // Handler that matched this request; set by mg_rpc_process() + mg_pfn_t pfn; // Output function for the response (e.g. mg_pfn_iobuf) + void *pfn_data; // Argument passed to pfn (e.g. pointer to an mg_iobuf) + void *req_data; // Arbitrary caller data, not touched by the RPC layer + struct mg_str frame; // Raw JSON-RPC frame, e.g. {"id":1,"method":"add","params":[1,2]} +}; + +// A registered JSON-RPC method handler. Managed by mg_rpc_add/del; do not +// allocate directly. method is a glob pattern matched via mg_match(). +struct mg_rpc { + struct mg_rpc *next; // Next handler in the linked list + struct mg_str method; // Method name glob pattern (heap-allocated copy) + void (*fn)(struct mg_rpc_req *); // Handler function called on a match + void *fn_data; // Passed to fn as r->rpc->fn_data +}; + +// Allocates a handler entry and prepends it to *head. method_pattern is +// copied (mg_strdup). Glob patterns are supported, e.g. "get/*". +// To receive JSON-RPC responses ($.result / $.error), register with method="" +void mg_rpc_add(struct mg_rpc **head, struct mg_str method_pattern, + void (*handler)(struct mg_rpc_req *), void *handler_data); + +// Removes and frees all handlers in *head whose fn matches handler. +// Pass handler=NULL to remove every handler in the list. +void mg_rpc_del(struct mg_rpc **head, void (*handler)(struct mg_rpc_req *)); + +// Dispatches r->frame to the matching handler. Looks up "$.method" and calls +// the first handler whose pattern matches. If "$.result" or "$.error" is found +// instead (i.e. r->frame is a JSON-RPC response), dispatches to the "" handler. +// Sends a -32601 "not found" error if no handler matches, or -32700 on parse failure. +void mg_rpc_process(struct mg_rpc_req *); + +// Sends a JSON-RPC success response: {"id":,"result":}. +// fmt is a printf-style format string written to r->pfn; NULL sends "null". +// Does nothing if the request frame has no "id" field (notification). +void mg_rpc_ok(struct mg_rpc_req *, const char *fmt, ...); +void mg_rpc_vok(struct mg_rpc_req *, const char *fmt, va_list *ap); + +// Sends a JSON-RPC error response: {"id":,"error":{"code":,"message":}}. +// fmt is a printf-style format string; NULL sends "null" as the message. +// The "id" field is omitted if the request had none; the error is always sent. +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 *); + +// Built-in handler: responds with a JSON array of all registered method names. +// Register it as a method handler, e.g. mg_rpc_add(&head, mg_str("rpc.list"), mg_rpc_list, NULL); +void mg_rpc_list(struct mg_rpc_req *r); + + + + + + + + + +#ifndef MG_DASH_MAX_USER_NAME +#define MG_DASH_MAX_USER_NAME 32 +#endif + +#ifndef MG_HTTP_ADDR +#if MG_ARCH == MG_ARCH_UNIX || MG_ARCH == MG_ARCH_WIN32 +#define MG_HTTP_ADDR "http://0.0.0.0:8000" +#define MG_HTTPS_ADDR "https://0.0.0.0:8443" +#define MG_MODBUS_ADDR "tcp://0.0.0.0:8502" +#else +#define MG_HTTP_ADDR "http://0.0.0.0:80" +#define MG_HTTPS_ADDR "http://0.0.0.0:443" +#define MG_MODBUS_ADDR "tcp://0.0.0.0:502" +#endif +#endif + +#ifndef MG_DASH_MAX_SEND_BUF_SIZE +#define MG_DASH_MAX_SEND_BUF_SIZE (MG_IO_SIZE * 5) +#endif + +// C type of a value bound to a dashboard field, see struct mg_field +enum mg_val_type { + MG_VAL_INT, // int + MG_VAL_UINT64, // uint64_t + MG_VAL_BOOL, // bool + MG_VAL_DBL, // double + MG_VAL_STR, // Zero-terminated string + MG_VAL_RAW, // Pre-formatted JSON value, copied verbatim +}; + +// Dashboard field: binds a JSON key to a C variable for read and/or write +struct mg_field { + const char *name; // JSON key + enum mg_val_type type; // C type of the value, see enum mg_val_type + void *value; // Pointer to the bound variable + size_t value_size; // sizeof(*value) for type checking; 0 makes it read-only +}; + +// Operation passed to a struct mg_field_set read/write/delete/add callback. +// MG_DASH_ADD: fields hold the values of a new element to append; *index is +// not set. Return true to accept and persist it, false to reject (e.g. a cap +// was reached) +enum mg_dash_op { MG_DASH_READ, MG_DASH_WRITE, MG_DASH_DELETE, MG_DASH_ADD }; + +struct mg_dash; + +// Authenticated (or guest) dashboard user +struct mg_dash_user { + struct mg_dash_user *next; // Next user in the list + char name[MG_DASH_MAX_USER_NAME]; // Username + char token[21]; // Session token, sent to the client as a cookie + int level; // Access level, see struct mg_dash::authenticate + uint64_t expire; // Session expiration time, see mg_millis() + struct mg_dash *dash; // Dashboard this user belongs to +}; + +// Named group of related dashboard fields, e.g. "settings" or "metrics" +struct mg_field_set { + const char *name; // Set name, used as a JSON key + struct mg_field *fields; // Zero-terminated array of fields + bool (*fn)(enum mg_dash_op op, + struct mg_dash_user *u); // Read/write/delete/add callback + int *index; // Non-NULL: array set. *index is set before fn(READ) call; + // fn sets *index = -1 to signal end of iteration. + // Size query: framework sets *index = -1 before fn(READ); fn + // sets *index = total size + bool (*get_dir)(const struct mg_dash_user *u, char *buf, + size_t len); // Non-NULL: file array + struct mg_field_set *next; // Next set in the list +}; + +// Custom URI handler, registered with MG_DASH_REGISTER_CUSTOM_HANDLER() +struct mg_dash_custom_handler { + struct mg_dash_custom_handler *next; // Next handler in the list + struct mg_str uri_pattern; // URI to match, see mg_match() + mg_event_handler_t handler; // Event handler function + void *handler_data; // Opaque data for the handler +}; + +// Dashboard instance: fields, users and handlers served over HTTP and WS +struct mg_dash { + struct mg_field_set *sets; // Field sets, see MG_DASH_ADD_FIELD_SET() + struct mg_dash_custom_handler *custom_handlers; // Custom URI handlers + // Validates pass, fills in user, returns access level (<= 0: failure). + // NULL: no login required, all clients get guest access + int (*authenticate)(char *user, size_t userlen, const char *pass); + int session_auto_expiration_seconds; // Session lifetime, 0: 1 hour default + struct mg_dash_user *guest; // Used when authenticate == NULL + struct mg_fs *upload_fs; // FS for uploads/listing, default &mg_fs_posix +}; + +#define MG_DASH_ADD_FIELD_SET(dash_, set_) \ + do { \ + (set_)->next = (dash_)->sets; \ + (dash_)->sets = (set_); \ + } while (0) + +#define MG_DASH_REGISTER_CUSTOM_HANDLER(dash_, uri_, fn_, data_) \ + do { \ + static struct mg_dash_custom_handler ch_; \ + ch_.next = (dash_)->custom_handlers; \ + ch_.uri_pattern = mg_str(uri_); \ + ch_.handler = (fn_); \ + ch_.handler_data = (data_); \ + (dash_)->custom_handlers = &ch_; \ + } while (0) + +// Dashboard event handler. Pass to mg_http_listen() as the fn argument +void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data); + +// Notify WebSocket clients that a field set changed: broadcasts the new +// array size if *set->index < 0, or a single element update otherwise +void mg_dash_send_change(struct mg_mgr *mgr, struct mg_field_set *); + +// Default reader for file-backed array sets (set->get_dir != NULL) +// Usage example - this is a complete implemenation of the file manager: +// ```c +// static struct file { +// int index; +// char name[64]; +// size_t size; +// uint64_t checksum; +// } s_file; +// static struct mg_field_set set_files; +// +// static bool get_dir(const struct mg_dash_user *u, char *buf, size_t len) { +// (void) u; +// mkdir("/tmp/dashboard", 0755); +// mg_snprintf(buf, len, "%s", "/tmp/dashboard"); +// return true; +// } +// +// static struct mg_field fields_files[] = { +// {"name", MG_VAL_STR, s_file.name, sizeof(s_file.name)}, +// {"size", MG_VAL_UINT64, &s_file.size, 0}, +// {"checksum", MG_VAL_UINT64, &s_file.checksum, sizeof(s_file.checksum)}, +// {NULL, MG_VAL_INT, NULL, 0}, +// }; +// +// static bool files_fn(enum mg_dash_op op, struct mg_dash_user *u) { +// if (op == MG_DASH_WRITE) return s_uploads_enabled && u->level >= 7; +// if (op != MG_DASH_READ) return false; +// if (!mg_dash_dir_read(&set_files, u)) return false; +// s_file.checksum = 0; // Mock to show how to set custom fields +// return true; +// } +// +// static struct mg_field_set set_files = {"files", fields_files, files_fn, &s_file.index, get_dir, NULL}; +// ``` +bool mg_dash_dir_read(struct mg_field_set *set, struct mg_dash_user *u); + +// Helper forward declarations for Mongoose CMSIS pack modules +extern struct mg_mgr g_mgr; +extern void mg_dash_init(struct mg_mgr *); +extern void mg_dash_poll(struct mg_mgr *); +extern void mg_mqtt_init(struct mg_mgr *); +extern void mg_mqtt_poll(struct mg_mgr *); +// Firmware OTA updates +// +// This is a low-level OTA API, not intended to be called directly by users. +// Users are provided with a higher-level API: +// - defining MG_OTA_URL enables HTTP pull-based updates +// - mg_http_start_ota() enables push-based updates +// +// However, it is possible to use the mg_ota_* API directly. Below is the +// example code of a function that is called repeatedly. It expects the +// last chink of size 0, which marks the end of the OTA process +// +// ```c +// static void ota(size_t total, size_t offset, void *buf, size_t len) { +// bool ok = false; +// if (ofs == 0 && (ok = mg_ota_begin(total)) == false) { +// mg_ota_end(); +// } else if (len > 0 && (ok = mg_ota_write(buf, len)) == false) { +// mg_ota_end(); +// } else if (len == 0) {} +// ok = mg_ota_end(); +// } +// return ok; +// } +// ``` + + + + + + +// Supported OTA targets +#define MG_OTA_NONE 0 // No OTA support +#define MG_OTA_STM32H5 1 // STM32 H5 +#define MG_OTA_STM32H7 2 // STM32 H7 +#define MG_OTA_STM32H7_DUAL_CORE 3 // STM32 H7 dual core +#define MG_OTA_STM32F 4 // STM32 F7/F4/F2 +#define MG_OTA_CH32V307 100 // WCH CH32V307 +#define MG_OTA_U2A 200 // Renesas U2A16, U2A8, U2A6 +#define MG_OTA_RT1020 300 // IMXRT1020 +#define MG_OTA_RT1050 301 // IMXRT1050 +#define MG_OTA_RT1060 302 // IMXRT1060 +#define MG_OTA_RT1064 303 // IMXRT1064 +#define MG_OTA_RT1170 304 // IMXRT1170 +#define MG_OTA_MCXN 310 // MCXN947 +#define MG_OTA_RW612 320 // FRDM-RW612 +#define MG_OTA_FLASH 900 // OTA via internal flash +#define MG_OTA_ESP32 910 // ESP32 OTA implementation +#define MG_OTA_PICOSDK 920 // RP2040/2350 using Pico-SDK hardware_flash +#define MG_OTA_CUSTOM 1000 // Custom implementation + +// OTA target. Settable in mongoose_config.h +#ifndef MG_OTA +#define MG_OTA MG_OTA_NONE +#else +#ifndef MG_IRAM +#if defined(__GNUC__) +// Places code block in .iram ELF section. A linker script can place it in RAM +#define MG_IRAM __attribute__((noinline, section(".iram"))) +#else +#define MG_IRAM +#endif // compiler +#endif // IRAM +#endif // OTA + +// Starts the OTA process. Called once at the beginning of a firmware update. +bool mg_ota_begin(size_t new_firmware_size); + +// Writes the next firmware chunk. Called repeatedly until all data is written. +bool mg_ota_write(const void *buf, size_t len); + +// Ends the OTA process and commits the written firmware. +bool mg_ota_end(void); + +// MG_OTA_ROLLBACK: swap firmware banks and reset. On flash-based OTA, the +// default calls swap_fn() (no-op on single-bank) then resets. Override in +// mongoose_config.h for platforms without mg_flash (ESP32, PicoSDK, etc.). +#ifndef MG_OTA_ROLLBACK +#define MG_OTA_ROLLBACK() \ + do { \ + mg_flash->swap_fn(); \ + NVIC_SystemReset(); \ + } while (0) +#endif + +// Arm a hardware watchdog. mg_ota_poll() feeds it while in MG_OTA_TESTING state; +// if the event loop hangs, it fires, resets the device, and MG_OTA_FAILED +// triggers MG_OTA_ROLLBACK() on next boot. Timeout is arch-defined. +#ifndef MG_OTA_ROLLBACK_TIMER_START +#define MG_OTA_ROLLBACK_TIMER_START() +#endif + +#ifndef MG_OTA_ROLLBACK_TIMER_FEED +#define MG_OTA_ROLLBACK_TIMER_FEED() +#endif + +// OTA state values for MG_OTA_STATE_GET / MG_OTA_STATE_SET. +// MG_OTA_CONFIRMED: firmware is committed, normal operation. +// MG_OTA_TESTING: new firmware booted for the first time; IWDG is armed. +// If the device reboots while in this state, it transitions +// to MG_OTA_FAILED and rolls back on the next boot. +// MG_OTA_FAILED: previous boot did not commit in time; rollback on next boot. +// MG_OTA_ROLLEDBACK: device rolled back to previous firmware. Sent to the OTA +// server as boot=3 on every poll until a new OTA succeeds, +// which overwrites this state with MG_OTA_TESTING. +enum { + MG_OTA_CONFIRMED = 0, + MG_OTA_TESTING = 1, + MG_OTA_FAILED = 2, + MG_OTA_ROLLEDBACK = 3 +}; + +// Persistent OTA state storage. Override in mongoose_config.h to persist state across resets. +#ifndef MG_OTA_STATE_GET +#define MG_OTA_STATE_GET() 0 +#endif +#ifndef MG_OTA_STATE_SET +#define MG_OTA_STATE_SET(val) (void) (val) +#endif + +// Evaluation window for new firmware in seconds. mg_ota_poll() feeds the +// rollback watchdog while in MG_OTA_TESTING state and within this window; +// when it expires, auto-commits if the OTA server confirmed the same version, +// otherwise stops feeding and the watchdog triggers a rollback. +#ifndef MG_OTA_ROLLBACK_TIMEOUT_SECONDS +#define MG_OTA_ROLLBACK_TIMEOUT_SECONDS 120 +#endif + +// Call once at boot, after clock and backup-domain init (e.g. after HAL_Init() +// + SystemClock_Config() on CubeMX projects, or after hal_clock_init() on +// Mongoose tutorials). The backup domain must be write-accessible so the state +// transition TESTING → FAILED persists across resets. +// Arms the rollback watchdog if new firmware is in TESTING state, rolls back +// immediately if FAILED. mg_ota_poll() feeds the watchdog and auto-commits +// when the server confirms the same version within the window. +// boot (TESTING) → set FAILED → arm IWDG → run firmware +// ├── server ok + window expires → set CONFIRMED → IWDG resets cleanly +// └── no server ok + window expires → stop feeding → IWDG fires → FAILED → rollback +#define MG_OTA_BOOT_CHECK() \ + do { \ + if (MG_OTA_STATE_GET() == MG_OTA_FAILED) { \ + MG_OTA_STATE_SET(MG_OTA_ROLLEDBACK); \ + MG_INFO(("Commit deadline expired, rolling back")); \ + MG_OTA_ROLLBACK(); \ + } else if (MG_OTA_STATE_GET() == MG_OTA_TESTING) { \ + MG_OTA_STATE_SET(MG_OTA_FAILED); \ + MG_INFO(("New firmware: commit within %u sec or rolls back", \ + (unsigned) MG_OTA_ROLLBACK_TIMEOUT_SECONDS)); \ + MG_OTA_ROLLBACK_TIMER_START(); \ + } \ + } while (0) + +// Pull-based OTA over HTTP. Fetches metadata_url, which must return a JSON +// object like: { "version": "1.2.3", "url": "FIRMWARE_URL", "size": 324645 } +// If the server version differs from MG_OTA_FIRMWARE_VERSION, downloads +// FIRMWARE_URL and performs the OTA update. fn is called on every outcome: +// NULL on successful flash, "Same version" when already up to date, or an +// error string on failure. Pass NULL to use the default handler which logs +// failures via MG_ERROR. +void mg_ota_url_check(struct mg_mgr *mgr, const char *metadata_url, + void (*fn)(const char *error_message)); + +// Firmware info URL for mg_ota_poll() which calls mg_ota_url_check(). +// Example: "http://mongoose.ws/ota/u/0/ota.json". See http://mongoose.ws/ota/ +// Settable in mongoose_config.h +#ifndef MG_OTA_URL +#define MG_OTA_URL NULL +#endif + +// OTA status callback function for mg_ota_poll() +#ifndef MG_OTA_STATUS_FN +#define MG_OTA_STATUS_FN NULL +#endif + +// Firmware version for mg_ota_poll(). Settable in mongoose_config.h +#ifndef MG_OTA_FIRMWARE_VERSION +#define MG_OTA_FIRMWARE_VERSION "1.0.0" +#endif + +// Maximum version string length +#ifndef MG_OTA_MAX_VERSION_LEN +#define MG_OTA_MAX_VERSION_LEN 64 +#endif + +// How often mg_ota_poll() checks for a firmware update, in seconds +#ifndef MG_OTA_PULL_INTERVAL_SECONDS +#define MG_OTA_PULL_INTERVAL_SECONDS 60 +#endif + +// Set to 1 to define a custom mg_ota_device_id(). Settable in mongoose_config.h +#ifndef MG_ENABLE_CUSTOM_DEVICE_ID +#define MG_ENABLE_CUSTOM_DEVICE_ID 0 +#endif + +// Populates buf with a unique device ID string. +// E.g. on STM32, uses the 96-bit MCU UID converted to a hex string. +void mg_ota_device_id(char *buf, size_t len); + +// Checks for a firmware update over HTTP. Called automatically by mg_mgr_poll() +// when MG_OTA_URL is set in mongoose_config.h. Do not call directly. +void mg_ota_poll(struct mg_mgr *); + + + + +#if (MG_OTA != MG_OTA_NONE && MG_OTA != MG_OTA_CUSTOM) || MG_ENABLE_LFS + +struct mg_flash { + void *start; // Address at which flash starts + size_t size; // Flash size + size_t secsz; // Sector size + size_t align; // Write alignment + bool (*write_fn)(void *, const void *, size_t); // Write function + bool (*swap_fn)(void); // Swap partitions +}; + +extern struct mg_flash *mg_flash; +bool mg_lfs_init(size_t size); + +bool mg_ota_flash_begin(size_t new_firmware_size, struct mg_flash *flash); +bool mg_ota_flash_write(const void *buf, size_t len, struct mg_flash *flash); +bool mg_ota_flash_end(struct mg_flash *flash); + +#endif + + + + + + +struct mg_wifi_data { + char *ssid, *pass; // STA mode, SSID to connect to + char *apssid, *appass; // AP mode, our SSID + uint32_t apip, apmask; // AP mode, our IP address and mask + uint8_t security; // STA mode, TBD + uint8_t apsecurity; // AP mode, TBD + uint8_t apchannel; // AP mode, channel to use + bool apmode; // start in AP mode; 'false' -> connect to 'ssid' != NULL +}; + +struct mg_wifi_scan_bss_data { + struct mg_str SSID; + char *BSSID; + int16_t RSSI; + uint8_t security; +#define MG_WIFI_SECURITY_OPEN 0 +#define MG_WIFI_SECURITY_WEP MG_BIT(0) +#define MG_WIFI_SECURITY_WPA MG_BIT(1) +#define MG_WIFI_SECURITY_WPA2 MG_BIT(2) +#define MG_WIFI_SECURITY_WPA3 MG_BIT(3) +#define MG_WIFI_SECURITY_WPA_ENTERPRISE MG_BIT(4) +#define MG_WIFI_SECURITY_WPA2_ENTERPRISE MG_BIT(5) +#define MG_WIFI_SECURITY_WPA3_ENTERPRISE MG_BIT(6) + uint8_t channel; + unsigned band : 2; +#define MG_WIFI_BAND_2G 0 +#define MG_WIFI_BAND_5G 1 + unsigned has_n : 1; + unsigned has_ac : 1; + unsigned has_ax : 1; +}; + +bool mg_wifi_scan(void); +bool mg_wifi_connect(struct mg_wifi_data *); +bool mg_wifi_disconnect(void); +bool mg_wifi_ap_start(struct mg_wifi_data *); +bool mg_wifi_ap_stop(void); + + + + + +#if MG_ENABLE_TCPIP + +// no config defaults to 0 => Ethernet +enum mg_l2type { MG_TCPIP_L2_ETH = 0, MG_TCPIP_L2_PPP, MG_TCPIP_L2_PPPoE, MG_TCPIP_L2_USER }; + +#if defined(__DCC__) +#pragma pack(1) +#else +#pragma pack(push, 1) +#endif + +struct mg_l2addr { + union { + uint8_t mac[6]; // ieee48 + uint64_t ieee64; // PPP ifcid + uint16_t ieee16; + } addr; +}; + +// L2 using L2 (PPPoE) must inherit the lower layer first, so they overlap +struct eth_data { + uint16_t vlan_id; +}; +struct pppoe_data { // (struct eth_data *) &pppoe_data = pppoedata.eth + struct eth_data eth; +}; + +union mg_l2data { + struct eth_data eth; + struct pppoe_data pppoe; +}; + +#if defined(__DCC__) +#pragma pack(0) +#else +#pragma pack(pop) +#endif + + +enum mg_l2proto { + MG_TCPIP_L2PROTO_IPV4 = 0, + MG_TCPIP_L2PROTO_IPV6, + MG_TCPIP_L2PROTO_ARP, + MG_TCPIP_L2PROTO_PPPoE_DISC, + MG_TCPIP_L2PROTO_PPPoE_SESS +}; +enum mg_l2addrtype { + MG_TCPIP_L2ADDR_BCAST, + MG_TCPIP_L2ADDR_MCAST, + MG_TCPIP_L2ADDR_MCAST6 +}; + +#endif + + + + + + + + +#if MG_ENABLE_TCPIP + +// Mongoose TCP/IP network interface +struct mg_tcpip_if; + +// Hardware driver interface. Implement all function pointers to plug in a +// new network device. Polling drivers set rx; interrupt/DMA drivers set +// rx=NULL and push frames via mg_tcpip_qwrite() from their ISR instead. +struct mg_tcpip_driver { + bool (*init)(struct mg_tcpip_if *); // One-time hardware init; return false on failure + size_t (*tx)(const void *, size_t, struct mg_tcpip_if *); // Transmit one raw frame; return bytes sent + size_t (*rx)(void *buf, size_t len, struct mg_tcpip_if *); // Read one raw frame; return frame size, 0 if none. NULL for interrupt-driven drivers + bool (*poll)(struct mg_tcpip_if *, bool); // Check link status; second arg is true once per second. Return true=up, false=down +}; + +// Event handler called by the TCP/IP stack for network-level events. +// ev is one of MG_TCPIP_EV_*; ev_data type is documented per event below. +typedef void (*mg_tcpip_event_handler_t)(struct mg_tcpip_if *ifp, int ev, + void *ev_data); + +// clang-format off +// Events delivered to mg_tcpip_if.fn (user) and mg_tcpip_if.pfn (driver). +// Each entry shows the ev_data type to cast inside the handler. +enum { + MG_TCPIP_EV_STATE_CHANGE, // IPv4 link state changed uint8_t *(&ifp->state) + MG_TCPIP_EV_DHCP_DNS, // DHCP assigned a DNS server uint32_t *ipaddr + MG_TCPIP_EV_DHCP_SNTP, // DHCP assigned an SNTP server uint32_t *ipaddr + MG_TCPIP_EV_ARP, // Raw ARP frame received struct mg_str * + MG_TCPIP_EV_TIMER_1S, // 1-second periodic tick NULL + MG_TCPIP_EV_WIFI_SCAN_RESULT, // Wi-Fi BSS scan entry struct mg_wifi_scan_bss_data * + MG_TCPIP_EV_WIFI_SCAN_END, // Wi-Fi scan finished NULL + MG_TCPIP_EV_WIFI_CONNECT_ERR, // Wi-Fi connection failed driver and chip specific + MG_TCPIP_EV_DRIVER, // Driver-defined event driver specific + MG_TCPIP_EV_STATE6_CHANGE, // IPv6 link state changed uint8_t *(&ifp->state6) + MG_TCPIP_EV_USER // Starting ID for user-defined events +}; +// clang-format on + +// Network interface. Zero-initialise, then set the fields marked "user-set" +// before calling mg_tcpip_init(). Fields in the "internal state" section +// below are managed by the stack; read them freely but do not modify them. +struct mg_tcpip_if { + // User-set configuration + uint8_t mac[sizeof(struct mg_l2addr)]; // MAC address; must be set to a valid address + uint32_t ip, mask, gw; // Static IPv4 address, netmask, gateway; 0 = use DHCP + struct mg_str tx; // TX frame buffer (managed by the stack) + bool enable_dhcp_client; // Enable DHCP client; auto-set if ip==0 + bool enable_dhcp_server; // Enable DHCP server + bool enable_get_gateway; // DHCP server offers itself as the default gateway + bool enable_req_dns; // DHCP client requests a DNS server address + bool enable_req_sntp; // DHCP client requests an SNTP server address + bool enable_fcs_check; // Verify and strip FCS from received frames + bool enable_mac_check; // Drop frames not addressed to this MAC + bool update_mac_hash_table; // Signal driver to refresh MAC multicast hash table + bool is_ip_changed; // Set by stack when IP changes; triggers connection restart + struct mg_tcpip_driver *driver; // Hardware driver; must be set before mg_tcpip_init() + void *driver_data; // Passed to all driver functions as ifp->driver_data + mg_tcpip_event_handler_t pfn; // Driver-level event handler; called before fn + mg_tcpip_event_handler_t fn; // User event handler for MG_TCPIP_EV_* events + struct mg_mgr *mgr; // Mongoose event manager; set by mg_tcpip_init() + struct mg_queue recv_queue; // Incoming frame queue; pre-size by setting recv_queue.size + char dhcp_name[MG_TCPIP_DHCPNAME_SIZE]; // Hostname sent in DHCP requests; defaults to "mip" + uint16_t mtu; // IP MTU (max payload size at the IP layer) + uint16_t framesize; // Maximum L2 frame size in bytes + uint16_t l2mtu; // L2 frame payload, default net MTU + +#if MG_ENABLE_IPV6 + uint64_t ip6ll[2], ip6[2]; // IPv6 link-local and global addresses + uint8_t prefix[8]; // Global prefix bytes + uint8_t prefix_len; // Global prefix length in bits + uint64_t gw6[2]; // IPv6 default gateway + bool enable_slaac; // Enable SLAAC (stateless address autoconfiguration) + bool enable_dhcp6_client; // Enable DHCPv6 client (not yet fully implemented) + bool is_ip6_changed; // Set by stack when IPv6 address changes +#endif + + // Internal state - read-only for users, managed by the stack + uint8_t gwmac[sizeof(struct mg_l2addr)]; // Gateway hardware address (resolved via ARP) + enum mg_l2type l2type; // Layer-2 type: Ethernet, PPP, etc. (see l2.h) + union mg_l2data l2data; // Layer-2 config and state (see l2.h) + char *dns4_url; // Active DNS server URL (set from DHCP or config) + uint64_t now; // Current time in ms (updated each mg_mgr_poll) + uint64_t timer_1000ms; // Timestamp of last 1-second tick + uint64_t lease_expire; // DHCP lease expiry time in ms + uint16_t eport; // Next ephemeral port to assign + volatile uint32_t ndrop; // Frames received but dropped (queue full) + volatile uint32_t nrecv; // Total frames received + volatile uint32_t nsent; // Total frames transmitted + volatile uint32_t nerr; // Driver-reported transmit errors + uint8_t state; // Current IPv4 link/IP state (MG_TCPIP_STATE_*) +#define MG_TCPIP_STATE_DOWN 0 // Physical link is down +#define MG_TCPIP_STATE_LINK_UP 1 // Driver reports physical link is up +#define MG_TCPIP_STATE_UP 2 // L2 is ready (e.g. PPP negotiated) +#define MG_TCPIP_STATE_REQ 3 // DHCP REQUESTING: waiting for DHCP offer +#define MG_TCPIP_STATE_IP 4 // IP address assigned; resolving gateway MAC +#define MG_TCPIP_STATE_READY 5 // Fully operational; gateway MAC resolved + bool gw_ready; // Gateway MAC address has been resolved via ARP + +#if MG_ENABLE_IPV6 + uint8_t gw6mac[sizeof(struct mg_l2addr)]; // IPv6 gateway hardware address + uint8_t state6; // Current IPv6 link/IP state (MG_TCPIP_STATE_*) + bool gw6_ready; // IPv6 gateway MAC address has been resolved +#endif +}; + +// Initialises the TCP/IP stack. Calls driver->init(), allocates the TX frame +// buffer (ifp->framesize bytes) and the RX queue. If ifp->ip == 0 and the +// L2 type is not PPP, DHCP client is enabled automatically. +// Must be called once after populating ifp->driver, ifp->mac, and any +// static IP/mask/gw values. +void mg_tcpip_init(struct mg_mgr *, struct mg_tcpip_if *); + +// Frees the TX buffer and RX queue allocated by mg_tcpip_init(). +void mg_tcpip_free(struct mg_tcpip_if *); + +// Pushes a received raw frame into ifp->recv_queue. Used by interrupt-driven +// or DMA-based drivers (those with rx=NULL) to hand frames to the stack from +// an ISR or DMA completion callback. Drops the frame and increments ndrop if +// the queue is full. +void mg_tcpip_qwrite(void *buf, size_t len, struct mg_tcpip_if *ifp); + +// Sends a broadcast ARP who-has request for ip. mac may be NULL for a plain +// broadcast, or a non-NULL hint to set the target MAC field in the request. +void mg_tcpip_arp_request(struct mg_tcpip_if *ifp, uint32_t ip, uint8_t *mac); + +// Built-in hardware drivers. Assign one to mg_tcpip_if.driver before calling +// mg_tcpip_init(). Each driver also requires a corresponding driver_data +// struct; see the matching driver header for details. +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_w5100; +extern struct mg_tcpip_driver mg_tcpip_driver_tm4c; +extern struct mg_tcpip_driver mg_tcpip_driver_tms570; +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; +extern struct mg_tcpip_driver mg_tcpip_driver_pico_w; +extern struct mg_tcpip_driver mg_tcpip_driver_rw612; +extern struct mg_tcpip_driver mg_tcpip_driver_cyw; +extern struct mg_tcpip_driver mg_tcpip_driver_nxp_wifi; +extern struct mg_tcpip_driver mg_tcpip_driver_st67w6; +extern struct mg_tcpip_driver mg_tcpip_driver_atcmd; + +// SPI bus abstraction for SPI-attached network chips (e.g. W5500). +// Populate and assign to the driver's driver_data field. +struct mg_tcpip_spi { + void *spi; // Opaque SPI bus handle passed to all callbacks + void (*begin)(void *); // Assert chip-select (start of transaction) + void (*end)(void *); // Deassert chip-select (end of transaction) + void (*txn)(void *, uint8_t *write, uint8_t *read, size_t); // Full-duplex transfer: write and read len bytes simultaneously +}; + +// Alignment attributes for DMA-capable Ethernet descriptor buffers. +// Use these on driver RX/TX descriptor arrays where the hardware requires +// specific alignment. +#ifndef MG_8BYTE_ALIGNED +#if defined(__GNUC__) +#define MG_8BYTE_ALIGNED __attribute__((aligned((8U)))) +#else +#define MG_8BYTE_ALIGNED +#endif // compiler +#endif // 8BYTE_ALIGNED + +#ifndef MG_16BYTE_ALIGNED +#if defined(__GNUC__) +#define MG_16BYTE_ALIGNED __attribute__((aligned((16U)))) +#else +#define MG_16BYTE_ALIGNED +#endif // compiler +#endif // 16BYTE_ALIGNED + +#ifndef MG_32BYTE_ALIGNED +#if defined(__GNUC__) +#define MG_32BYTE_ALIGNED __attribute__((aligned((32U)))) +#else +#define MG_32BYTE_ALIGNED +#endif // compiler +#endif // 32BYTE_ALIGNED + +#ifndef MG_64BYTE_ALIGNED +#if defined(__GNUC__) +#define MG_64BYTE_ALIGNED __attribute__((aligned((64U)))) +#else +#define MG_64BYTE_ALIGNED +#endif // compiler +#endif // 64BYTE_ALIGNED + +// Memory section attribute for Ethernet frame buffers. Define to +// __attribute__((section(".eth_ram"))) or similar if the Ethernet DMA +// requires buffers in a specific RAM region. +#ifndef MG_ETH_RAM +#define MG_ETH_RAM +#endif + +#endif + + + + + +#if MG_ENABLE_BSD_SOCKETS + +#ifndef MG_ENABLE_BSD_PROTOTYPES +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#else +#include +typedef unsigned int socklen_t; +typedef uint32_t in_addr_t; +struct in_addr { in_addr_t s_addr; }; +struct in6_addr { uint8_t s6_addr[16]; }; +struct sockaddr { uint16_t sa_family; char sa_data[14]; }; +struct sockaddr_in { + uint16_t sin_family; + uint16_t sin_port; + struct in_addr sin_addr; + char sin_zero[8]; +}; +struct sockaddr_in6 { + uint16_t sin6_family; + uint16_t sin6_port; + uint32_t sin6_flowinfo; + struct in6_addr sin6_addr; + uint32_t sin6_scope_id; +}; +struct sockaddr_storage { uint16_t ss_family; char __ss_pad[126]; }; +struct hostent { + char *h_name; + char **h_aliases; + int h_addrtype; + int h_length; + char **h_addr_list; +}; +#define h_addr h_addr_list[0] +struct addrinfo { + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + socklen_t ai_addrlen; + struct sockaddr *ai_addr; + char *ai_canonname; + struct addrinfo *ai_next; +}; +struct pollfd { int fd; short events; short revents; }; +#define AF_INET 2 +#define AF_INET6 10 +#define AF_UNSPEC 0 +#define PF_INET AF_INET +#define PF_INET6 AF_INET6 +#define PF_UNSPEC AF_UNSPEC +#define SOCK_STREAM 1 +#define SOCK_DGRAM 2 +#define IPPROTO_TCP 6 +#define IPPROTO_UDP 17 +#define INADDR_ANY 0 +#define INADDR_LOOPBACK 0x7f000001 +#define SOL_SOCKET 0xffff +#define SO_REUSEADDR 2 +#define SO_REUSEPORT 15 +#define SO_KEEPALIVE 9 +#define SO_ERROR 4 +#define SO_BROADCAST 6 +#define SO_RCVBUF 8 +#define SO_SNDBUF 7 +#define TCP_NODELAY 1 +#define MSG_DONTWAIT 0x40 +#define MSG_NOSIGNAL 0x4000 +#define MSG_PEEK 0x02 +#define F_GETFL 3 +#define F_SETFL 4 +#define O_NONBLOCK 0x0004 +#define SHUT_RD 0 +#define SHUT_WR 1 +#define SHUT_RDWR 2 +#define AI_PASSIVE 0x0001 +#define NI_MAXHOST 1025 +#define NI_MAXSERV 32 +#ifndef EAGAIN +#define EAGAIN 11 +#endif +#ifndef EWOULDBLOCK +#define EWOULDBLOCK EAGAIN +#endif +#ifndef EINPROGRESS +#define EINPROGRESS 36 +#endif +#ifndef ENOTCONN +#define ENOTCONN 107 +#endif +#ifndef ECONNREFUSED +#define ECONNREFUSED 111 +#endif +#ifndef ECONNRESET +#define ECONNRESET 104 +#endif +#ifndef EADDRINUSE +#define EADDRINUSE 98 +#endif +#ifndef ETIMEDOUT +#define ETIMEDOUT 110 +#endif +#define POLLIN 0x001 +#define POLLOUT 0x004 +#define POLLERR 0x008 +#define POLLHUP 0x010 +#define POLLNVAL 0x020 +uint16_t htons(uint16_t); +uint16_t ntohs(uint16_t); +uint32_t htonl(uint32_t); +uint32_t ntohl(uint32_t); +#endif // MG_ENABLE_BSD_PROTOTYPES + +#define closesocket(a) close(a) + +#ifndef MG_BSD_BACKLOG +#define MG_BSD_BACKLOG 5 +#endif + +#ifndef MG_BSD_CONNECT_TIMEOUT_MS +#define MG_BSD_CONNECT_TIMEOUT_MS 10000 +#endif + +// Mongoose-specific API +void mg_bsd_init(void); // must be called before socket() +void mg_bsd_poll(struct mg_mgr *); // process pending BSD commands + +// Transport backend: implement these OR define MG_ENABLE_FREERTOS for the built-in backend +void *mg_bsd_transport_new(int domain, int type, int proto); +void mg_bsd_transport_free(void *t); +int mg_bsd_transport_listen(void *t, const struct sockaddr_in *addr); +void *mg_bsd_transport_accept(void *t, struct sockaddr_in *peer, bool nonblock); +ssize_t mg_bsd_transport_recv(void *t, void *buf, size_t len, bool nonblock); +ssize_t mg_bsd_transport_send(void *t, const void *buf, size_t len, bool nonblock); +int mg_bsd_transport_connect(void *t, const struct sockaddr_in *addr, bool nonblock); +void mg_bsd_transport_close(void *t); + +// Standard BSD socket API +int socket(int, int, int); +int bind(int, const struct sockaddr *, socklen_t); +int listen(int, int); +int accept(int, struct sockaddr *, socklen_t *); +int connect(int, const struct sockaddr *, socklen_t); +ssize_t send(int, const void *, size_t, int); +ssize_t recv(int, void *, size_t, int); +ssize_t sendto(int, const void *, size_t, int, const struct sockaddr *, socklen_t); +ssize_t recvfrom(int, void *, size_t, int, struct sockaddr *, socklen_t *); +int close(int); +int shutdown(int, int); +int fcntl(int, int, int); +int setsockopt(int, int, int, const void *, socklen_t); +int getsockopt(int, int, int, void *, socklen_t *); +int getsockname(int, struct sockaddr *, socklen_t *); +int getpeername(int, struct sockaddr *, socklen_t *); +int select(int, fd_set *, fd_set *, fd_set *, struct timeval *); +int poll(struct pollfd *, unsigned int, int); +struct hostent *gethostbyname(const char *); +int getaddrinfo(const char *, const char *, const struct addrinfo *, struct addrinfo **); +void freeaddrinfo(struct addrinfo *); +int inet_pton(int, const char *, void *); +const char *inet_ntop(int, const void *, char *, socklen_t); +in_addr_t inet_addr(const char *); +char *inet_ntoa(struct in_addr); + +#endif // MG_ENABLE_BSD_SOCKETS + + +struct mg_tcpip_driver_atcmd_data { + void *usart; // Opaque UART/SPI descriptor + void (*reset)(void *); // Modem hardware reset + void (*tx)(void *, uint8_t); // USART transmit single byte + int (*rx)(void *); // USART receive single byte + const char **script; // List of AT commands and expected replies + int script_index; // Index of the current AT command in the list + uint64_t deadline; // AT command deadline in ms + bool no_byte_stuff; // Do not perform byte stuffing/unstuffing + bool link; // internal use: done with AT processing + bool unstuffing; // internal use: unstuffing state +}; + + +#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_CYW) && MG_ENABLE_DRIVER_CYW) || \ + (defined(MG_ENABLE_DRIVER_CYW_SDIO) && MG_ENABLE_DRIVER_CYW_SDIO)) + +struct mg_tcpip_driver_cyw_firmware { + const uint8_t *code_addr; + size_t code_len; + const uint8_t *nvram_addr; + size_t nvram_len; + const uint8_t *clm_addr; + size_t clm_len; +}; + +struct mg_tcpip_driver_cyw_data { + struct mg_wifi_data wifi; + void *bus; + struct mg_tcpip_driver_cyw_firmware *fw; + bool hs; // use chip "high-speed" mode; otherwise SPI CPOL0 CPHA0 (DS 4.2.3 Table 6) +}; + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_cyw_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + MG_SET_WIFI_CONFIG(&mif_, &driver_data_); \ + mif_.ip = MG_TCPIP_IP; \ + mif_.mask = MG_TCPIP_MASK; \ + mif_.gw = MG_TCPIP_GW; \ + mif_.driver = &mg_tcpip_driver_cyw; \ + mif_.driver_data = &driver_data_; \ + mif_.recv_queue.size = 8192; \ + mif_.mac[0] = 2; /* MAC read from OTP at driver init */ \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: cyw, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#endif + + +#if MG_ENABLE_TCPIP && \ + (defined(MG_ENABLE_DRIVER_IMXRT10) && MG_ENABLE_DRIVER_IMXRT10) || \ + (defined(MG_ENABLE_DRIVER_IMXRT11) && MG_ENABLE_DRIVER_IMXRT11) || \ + (defined(MG_ENABLE_DRIVER_MCXE) && MG_ENABLE_DRIVER_MCXE) + +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 + + +#if MG_ENABLE_TCPIP && \ + defined(MG_ENABLE_DRIVER_NXP_WIFI) && MG_ENABLE_DRIVER_NXP_WIFI + + +struct mg_tcpip_driver_nxp_wifi_data { + struct mg_wifi_data wifi; +}; + + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_nxp_wifi_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + MG_SET_WIFI_CONFIG(&mif_, &driver_data_); \ + mif_.ip = MG_TCPIP_IP; \ + mif_.mask = MG_TCPIP_MASK; \ + mif_.gw = MG_TCPIP_GW; \ + mif_.driver = &mg_tcpip_driver_nxp_wifi; \ + mif_.driver_data = &driver_data_; \ + mif_.recv_queue.size = 8192; \ + mif_.mac[0] = 2; /* MAC read from OTP at driver init */ \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: nxp wifi, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#endif + + + + +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 { + // Set if PHY LEDs are connected to ground + MG_PHY_LEDS_ACTIVE_HIGH = (1 << 0), + // Set when PHY clocks MAC. Otherwise, MAC clocks PHY + MG_PHY_CLOCKS_MAC = (1 << 1) +}; + +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 && MG_ARCH == MG_ARCH_PICOSDK && \ + defined(MG_ENABLE_DRIVER_PICO_W) && MG_ENABLE_DRIVER_PICO_W + +#include "cyw43.h" // keep this include +#include "pico/cyw43_arch.h" // keep this include +#include "pico/unique_id.h" // keep this include + +struct mg_tcpip_driver_pico_w_data { + struct mg_wifi_data wifi; +}; + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_pico_w_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + MG_SET_WIFI_CONFIG(&mif_, &driver_data_); \ + mif_.ip = MG_TCPIP_IP; \ + mif_.mask = MG_TCPIP_MASK; \ + mif_.gw = MG_TCPIP_GW; \ + mif_.driver = &mg_tcpip_driver_pico_w; \ + mif_.driver_data = &driver_data_; \ + mif_.recv_queue.size = 8192; \ + mif_.mac[0] = 2; /* MAC read from OTP at driver init */ \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: pico-w, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#endif + + +#if MG_ENABLE_TCPIP && \ + (defined(MG_ENABLE_DRIVER_RA6) && MG_ENABLE_DRIVER_RA6) || \ + (defined(MG_ENABLE_DRIVER_RA8) && MG_ENABLE_DRIVER_RA8) + +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 +}; + +#ifndef MG_DRIVER_CLK_FREQ +#define MG_DRIVER_CLK_FREQ 100000000UL +#endif + +#ifndef MG_DRIVER_IRQ_NO +#define MG_DRIVER_IRQ_NO 0 +#endif + +#ifndef MG_TCPIP_PHY_ADDR +#define MG_TCPIP_PHY_ADDR 0 +#endif + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_ra_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + driver_data_.clock = MG_DRIVER_CLK_FREQ; \ + driver_data_.irqno = MG_DRIVER_IRQ_NO; \ + 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_ra; \ + mif_.driver_data = &driver_data_; \ + MG_SET_MAC_ADDRESS(mif_.mac); \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: ra, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RW612) && MG_ENABLE_DRIVER_RW612 + +struct mg_tcpip_driver_rw612_data { + // 38.1.8 MII Speed Control Register (MSCR) + // MDC clock frequency must not exceed 2.5 MHz and is calculated as follows: + // MDC_freq = P_clock / ((mdc_cr + 1) * 2), where P_clock is the + // peripheral bus clock. + // IEEE802.3 clause 22 defines a minimum of 10 ns for the hold time on the + // MDIO output. Depending on the host bus frequency, the setting may need + // to be increased. + int mdc_cr; + int mdc_holdtime; // Valid values: [0-7] + uint8_t phy_addr; +}; + +#ifndef MG_TCPIP_PHY_ADDR +#define MG_TCPIP_PHY_ADDR 2 +#endif + +#ifndef MG_DRIVER_MDC_CR +#define MG_DRIVER_MDC_CR 51 +#endif + +#ifndef MG_DRIVER_MDC_HOLDTIME +#define MG_DRIVER_MDC_HOLDTIME 3 +#endif + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_rw612_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_rw612; \ + mif_.driver_data = &driver_data_; \ + MG_SET_MAC_ADDRESS(mif_.mac); \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: rw612, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_SAME54) && \ + MG_ENABLE_DRIVER_SAME54 + +#ifndef MG_DRIVER_MDC_CR +#define MG_DRIVER_MDC_CR 5 +#endif + +#ifndef MG_DRIVER_PHY_ADDR +#define MG_DRIVER_PHY_ADDR 0 +#endif + +struct mg_tcpip_driver_same54_data { + int mdc_cr; + uint8_t phy_addr; +}; + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_same54_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \ + driver_data_.phy_addr = MG_DRIVER_PHY_ADDR; \ + mif_.ip = MG_TCPIP_IP; \ + mif_.mask = MG_TCPIP_MASK; \ + mif_.gw = MG_TCPIP_GW; \ + mif_.driver = &mg_tcpip_driver_same54; \ + mif_.driver_data = &driver_data_; \ + MG_SET_MAC_ADDRESS(mif_.mac); \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: same54, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#endif + + +#if MG_ENABLE_TCPIP && \ + (defined(MG_ENABLE_DRIVER_CYW_SDIO) && MG_ENABLE_DRIVER_CYW_SDIO) + +// Specific chip/card driver --> SDIO driver --> HAL --> SDIO hw controller + +// API with HAL for hardware controller +// - Provide a function to init the controller (external) +// - Provide these functions: +struct mg_tcpip_sdio { + void *sdio; // Opaque SDIO bus descriptor + void (*cfg)(void *, uint8_t); // select operating parameters + // SDIO transaction: send cmd with a possible 1-byte read or write + bool (*txn)(void *, uint8_t cmd, uint32_t arg, uint32_t *r); + // SDIO extended transaction: write or read len bytes, using blksz blocks + bool (*xfr)(void *, bool write, uint32_t arg, uint16_t blksz, uint32_t *, + uint32_t len, uint32_t *r); +}; + +// API with driver (e.g.: cyw.c) +// Once the hardware controller has been initialized: +// - Init card: selects the card, sets F0 block size, sets bus width and speed +bool mg_sdio_init(struct mg_tcpip_sdio *sdio); +// - Enable other possible functions (F1 to F7) +bool mg_sdio_enable_f(struct mg_tcpip_sdio *sdio, unsigned int f); +// - Wait for them to be ready +bool mg_sdio_waitready_f(struct mg_tcpip_sdio *sdio, unsigned int f); +// - Set their transfer block length +bool mg_sdio_set_blksz(struct mg_tcpip_sdio *sdio, unsigned int f, + uint16_t blksz); +// - Transfer data to/from a function (abstracts from transaction type) +// - Requesting a read transfer > blocksize means block transfer will be used. +// - Drivers must have room to accomodate a whole block transfer, see sdio.c +// - Transfers of > 1 byte --> (uint32_t *) data. 1-byte --> (uint8_t *) data +bool mg_sdio_transfer(struct mg_tcpip_sdio *sdio, bool write, unsigned int f, + uint32_t addr, void *data, uint32_t len); + +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_ST67W6) && \ + MG_ENABLE_DRIVER_ST67W6 + +struct mg_tcpip_driver_st67w6_data { + struct mg_wifi_data wifi; + void *spi; + bool (*is_ready)(void); // return state of module RDY pin + struct mg_queue send_queue; // decouple tx calls from module polls +}; + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_st67w6_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + MG_SET_WIFI_CONFIG(&mif_, &driver_data_); \ + mif_.ip = MG_TCPIP_IP; \ + mif_.mask = MG_TCPIP_MASK; \ + mif_.gw = MG_TCPIP_GW; \ + mif_.driver = &mg_tcpip_driver_st67w6; \ + mif_.driver_data = &driver_data_; \ + mif_.recv_queue.size = 8192; \ + mif_.mac[0] = 2; /* MAC read from OTP at driver init */ \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: st67w6, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#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 + // ------------------------------------- + // -1 <-- tell driver to guess the value + // 60-100 MHz HCLK/42 0 + // 100-150 MHz HCLK/62 1 + // 20-35 MHz HCLK/16 2 + // 35-60 MHz HCLK/26 3 + // 150-216 MHz HCLK/102 4 <-- value for Nucleo-F* on max speed + // 216-310 MHz HCLK/124 5 + // 110, 111 Reserved + int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5 + + 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 + +#if MG_ARCH == MG_ARCH_CUBE && !defined(MG_ENABLE_ETH_IRQ) +#define MG_ENABLE_ETH_IRQ() NVIC_EnableIRQ(ETH_IRQn) +#elif !defined(MG_ENABLE_ETH_IRQ) +#define MG_ENABLE_ETH_IRQ() +#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_ENABLE_ETH_IRQ(); \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: stm32f, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#endif + + +#if MG_ENABLE_TCPIP +#if !defined(MG_ENABLE_DRIVER_STM32H) +#define MG_ENABLE_DRIVER_STM32H 0 +#endif +#if !defined(MG_ENABLE_DRIVER_MCXN) +#define MG_ENABLE_DRIVER_MCXN 0 +#endif +#if !defined(MG_ENABLE_DRIVER_STM32N) +#define MG_ENABLE_DRIVER_STM32N 0 +#endif +#if MG_ENABLE_DRIVER_STM32H || MG_ENABLE_DRIVER_MCXN || MG_ENABLE_DRIVER_STM32N + +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 + // 300-500 MHz HCLK/204 6 + // 500-800 MHz HCLK/324 7 + 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_CONF +#define MG_TCPIP_PHY_CONF MG_PHY_CLOCKS_MAC +#endif + +#ifndef MG_TCPIP_PHY_ADDR +#define MG_TCPIP_PHY_ADDR 0 +#endif + +#ifndef MG_DRIVER_MDC_CR +#define MG_DRIVER_MDC_CR 4 +#endif + +#if MG_ENABLE_DRIVER_STM32H && MG_ARCH == MG_ARCH_CUBE && !defined(MG_ENABLE_ETH_IRQ) +#define MG_ENABLE_ETH_IRQ() NVIC_EnableIRQ(ETH_IRQn) +#elif MG_ENABLE_DRIVER_STM32N && MG_ARCH == MG_ARCH_CUBE && !defined(MG_ENABLE_ETH_IRQ) +#define MG_ENABLE_ETH_IRQ() NVIC_EnableIRQ(ETH1_IRQn) +#elif !defined(MG_ENABLE_ETH_IRQ) +#define MG_ENABLE_ETH_IRQ() +#endif + +#if MG_ENABLE_IPV6 +#define MG_IPV6_INIT(mif) \ + do { \ + memcpy(mif.ip6ll, (uint8_t[16]) MG_TCPIP_IPV6_LINKLOCAL, 16); \ + memcpy(mif.ip6, (uint8_t[16]) MG_TCPIP_GLOBAL, 16); \ + memcpy(mif.gw6, (uint8_t[16]) MG_TCPIP_GW6, 16); \ + mif.prefix_len = MG_TCPIP_PREFIX_LEN; \ + } while(0) +#else +#define MG_IPV6_INIT(mif) +#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; \ + driver_data_.phy_conf = MG_TCPIP_PHY_CONF; \ + 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_IPV6_INIT(mif_); \ + mg_tcpip_init(mgr, &mif_); \ + MG_ENABLE_ETH_IRQ(); \ + MG_INFO(("Driver: stm32h, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#endif +#endif + + +#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 + // ------------------------------------- + // -1 <-- tell driver to guess the value + // 60-100 MHz SYSCLK/42 0 + // 100-150 MHz SYSCLK/62 1 <-- value for EK-TM4C129* on max speed + // 20-35 MHz SYSCLK/16 2 + // 35-60 MHz SYSCLK/26 3 + // 0x4-0xF Reserved + int mdc_cr; // Valid values: -1, 0, 1, 2, 3 +}; + +#ifndef MG_DRIVER_MDC_CR +#define MG_DRIVER_MDC_CR 1 +#endif + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_tm4c_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \ + mif_.ip = MG_TCPIP_IP; \ + mif_.mask = MG_TCPIP_MASK; \ + mif_.gw = MG_TCPIP_GW; \ + mif_.driver = &mg_tcpip_driver_tm4c; \ + mif_.driver_data = &driver_data_; \ + MG_SET_MAC_ADDRESS(mif_.mac); \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: tm4c, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_TMS570) && MG_ENABLE_DRIVER_TMS570 +struct mg_tcpip_driver_tms570_data { + int mdc_cr; + int phy_addr; +}; + +#ifndef MG_TCPIP_PHY_ADDR +#define MG_TCPIP_PHY_ADDR 0 +#endif + +#ifndef MG_DRIVER_MDC_CR +#define MG_DRIVER_MDC_CR 1 +#endif + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_tms570_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_tms570; \ + mif_.driver_data = &driver_data_; \ + MG_SET_MAC_ADDRESS(mif_.mac); \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: tms570, 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 + + +#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 + + +#ifdef __cplusplus +} +#endif +#endif // MONGOOSE_H diff --git a/run.sh b/run.sh index 455d587..292ad6c 100644 --- a/run.sh +++ b/run.sh @@ -7,8 +7,11 @@ case "$1" in shell) make clean && make && ./shell.exe ;; + web) + make clean && make web && ./moss-web.exe + ;; *) - echo "Usage: $0 {test|shell}" + echo "Usage: $0 {test|shell|web}" exit 1 ;; esac \ No newline at end of file diff --git a/src/gui/web/web_main.c b/src/gui/web/web_main.c new file mode 100644 index 0000000..6253880 --- /dev/null +++ b/src/gui/web/web_main.c @@ -0,0 +1,58 @@ +#include "../lib/mongoose/mongoose.h" + +#include +#include +#include + +static volatile sig_atomic_t s_running = 1; + +static void signal_handler(int signo) { + (void)signo; + s_running = 0; +} + +static const char *s_http_port = "http://localhost:8080"; + +static void ev_handler(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_HTTP_MSG) { + (void)ev_data; + mg_http_reply(c, 200, + "Content-Type: text/html; charset=utf-8\r\n", + "MOSS" + "" + "" + "

MOSS Shell

" + "

Web interface running on %s

" + "

Terminal coming in next phase.

" + "", + s_http_port); + } +} + +int main(void) { + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + + struct mg_mgr mgr; + mg_mgr_init(&mgr); + + struct mg_connection *c = mg_http_listen(&mgr, s_http_port, ev_handler, NULL); + + if (c == NULL) { + fprintf(stderr, "Failed to listen on %s\n", s_http_port); + mg_mgr_free(&mgr); + return EXIT_FAILURE; + } + + printf("MOSS Web Shell listening on %s\n", s_http_port); + + while (s_running) + mg_mgr_poll(&mgr, 1000); + + mg_mgr_free(&mgr); + printf("\nMOSS Web Shell stopped.\n"); + + return EXIT_SUCCESS; +} From 6c606033ac7dca390a5c5d50659b3324424e3672 Mon Sep 17 00:00:00 2001 From: Mahim Date: Wed, 1 Jul 2026 11:39:13 -0400 Subject: [PATCH 2/4] PTY & WebSocket Server --- .gitignore | 5 + .vscode/c_cpp_properties.json | 38 ++++--- Makefile | 21 +++- lib/mongoose/errtest.c | 7 ++ lib/mongoose/socktest.c | 17 ++++ lib/mongoose/socktest2.c | 18 ++++ lib/mongoose/socktest3.c | 9 ++ run.sh | 2 - scripts/embed.pl | 30 ++++++ src/gui/web/index.html | 11 ++ src/gui/web/main.js | 34 +++++++ src/gui/web/styles.css | 12 +++ src/gui/web/web_main.c | 58 ----------- src/gui/web_main.c | 186 ++++++++++++++++++++++++++++++++++ 14 files changed, 370 insertions(+), 78 deletions(-) create mode 100644 lib/mongoose/errtest.c create mode 100644 lib/mongoose/socktest.c create mode 100644 lib/mongoose/socktest2.c create mode 100644 lib/mongoose/socktest3.c create mode 100644 scripts/embed.pl create mode 100644 src/gui/web/index.html create mode 100644 src/gui/web/main.js create mode 100644 src/gui/web/styles.css delete mode 100644 src/gui/web/web_main.c create mode 100644 src/gui/web_main.c diff --git a/.gitignore b/.gitignore index 617a84c..04233e0 100644 --- a/.gitignore +++ b/.gitignore @@ -24,5 +24,10 @@ Thumbs.db core *.core +# Generated web assets (auto-created by make) +src/gui/web/*.html.h +src/gui/web/*.css.h +src/gui/web/*.js.h + *.md *.txt \ No newline at end of file diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 2682031..f696c8e 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -1,16 +1,24 @@ { - "configurations": [ - { - "name": "windows-gcc-x64", - "compilerPath": "C:/msys64/mingw64/bin/gcc.exe", - "intelliSenseMode": "windows-gcc-x64", - "cStandard": "${default}", - "cppStandard": "${default}", - "includePath": [ - "${workspaceFolder}/**" - ], - "defines": [] - } - ], - "version": 4 -} \ No newline at end of file + "configurations": [ + { + "name": "Cygwin", + "compilerPath": "C:/cygwin64/bin/gcc.exe", + "cStandard": "c17", + "intelliSenseMode": "linux-gcc-x64", + "includePath": [ + "${workspaceFolder}/**", + "${workspaceFolder}/src/gui", + "${workspaceFolder}/src/gui/web", + "${workspaceFolder}/include", + "${workspaceFolder}/lib/mongoose" + ], + "defines": [ + "__CYGWIN__" + ], + "env": { + "PATH": "C:/cygwin64/bin;${env:PATH}" + } + } + ], + "version": 4 +} diff --git a/Makefile b/Makefile index 271114f..a0fc3ac 100644 --- a/Makefile +++ b/Makefile @@ -9,8 +9,13 @@ SRC = $(wildcard src/*.c) \ SRC := $(filter-out src/modules/builtin.c, $(SRC)) OUT = shell -WEB_SRC = src/gui/web/web_main.c +WEB_SRC = src/gui/web_main.c WEB_OUT = moss-web +WEB_HTML = src/gui/web/index.html +WEB_CSS = src/gui/web/styles.css +WEB_JS = src/gui/web/main.js + +WEB_HDRS = $(WEB_HTML:.html=.html.h) $(WEB_CSS:.css=.css.h) $(WEB_JS:.js=.js.h) TEST_SRC = $(wildcard tests/*.c) TEST_BINS = $(TEST_SRC:.c=) @@ -23,8 +28,17 @@ $(OUT): $(SRC) web: $(WEB_OUT) -$(WEB_OUT): $(WEB_SRC) - $(CC) $(CFLAGS) $(WEB_SRC) lib/mongoose/mongoose.c -o $(WEB_OUT) -lws2_32 +$(WEB_OUT): $(WEB_SRC) $(WEB_HDRS) + $(CC) $(CFLAGS) $(WEB_SRC) lib/mongoose/mongoose.c -o $(WEB_OUT) + +%.html.h: %.html + perl scripts/embed.pl $< $(subst .,_,$(notdir $(basename $<)))_html + +%.css.h: %.css + perl scripts/embed.pl $< $(subst .,_,$(notdir $(basename $<)))_css + +%.js.h: %.js + perl scripts/embed.pl $< $(subst .,_,$(notdir $(basename $<)))_js test: $(TEST_BINS) @echo "Running all tests..." @@ -42,6 +56,7 @@ clean: rm -f $(OUT) rm -f $(TEST_BINS) rm -f $(WEB_OUT) + rm -f $(WEB_HDRS) scan: cppcheck --enable=all --force -Iinclude src/ diff --git a/lib/mongoose/errtest.c b/lib/mongoose/errtest.c new file mode 100644 index 0000000..b8ba7bc --- /dev/null +++ b/lib/mongoose/errtest.c @@ -0,0 +1,7 @@ +#include +#include +#include +int main() { + printf("errno 88: %s\n", strerror(88)); + return 0; +} diff --git a/lib/mongoose/socktest.c b/lib/mongoose/socktest.c new file mode 100644 index 0000000..8a53555 --- /dev/null +++ b/lib/mongoose/socktest.c @@ -0,0 +1,17 @@ +#include +#include +#include +#include +int main() { + int fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) { perror("socket"); return 1; } + struct sockaddr_in a = {0}; + a.sin_family = AF_INET; + a.sin_port = htons(8082); + a.sin_addr.s_addr = INADDR_ANY; + if (bind(fd, (void*)&a, sizeof(a)) < 0) { perror("bind"); close(fd); return 1; } + if (listen(fd, 5) < 0) { perror("listen"); close(fd); return 1; } + printf("OK fd=%d\n", fd); + close(fd); + return 0; +} diff --git a/lib/mongoose/socktest2.c b/lib/mongoose/socktest2.c new file mode 100644 index 0000000..1691201 --- /dev/null +++ b/lib/mongoose/socktest2.c @@ -0,0 +1,18 @@ +#include "mongoose.h" +#include +#include +#include +#include +int main() { + struct mg_mgr mgr; + mg_mgr_init(&mgr); + printf("mgr init done\n"); + struct mg_connection *c = mg_http_listen(&mgr, "http://0.0.0.0:8083", NULL, NULL); + if (c) { + printf("HTTP listen OK\n"); + } else { + printf("HTTP listen FAILED\n"); + } + mg_mgr_free(&mgr); + return 0; +} diff --git a/lib/mongoose/socktest3.c b/lib/mongoose/socktest3.c new file mode 100644 index 0000000..a20b676 --- /dev/null +++ b/lib/mongoose/socktest3.c @@ -0,0 +1,9 @@ +#include "mongoose.h" +#include +#include +#include +int main() { + int fd = socket(AF_INET, SOCK_STREAM, 0); + printf("socket() returned: %d, errno: %d (%s)\n", fd, errno, strerror(errno)); + return 0; +} diff --git a/run.sh b/run.sh index 292ad6c..bba714f 100644 --- a/run.sh +++ b/run.sh @@ -1,5 +1,3 @@ -#!/bin/bash - case "$1" in test) make clean && make test diff --git a/scripts/embed.pl b/scripts/embed.pl new file mode 100644 index 0000000..32010c4 --- /dev/null +++ b/scripts/embed.pl @@ -0,0 +1,30 @@ +#!/usr/bin/env perl +use strict; +use warnings; + +die "Usage: $0 \n" unless @ARGV == 2; +my ($input, $varname) = @ARGV; + +open(my $fh, "<:raw", $input) or die "open $input: $!"; +my $data; +{ local $/; $data = <$fh>; } +close($fh); +my $len = length($data); + +my $output = "$input.h"; +open(my $out, ">", $output) or die "write $output: $!"; +print $out "/* Auto-generated, do not edit */\n"; +print $out "static const char ${varname}[$len] = {"; +for my $i (0..$len-1) { + if ($i % 12 == 0) { + print $out "\n " if $i > 0; + } else { + print $out " " if $i > 0; + } + printf $out "0x%02x", ord(substr($data, $i, 1)); + print $out "," if $i < $len - 1; +} +print $out "\n};\n"; +print $out "static const unsigned int ${varname}_len = $len;\n"; +close($out); +print "Generated $output ($len bytes)\n"; diff --git a/src/gui/web/index.html b/src/gui/web/index.html new file mode 100644 index 0000000..62e9a70 --- /dev/null +++ b/src/gui/web/index.html @@ -0,0 +1,11 @@ + + + + +MOSS Web Shell + + + + + + diff --git a/src/gui/web/main.js b/src/gui/web/main.js new file mode 100644 index 0000000..6d83a87 --- /dev/null +++ b/src/gui/web/main.js @@ -0,0 +1,34 @@ +const term = new Terminal({ + cursorBlink:true, + fontSize:14, + theme: { + background:'#1e1e2e', + foreground:'#cdd6f4', + cursor:'#f5e0dc', + selectionBackground:'#585b70' + } +}); + +const fitAddon = new FitAddon.FitAddon(); + +term.loadAddon(fitAddon); +term.open(document.getElementById('term')); + +const ws = new WebSocket('ws://'+location.host+'/ws'); + +const sendSize = () => { + fitAddon.fit(); + ws.send(JSON.stringify({ + cols:term.cols, + rows:term.rows + })); +} + +ws.onopen = () =>{ + sendSize(); + term.focus() +}; + +ws.onmessage = e => term.write(e.data); +term.onData(d => ws.send(d)); +window.addEventListener('resize',sendSize); \ No newline at end of file diff --git a/src/gui/web/styles.css b/src/gui/web/styles.css new file mode 100644 index 0000000..d4f5621 --- /dev/null +++ b/src/gui/web/styles.css @@ -0,0 +1,12 @@ +body{ + margin:0; + padding:0; + background:#1e1e2e; + height:100vh; + overflow:hidden +} + +#term{ + height:100vh; + padding:4px +} \ No newline at end of file diff --git a/src/gui/web/web_main.c b/src/gui/web/web_main.c deleted file mode 100644 index 6253880..0000000 --- a/src/gui/web/web_main.c +++ /dev/null @@ -1,58 +0,0 @@ -#include "../lib/mongoose/mongoose.h" - -#include -#include -#include - -static volatile sig_atomic_t s_running = 1; - -static void signal_handler(int signo) { - (void)signo; - s_running = 0; -} - -static const char *s_http_port = "http://localhost:8080"; - -static void ev_handler(struct mg_connection *c, int ev, void *ev_data) { - if (ev == MG_EV_HTTP_MSG) { - (void)ev_data; - mg_http_reply(c, 200, - "Content-Type: text/html; charset=utf-8\r\n", - "MOSS" - "" - "" - "

MOSS Shell

" - "

Web interface running on %s

" - "

Terminal coming in next phase.

" - "", - s_http_port); - } -} - -int main(void) { - signal(SIGINT, signal_handler); - signal(SIGTERM, signal_handler); - - struct mg_mgr mgr; - mg_mgr_init(&mgr); - - struct mg_connection *c = mg_http_listen(&mgr, s_http_port, ev_handler, NULL); - - if (c == NULL) { - fprintf(stderr, "Failed to listen on %s\n", s_http_port); - mg_mgr_free(&mgr); - return EXIT_FAILURE; - } - - printf("MOSS Web Shell listening on %s\n", s_http_port); - - while (s_running) - mg_mgr_poll(&mgr, 1000); - - mg_mgr_free(&mgr); - printf("\nMOSS Web Shell stopped.\n"); - - return EXIT_SUCCESS; -} diff --git a/src/gui/web_main.c b/src/gui/web_main.c new file mode 100644 index 0000000..4067f27 --- /dev/null +++ b/src/gui/web_main.c @@ -0,0 +1,186 @@ +#include "mongoose.h" +#include "web/index.html.h" +#include "web/styles.css.h" +#include "web/main.js.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __linux__ + #include +#elif defined(__APPLE__) + #include +#else + #include +#endif + +static volatile sig_atomic_t s_running = 1; +static int s_master_fd = -1; +static pid_t s_shell_pid = -1; +static struct mg_connection *s_ws_conn = NULL; + +enum { + HTTP_OK = 200, + HTTP_NOT_FOUND = 404, + HTTP_SERVER_ERROR = 500, +}; + +static void signal_handler(int signo) { + (void)signo; + s_running = 0; +} + +static void pty_poll_cb(void *arg) { + (void)arg; + char buf[4096]; + ssize_t n = read(s_master_fd, buf, sizeof(buf)); + + if (n > 0 && s_ws_conn) + mg_ws_send(s_ws_conn, buf, (size_t)n, WEBSOCKET_OP_BINARY); + + if (n == 0) + s_running = 0; +} + +static int pty_fork_and_exec(void) { + int master_fd; + pid_t pid = forkpty(&master_fd, NULL, NULL, NULL); + + if (pid == -1) { + perror("forkpty"); + return -1; + } + + if (pid == 0) { + setenv("TERM", "xterm-256color", 1); + char *args[] = {"./shell", NULL}; + execvp("./shell", args); + perror("execvp"); + _exit(127); + } + + int flags = fcntl(master_fd, F_GETFL, 0); + fcntl(master_fd, F_SETFL, flags | O_NONBLOCK); + s_master_fd = master_fd; + s_shell_pid = pid; + + return master_fd; +} + +static void ev_handler(struct mg_connection *c, int ev, void *ev_data) { + switch (ev){ + case MG_EV_HTTP_MSG: { + struct mg_http_message *hm = (struct mg_http_message *) ev_data; + + if (mg_match(hm->uri, mg_str("/ws"), NULL)) + mg_ws_upgrade(c, hm, NULL); + else if (mg_match(hm->uri, mg_str("/styles.css"), NULL)) + mg_http_reply(c, HTTP_OK, "Content-Type: text/css; charset=utf-8\r\n", "%.*s", (int)styles_css_len, styles_css); + else if (mg_match(hm->uri, mg_str("/main.js"), NULL)) + mg_http_reply(c, HTTP_OK, "Content-Type: application/javascript; charset=utf-8\r\n", "%.*s", (int)main_js_len, main_js); + else + mg_http_reply(c, HTTP_OK, "Content-Type: text/html; charset=utf-8\r\n", "%.*s", (int)index_html_len, index_html); + + break; + } + + case MG_EV_WS_OPEN: + s_ws_conn = c; + printf("WebSocket connected\n"); + break; + + case MG_EV_WS_MSG: { + struct mg_ws_message *wm = (struct mg_ws_message *) ev_data; + + if (s_master_fd == -1) + return; + + double cols, rows; + + if (wm->data.len > 0 && wm->data.buf[0] == '{' && mg_json_get_num(wm->data, "$.cols", &cols) && mg_json_get_num(wm->data, "$.rows", &rows)) { + struct winsize ws; + ws.ws_row = (unsigned short)rows; + ws.ws_col = (unsigned short)cols; + ws.ws_xpixel = ws.ws_ypixel = 0; + ioctl(s_master_fd, TIOCSWINSZ, &ws); + } + else { + write(s_master_fd, wm->data.buf, wm->data.len); + } + + break; + } + + case MG_EV_CLOSE: + if (c == s_ws_conn) { + s_ws_conn = NULL; + printf("WebSocket disconnected\n"); + } + + break; + } +} + +static void cleanup(void) { + if (s_shell_pid > 0) { + kill(s_shell_pid, SIGHUP); + waitpid(s_shell_pid, NULL, 0); + } + + if (s_master_fd != -1) + close(s_master_fd); +} + +int main(void) { + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + atexit(cleanup); + + if (pty_fork_and_exec() == -1) { + fprintf(stderr, "Failed to start shell in PTY\n"); + return EXIT_FAILURE; + } + + struct mg_mgr mgr; + mg_mgr_init(&mgr); + + const char *port = getenv("MOSS_PORT"); + + if (!port) + port = "8080"; + + char url[64]; + + snprintf(url, sizeof(url), "http://0.0.0.0:%s", port); + + struct mg_connection *c = mg_http_listen(&mgr, url, ev_handler, NULL); + + if (c == NULL) { + fprintf(stderr, "Failed to listen on %s\n", url); + mg_mgr_free(&mgr); + return EXIT_FAILURE; + } + + struct mg_timer pty_timer; + mg_timer_init(&mgr.timers, &pty_timer, 50, MG_TIMER_REPEAT, pty_poll_cb, NULL); + + printf("MOSS Web Shell: %s\n", url); + printf("Shell PID: %d\n", s_shell_pid); + + while (s_running) + mg_mgr_poll(&mgr, 50); + + mg_mgr_free(&mgr); + printf("MOSS Web Shell stopped.\n"); + + return EXIT_SUCCESS; +} From 88d7d59f4bce19c4a23e6b9f4c8bfbdf40983a04 Mon Sep 17 00:00:00 2001 From: Mahim Date: Wed, 1 Jul 2026 12:07:02 -0400 Subject: [PATCH 3/4] working gui --- Makefile | 2 +- run.sh | 2 +- src/gui/web/index.html | 9 +++++++-- src/gui/web_main.c | 25 ++++++++++++++++++++----- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index a0fc3ac..9c044e4 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ SRC = $(wildcard src/*.c) \ $(wildcard src/modules/builtins/*.c) \ $(wildcard src/modules/autocomplete/*.c) \ -SRC := $(filter-out src/modules/builtin.c, $(SRC)) +SRC := $(filter-out src/modules/builtin.c src/gui/web_main.c, $(SRC)) OUT = shell WEB_SRC = src/gui/web_main.c diff --git a/run.sh b/run.sh index bba714f..e3d0665 100644 --- a/run.sh +++ b/run.sh @@ -6,7 +6,7 @@ case "$1" in make clean && make && ./shell.exe ;; web) - make clean && make web && ./moss-web.exe + make clean && make && make web && ./moss-web.exe ;; *) echo "Usage: $0 {test|shell|web}" diff --git a/src/gui/web/index.html b/src/gui/web/index.html index 62e9a70..60c9929 100644 --- a/src/gui/web/index.html +++ b/src/gui/web/index.html @@ -1,11 +1,16 @@ - + + MOSS Web Shell + + +
- + + diff --git a/src/gui/web_main.c b/src/gui/web_main.c index 4067f27..55f17cc 100644 --- a/src/gui/web_main.c +++ b/src/gui/web_main.c @@ -27,6 +27,8 @@ static volatile sig_atomic_t s_running = 1; static int s_master_fd = -1; static pid_t s_shell_pid = -1; static struct mg_connection *s_ws_conn = NULL; +static char s_pty_buf[65536]; +static size_t s_pty_buf_len = 0; enum { HTTP_OK = 200, @@ -44,8 +46,15 @@ static void pty_poll_cb(void *arg) { char buf[4096]; ssize_t n = read(s_master_fd, buf, sizeof(buf)); - if (n > 0 && s_ws_conn) - mg_ws_send(s_ws_conn, buf, (size_t)n, WEBSOCKET_OP_BINARY); + if (n > 0) { + if (s_ws_conn) { + mg_ws_send(s_ws_conn, buf, (size_t)n, WEBSOCKET_OP_TEXT); + } + else if (s_pty_buf_len + (size_t)n < sizeof(s_pty_buf)) { + memcpy(s_pty_buf + s_pty_buf_len, buf, (size_t)n); + s_pty_buf_len += (size_t)n; + } + } if (n == 0) s_running = 0; @@ -62,8 +71,8 @@ static int pty_fork_and_exec(void) { if (pid == 0) { setenv("TERM", "xterm-256color", 1); - char *args[] = {"./shell", NULL}; - execvp("./shell", args); + char *args[] = {"/bin/bash", NULL}; + execvp("/bin/bash", args); perror("execvp"); _exit(127); } @@ -95,6 +104,12 @@ static void ev_handler(struct mg_connection *c, int ev, void *ev_data) { case MG_EV_WS_OPEN: s_ws_conn = c; + + if (s_pty_buf_len > 0) { + mg_ws_send(c, s_pty_buf, s_pty_buf_len, WEBSOCKET_OP_BINARY); + s_pty_buf_len = 0; + } + printf("WebSocket connected\n"); break; @@ -118,7 +133,7 @@ static void ev_handler(struct mg_connection *c, int ev, void *ev_data) { } break; - } + } case MG_EV_CLOSE: if (c == s_ws_conn) { From 0405d2926bd92e24e36e4be3fb4d88c821c8cd3d Mon Sep 17 00:00:00 2001 From: Mahim Date: Wed, 1 Jul 2026 12:29:39 -0400 Subject: [PATCH 4/4] enhanced web ui --- file | 1 + src/gui/web/index.html | 20 ++-- src/gui/web/main.js | 59 +++++++---- src/gui/web/styles.css | 221 ++++++++++++++++++++++++++++++++++++++--- src/gui/web_main.c | 6 +- 5 files changed, 270 insertions(+), 37 deletions(-) create mode 100644 file diff --git a/file b/file new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/file @@ -0,0 +1 @@ +test diff --git a/src/gui/web/index.html b/src/gui/web/index.html index 60c9929..0c6692f 100644 --- a/src/gui/web/index.html +++ b/src/gui/web/index.html @@ -4,13 +4,21 @@ MOSS Web Shell - - + + -
- - - +
+ +
+
+
+
+ + + diff --git a/src/gui/web/main.js b/src/gui/web/main.js index 6d83a87..c414283 100644 --- a/src/gui/web/main.js +++ b/src/gui/web/main.js @@ -1,34 +1,59 @@ const term = new Terminal({ - cursorBlink:true, - fontSize:14, + cursorBlink: true, + fontSize: 15, + fontFamily: "'Cascadia Code', 'JetBrains Mono', 'Fira Code', 'SF Mono', 'Consolas', monospace", + fontWeight: 400, + letterSpacing: 0, + lineHeight: 1.3, theme: { - background:'#1e1e2e', - foreground:'#cdd6f4', - cursor:'#f5e0dc', - selectionBackground:'#585b70' - } + background: '#0a0a1a', + foreground: '#b3ffe0', + cursor: '#00f0ff', + cursorAccent: '#0a0a1a', + selectionBackground: 'rgba(0,240,255,0.25)', + selectionForeground: '#ffffff', + black: '#0d0d22', + red: '#ff2e97', + green: '#00ff66', + yellow: '#ffdd00', + blue: '#00c8ff', + magenta: '#c74ded', + cyan: '#00f0ff', + white: '#ddeeff', + brightBlack: '#445577', + brightRed: '#ff5eaa', + brightGreen: '#44ff88', + brightYellow: '#ffee44', + brightBlue: '#44ddff', + brightMagenta: '#e088ff', + brightCyan: '#66ffff', + brightWhite: '#ffffff' + }, + allowProposedApi: true }); const fitAddon = new FitAddon.FitAddon(); - term.loadAddon(fitAddon); term.open(document.getElementById('term')); -const ws = new WebSocket('ws://'+location.host+'/ws'); +const ws = new WebSocket('ws://' + location.host + '/ws'); const sendSize = () => { fitAddon.fit(); - ws.send(JSON.stringify({ - cols:term.cols, - rows:term.rows - })); -} + ws.send(JSON.stringify({ cols: term.cols, rows: term.rows })); + document.getElementById('dims').textContent = term.cols + 'x' + term.rows; +}; -ws.onopen = () =>{ +ws.onopen = () => { + document.getElementById('status').setAttribute('data-state', 'live'); sendSize(); - term.focus() + term.focus(); }; ws.onmessage = e => term.write(e.data); +ws.onclose = () => { + document.getElementById('status').setAttribute('data-state', 'dead'); +}; + term.onData(d => ws.send(d)); -window.addEventListener('resize',sendSize); \ No newline at end of file +window.addEventListener('resize', sendSize); diff --git a/src/gui/web/styles.css b/src/gui/web/styles.css index d4f5621..7898ef8 100644 --- a/src/gui/web/styles.css +++ b/src/gui/web/styles.css @@ -1,12 +1,209 @@ -body{ - margin:0; - padding:0; - background:#1e1e2e; - height:100vh; - overflow:hidden -} - -#term{ - height:100vh; - padding:4px -} \ No newline at end of file +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;500;700&family=Space+Grotesk:wght@500;700&display=swap'); + +:root { + --bg-deep: #06060e; + --bg-frame: #0a0a1a; + --cyan: #00f0ff; + --cyan-dim: rgba(0,240,255,0.3); + --magenta: #ff2e97; + --magenta-dim: rgba(255,46,151,0.25); + --green: #00ff66; + --yellow: #ffdd00; + --white: #e0e0f0; + --text-dim: #8899bb; +} + +* { box-sizing: border-box; } + +body { + margin: 0; + padding: 0; + background: var(--bg-deep); + height: 100vh; + overflow: hidden; + font-family: 'Inter', 'Segoe UI', system-ui, sans-serif; + display: flex; + align-items: center; + justify-content: center; +} + +/* ==== Scanlines overlay ==== */ +#scanlines { + position: fixed; + inset: 0; + pointer-events: none; + z-index: 100; + background: repeating-linear-gradient( + 0deg, + transparent, + transparent 3px, + rgba(0,0,0,0.04) 3px, + rgba(0,0,0,0.04) 4px + ); +} + +/* ==== Outer glow pulse ==== */ +#glow { + position: fixed; + inset: 0; + pointer-events: none; + z-index: 99; + box-shadow: inset 0 0 120px rgba(0,240,255,0.06), + inset 0 0 30px rgba(255,46,151,0.04); + animation: glowPulse 4s ease-in-out infinite; +} + +@keyframes glowPulse { + 0%, 100% { opacity: 0.6; } + 50% { opacity: 1; } +} + +/* ==== Frame ==== */ +#frame { + position: relative; + width: 98vw; + height: 96vh; + border: 1px solid var(--cyan-dim); + border-radius: 6px; + background: var(--bg-frame); + box-shadow: + 0 0 40px rgba(0,240,255,0.08), + 0 0 80px rgba(0,240,255,0.03), + 0 0 10px rgba(0,240,255,0.15) inset, + 0 0 30px rgba(255,46,151,0.05) inset; + display: flex; + flex-direction: column; + overflow: hidden; +} + +/* Corner decorations */ +#frame::before, #frame::after { + content: ''; + position: absolute; + z-index: 2; + pointer-events: none; +} +#frame::before { + top: -1px; left: -1px; + width: 24px; height: 24px; + border-top: 2px solid var(--cyan); + border-left: 2px solid var(--cyan); + border-radius: 6px 0 0 0; +} +/* top-right corner */ +#frame::after { + top: -1px; right: -1px; + width: 24px; height: 24px; + border-top: 2px solid var(--magenta); + border-right: 2px solid var(--magenta); + border-radius: 0 6px 0 0; +} + +/* ==== Header ==== */ +#header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 16px; + border-bottom: 1px solid rgba(0,240,255,0.15); + background: linear-gradient(180deg, rgba(0,240,255,0.06), transparent); + z-index: 1; + min-height: 36px; +} + +#logo { + font-family: 'Space Grotesk', 'Inter', sans-serif; + font-weight: 700; + font-size: 16px; + letter-spacing: 6px; + color: var(--cyan); + text-shadow: + 0 0 10px var(--cyan-dim), + 0 0 30px rgba(0,240,255,0.4); + animation: logoFlicker 5s linear infinite; +} + +@keyframes logoFlicker { + 0%, 19%, 21%, 23%, 25%, 54%, 56%, 100% { opacity: 1; } + 20%, 24%, 55% { opacity: 0.4; } +} + +#status { + font-size: 10px; + letter-spacing: 3px; + text-transform: uppercase; + color: var(--text-dim); + display: flex; + align-items: center; + gap: 8px; +} + +#status::before { + content: ''; + display: inline-block; + width: 6px; height: 6px; + border-radius: 50%; + background: var(--green); + box-shadow: 0 0 6px var(--green), 0 0 12px var(--green); + animation: statusBlink 2s ease-in-out infinite; +} + +@keyframes statusBlink { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.3; } +} + +/* ==== Terminal ==== */ +#term { + flex: 1; + padding: 4px 8px; +} + +/* ==== Footer ==== */ +#footer { + display: flex; + justify-content: space-between; + align-items: center; + padding: 4px 16px; + border-top: 1px solid rgba(0,240,255,0.1); + background: linear-gradient(0deg, rgba(0,240,255,0.04), transparent); + font-size: 10px; + letter-spacing: 3px; + color: var(--text-dim); + z-index: 1; + min-height: 24px; +} + +#dims { letter-spacing: 1px; } + +/* ==== xterm overrides ==== */ +.xterm-viewport::-webkit-scrollbar { width: 4px; } +.xterm-viewport::-webkit-scrollbar-track { background: rgba(0,240,255,0.03); } +.xterm-viewport::-webkit-scrollbar-thumb { + background: var(--cyan-dim); + border-radius: 2px; + box-shadow: 0 0 4px var(--cyan-dim); +} +.xterm-viewport::-webkit-scrollbar-thumb:hover { + background: var(--cyan); +} + +/* Bottom corner decorations mirroring the top */ +#frame .corner-bl::after, +#frame .corner-br::after { + content: ''; + position: absolute; + z-index: 2; + pointer-events: none; + bottom: -1px; left: -1px; + width: 24px; height: 24px; + border-bottom: 2px solid var(--magenta); + border-left: 2px solid var(--magenta); +} + +#frame .corner-br { + bottom: -1px; right: -1px; + width: 24px; height: 24px; + border-bottom: 2px solid var(--cyan); + border-right: 2px solid var(--cyan); +} diff --git a/src/gui/web_main.c b/src/gui/web_main.c index 55f17cc..175b8e0 100644 --- a/src/gui/web_main.c +++ b/src/gui/web_main.c @@ -71,14 +71,16 @@ static int pty_fork_and_exec(void) { if (pid == 0) { setenv("TERM", "xterm-256color", 1); - char *args[] = {"/bin/bash", NULL}; - execvp("/bin/bash", args); + char *args[] = {"./shell", NULL}; + execvp("./shell", args); perror("execvp"); _exit(127); } int flags = fcntl(master_fd, F_GETFL, 0); fcntl(master_fd, F_SETFL, flags | O_NONBLOCK); + struct winsize ws = { 50, 140, 0, 0 }; + ioctl(master_fd, TIOCSWINSZ, &ws); s_master_fd = master_fd; s_shell_pid = pid;