Compare commits

..
12 Commits
Author SHA1 Message Date
Sergio R. Caprile cb63083bc3 Add full datagram support for BSD 2026-10-30 17:08:39 -03:00
Sergio R. Caprile b766c65615 avoid 802.15.4 using a standard BSD function name, enable BSD test after mg_error() fix 2026-10-23 14:00:24 -03:00
cpq 8296b88fb6 Update readme 2026-10-23 15:21:10 +01:00
Sergio R. Caprile a5efbf0592 misc cleanups and standardizations 2026-10-22 16:53:56 -03:00
Sergio R. Caprile 37c98dd893 mg_delay_ms() survives mg_millis() restarting on some 32-bit platforms 2026-10-22 13:49:21 -03:00
cpq 770a4ff27e Sync description 2026-10-22 17:21:05 +01:00
Sergio R. Caprile b6560306ba avoid reconnect timer being disrupted by possible SNTP updates 2026-10-22 12:12:27 -03:00
Sergio R. Caprile 5b39c93478 Merge pull request #3746 from cesanta/ota
Make mg_http_ota_cb public, add /ota endpoints to MCU examples
2026-10-22 11:30:35 -03:00
cpq 086008b716 Nit 2026-10-22 14:55:19 +01:00
cpq 35eb50a1e6 Nit 2026-10-22 14:46:19 +01:00
cpq 431e35ae8c Update README 2026-10-22 14:44:50 +01:00
cpq 25e2a99d8e Make mg_http_ota_cb public, add /ota endpoints to MCU examples 2026-10-22 14:30:10 +01:00
78 changed files with 2043 additions and 784 deletions

No files matched your search

-1
View File
@@ -637,7 +637,6 @@ jobs:
example:
- path: micropython/esp32
- path: esp32/uart-bridge
- path: esp32/minimal
name: ${{ matrix.example.path }}
env:
GO: 0
-1
View File
@@ -186,7 +186,6 @@ jobs:
matrix:
example:
- path: esp32/uart-bridge
- path: esp32/minimal
name: ${{ matrix.example.path }}
env:
GO: 0
+8 -4
View File
@@ -1,8 +1,12 @@
# Mongoose - Embedded Network Library
# Mongoose: a full-stack embedded networking library
Mongoose is a full-stack embedded networking library for C/C++: a TCP/IP stack
with drivers, TLS 1.3, an embedded web server (HTTP, WebSocket), MQTT and
firmware OTA updates in two files, `mongoose.c` and `mongoose.h`. Mongoose runs on
STM32, NXP and other microcontrollers, on bare metal or with an RTOS, with no
external dependencies. Use it for cloud connectivity, device web dashboards,
secure firmware updates and remote control.
Mongoose is an open source, two-file C networking library and embedded web server for
microcontrollers that combines TCP/IP stack, HTTP, WebSocket, MQTT, TLS 1.3
stack, built-in firmware OTA updates, and device-dashboard infrastructure.
License: GPLv2 or commercial.
Website: https://mongoose.ws/
GitHub repo: https://github.com/cesanta/mongoose
+51 -27
View File
@@ -1,4 +1,4 @@
# Mongoose - Embedded Web Server / Embedded Network Library
# Mongoose: a full-stack embedded networking library
[![License: GPLv2/Commercial](https://img.shields.io/badge/License-GPLv2%20or%20Commercial-green.svg)](https://opensource.org/licenses/gpl-2.0.php)
[![Build Status]( https://github.com/cesanta/mongoose/actions/workflows/quicktest.yml/badge.svg)](https://github.com/cesanta/mongoose/actions)
@@ -7,11 +7,17 @@
<img src="https://mongoose.ws/images/logo.svg" width="48" height="48"
align="left" style="float:left;" />
Two files. Drop `mongoose.c` and `mongoose.h` into any C/C++ project and get a
full network stack — HTTP, WebSocket, MQTT, TLS, firmware OTA
updates, device dashboard — on any microcontroller or desktop OS.
Mongoose is a full-stack embedded networking library for C/C++: a TCP/IP stack
with drivers, TLS 1.3, an embedded web server (HTTP, WebSocket), MQTT and
firmware OTA updates in two files, `mongoose.c` and `mongoose.h`. Mongoose runs on
STM32, NXP and other microcontrollers, on bare metal or with an RTOS, with no
external dependencies. Use it for cloud connectivity, device web dashboards,
secure firmware updates and remote control.
On the market since 2004, used by vast number of open source and commercial
Use it instead of lwIP + mbedTLS + httpd + an MQTT client + OTA glue code, or
run it on top of lwIP or Zephyr.
On the market since 2004, used by a vast number of open source and commercial
products - it even runs on the International Space Station! Features include:
- Cross-platform:
@@ -24,7 +30,7 @@ products - it even runs on the International Space Station! Features include:
- Source code is both ISO C and ISO C++ compliant
- Very easy to integrate
- Own TCP/IP stack for bare metal / RTOS, or run on top of any existing one (lwIP, Zephyr, ...)
- Built-in TLS 1.3 ECC stack. Also can use external TLS libraries - mbedTLS, OpenSSL, or other
- Built-in TLS 1.3 stack. Also can use external TLS libraries - mbedTLS, OpenSSL, or other
- Does not depend on any other software to implement networking
- Built-in firmware updates for STM32 H5, STM32 H7, NXP IMXRT, RP2040/2350, ESP32 and more
- Commercial support with proactive security updates; [CRA-compliant](https://mongoose.ws/security/cra-compliance/)
@@ -35,8 +41,32 @@ See https://mongoose.ws/ for complete documentation, videos, case studies, etc.
## Supported platforms
Mongoose can work on top of any TCP/IP stack that supports BSD sockets API.
Platforms supported by the 3rd party TCP/IP stacks:
Mongoose has its own built-in TCP/IP stack with drivers, so it needs no
additional software to implement networking. The built-in stack runs on bare
metal and under any RTOS. Platforms supported by the Mongoose built-in TCP/IP
stack:
| MCU | Connectivity | Notes |
| :-- | :----------- | :---- |
| **STM32** | Built-in Ethernet | Ethernet-capable STM32F, STM32H5, STM32H7, STM32N6, STM32C5 variants |
| **NXP i.MX RT** | Built-in Ethernet | RT102x, RT104x, RT105x, RT106x, RT117x, RT118x (NETC) |
| **NXP MCX** | Built-in Ethernet | MCXN94x, MCXE247 |
| **NXP RW612** | Eth, WiFi, 802.15.4 | Separate Ethernet and radio drivers |
| **Microchip ATSAME54** | Built-in Ethernet | GMAC driver |
| **Renesas RA** | Built-in Ethernet | RA6M, RA8M |
| **Infineon XMC** | Built-in Ethernet | XMC4, XMC7 |
| **Texas Instruments** | Built-in Ethernet | TM4C, TMS570 |
| **RP2040, RP2350, STM32, others** | CYW43xx Wi-Fi chips | SPI or SDIO; Pico W, Pico 2 W, Pico RM2, Arduino Portenta H7 |
| **STM32, others** | ST67W6 Wi-Fi modules | SPI; for example, STM32U5 with the ST67W611M1 module |
| **MCUs with an NXP Wi-Fi SDK port** | NXP Wi-Fi | Requires the SDK's Wi-Fi port implementing the driver's network hooks |
| **NXP MCXW71** | Built-in 802.15.4 radio | IPv4 over IEEE 802.15.4 |
| **NXP RW612** | Built-in 802.15.4 radio | IPv4 over IEEE 802.15.4; requires NXP radio firmware |
| **Any MCU with SPI** | W5500 or W5100 Ethernet chips | External Wiznet MAC+PHY |
| **MCUs connected to a cellular modem** | Cellular | AT commands and PPP; for example, SIM800 |
| **MCUs with CMSIS Ethernet drivers** | Ethernet via CMSIS-Driver | Requires CMSIS Ethernet MAC and PHY drivers |
Mongoose can also run on top of any TCP/IP stack that supports the BSD
sockets API. Platforms supported via 3rd party TCP/IP stacks:
| TCP/IP stack | Notes |
| :-------------- | :------- |
@@ -45,23 +75,6 @@ Platforms supported by the 3rd party TCP/IP stacks:
| **Other** | Any other TCP/IP stack that supports BSD socket API, for example Amazon FreeRTOS-TCP |
| **Linux, Mac, Windows** | Workstations, server, single board computers, embedded Linux devices running on MPUs or FPGAs |
Optionally, Mongoose provides its own built-in TCP/IP stack, eliminating the
need for additional software to implement networking functionality. The
built-in stack supports operation in both bare-metal and RTOS environments.
Platforms supported by the Mongoose built-in TCP/IP stack:
| Hardware | Notes |
| :------------- | :------- |
| **STM32** | All STM32 MCUs with built-in Ethernet: STM32Fxx, STM32H5xx, STM32H7xx |
| **NXP** | All NXP MCUs with built-in Ethernet: IMXRT102x, IMXRT104x, IMXRT105x, IMXRT106x, IMXRT117x, RW612, MCXN94x |
| **Microchip** | ATSAME54 MCUs with built-int Ethernet |
| **Renesas** | RA5M, RA6M, RA8M MCUs with built-in Ethernet |
| **Infineon** | XMC4, XMC7 MCUs with built-in Ethernet |
| **Texas Instruments** | TM4C, TMS570 MCUs with built-in Ethernet |
| **Cypress WiFi** | Any MCU with CY43xx WiFi chips, like RP2040 Pico-W, RP2350 Pico2-W, Arduino Portenta |
| **Wiznet Ethernet** | Any MCU that use Wiznet W5500 or Wiznet 5100 MAC+PHY chips |
| **Cellular** | NRF9160, SIM800 |
## Usage Examples
Below are quick snippets that should give an idea how simple the API is and
@@ -158,6 +171,17 @@ int main() {
}
```
## Licensing
Mongoose is dual-licensed:
- GPLv2: free for open source projects. Many popular open source projects use
Mongoose, for example
[SWUpdate](https://github.com/sbabic/swupdate/tree/master/mongoose), the
embedded Linux update framework
- Commercial: for closed-source products. Includes support directly from the
engineers who wrote the whole stack, and early security fixes.
See [licensing](https://mongoose.ws/licensing/)
## Commercial use
- Mongoose is used by hundreds of businesses, from Fortune500 giants like
Siemens, Schneider Electric, Broadcom, Bosch, Google, Samsung, Qualcomm, Caterpillar to the small businesses
@@ -222,12 +246,12 @@ Technical guides and deep dives into embedded web servers, WebUI integration and
- [lwIP vs Mongoose - TCP/IP Stack Integration Benchmark](https://mongoose.ws/articles/lwip-vs-mongoose-tcpip-stack-integration/)
- [Embedded TCP/IP Stacks Comparison](https://mongoose.ws/embedded-tcpip-stack-comparison/)
- [STM32H5 File System on Internal Flash: LittleFS + Mongoose](https://mongoose.ws/articles/stm32h5-file-system-on-internal-flash/)
- [Firmware OTA and remote control on NXP FRDM-RW612](https://mongoose.ws/articles/mdash/)
- [Firmware OTA and remote control on NXP FRDM-RW612](https://mongoose.ws/articles/rw612-mdash/)
- [25000 WebSocket messages per second on STM32 Nucleo-H723ZG](https://mongoose.ws/articles/stm32-websocket-benchmark/)
- [How to connect STM32 to AWS IoT Core](https://mongoose.ws/articles/stm32-aws-iot/)
- [How to connect STM32 to Azure IoT Hub](https://mongoose.ws/articles/stm32-azure-iot-hub/)
- [How to connect STM32 to EMQX](https://mongoose.ws/articles/stm32-emqx/)
- [STM32 Device Management deep dive: Secure OTA, Remote Control, health monitor](https://mongoose.ws/articles/stm32-mdash-deep-dive/")
- [STM32 Device Management deep dive: Secure OTA, Remote Control, health monitor](https://mongoose.ws/articles/stm32-mdash-deep-dive/)
- [STM32 TCP/IP footprint: how much flash and RAM does Mongoose take?](https://mongoose.ws/articles/tcp-ip-footprint/)
+321 -249
View File
@@ -167,6 +167,7 @@ fail:
#endif
#include <stdarg.h>
#if MG_ENABLE_BSD_SOCKETS && MG_ENABLE_FREERTOS
@@ -277,6 +278,57 @@ static struct mg_connection *bsd_new_conn(struct mg_bsd_sock *s) {
return c;
}
// Mirror net_builtin.c::trim_len() logic
static bool bsd_udp_too_big(struct mg_connection *c, size_t len) {
struct mg_tcpip_if *ifp = c->mgr->ifp;
size_t hlen = c->rem.is_ip6 ? 48 : 32; // IP plus UDP headers
size_t cap = ifp->mtu - hlen;
size_t room = ifp->tx.len - (ifp->framesize - ifp->l2mtu) - hlen;
return len > cap || len > room;
}
static bool bsd_udp_send(struct mg_connection *c, uint32_t ip, uint16_t port,
const void *buf, size_t len) {
struct mg_bsd_udp_frame *h;
size_t off = c->send.len, total = sizeof(*h) + len;
if (mg_iobuf_add(&c->send, off, NULL, total) != total) return false;
h = (struct mg_bsd_udp_frame *) (c->send.buf + off);
h->ip = ip;
h->port = port;
h->len = (uint16_t) len;
memcpy(h + 1, buf, len);
return true;
}
static size_t bsd_recv(struct mg_bsd_sock *s, void *buf, size_t len,
int flags, struct sockaddr *src, socklen_t *addrlen) {
struct mg_connection *c = s->c;
size_t n;
if (!s->is_udp) {
n = c->recv.len < len ? c->recv.len : len;
memcpy(buf, c->recv.buf, n);
if (!(flags & MSG_PEEK)) mg_iobuf_del(&c->recv, 0, n);
} else {
struct mg_bsd_udp_frame *h = (struct mg_bsd_udp_frame *) c->recv.buf;
assert(c->recv.len >= sizeof(*h));
assert(h->len <= c->recv.len - sizeof(*h));
n = h->len < len ? h->len : len;
memcpy(buf, h + 1, n);
if (src != NULL && addrlen != NULL) {
struct sockaddr_in *sa = (struct sockaddr_in *) src;
sa->sin_family = AF_INET;
sa->sin_port = h->port;
sa->sin_addr.s_addr = h->ip;
*addrlen = sizeof(*sa);
}
if (!(flags & MSG_PEEK)) mg_iobuf_del(&c->recv, 0, sizeof(*h) + h->len);
}
return n;
}
#if MG_ENABLE_FREERTOS
// Complete operations blocked on c before Mongoose destroys it.
static void bsd_finish_waiters(struct mg_bsd_sock *s, bool error) {
@@ -457,9 +509,9 @@ static int bsd_ready(struct mg_bsd_sock *s) {
return 0;
}
if (s->c->recv.len > 0 || s->c->is_draining) ready |= BSD_READ;
if (!s->is_udp && !s->c->is_connecting && !s->c->is_closing &&
if ((s->is_udp || !s->c->is_connecting) && !s->c->is_closing &&
!s->c->is_draining)
ready |= BSD_WRITE; // Established TCP socket
ready |= BSD_WRITE; // UDP socket or established TCP socket
return ready;
}
@@ -473,7 +525,7 @@ static void bsd_close(struct mg_bsd_sock *s) {
if (t->orphan && t->listener == s) {
if (t->c != NULL) {
t->c->fn_data = NULL;
mg_close_conn(t->c);
t->c->is_draining = 1;
t->c = NULL;
}
memset(t, 0, sizeof(*t)); // Release orphan
@@ -481,7 +533,7 @@ static void bsd_close(struct mg_bsd_sock *s) {
}
if (s->c != NULL) {
s->c->fn_data = NULL;
mg_close_conn(s->c);
s->c->is_draining = 1;
s->c = NULL;
}
memset(s, 0, sizeof(*s)); // Release socket
@@ -660,8 +712,8 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
cmd->result = -1, cmd->err = ECONNREFUSED;
return true;
}
if (!c->is_connecting) {
cmd->result = 0;
if (s->is_udp || !c->is_connecting) {
cmd->result = 0; // UDP may still resolve ARP; TCP waits for SYN/SYN-ACK
return true;
}
if (s->nonblock) {
@@ -685,11 +737,8 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
return true;
}
if (c->recv.len > 0) {
size_t n = cmd->op_recv.len;
if (c->recv.len < n) n = c->recv.len;
memcpy(cmd->op_recv.buf, c->recv.buf, n);
if (!(cmd->op_recv.flags & MSG_PEEK)) mg_iobuf_del(&c->recv, 0, n);
cmd->result = (int) n;
cmd->result = (int) bsd_recv(s, cmd->op_recv.buf, cmd->op_recv.len,
cmd->op_recv.flags, NULL, NULL);
return true;
}
if (s->err != 0) {
@@ -710,10 +759,17 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
case BSD_OP_SEND:
if ((s = bsd_get(cmd->op_send.fd)) == NULL) {
cmd->result = -1, cmd->err = EBADF;
} else if ((c = s->c) == NULL || s->err != 0 || c->is_closing ||
c->is_draining) {
} else if (s->err != 0 || s->closed ||
((c = s->c) != NULL && (c->is_closing || c->is_draining))) {
cmd->result = -1, cmd->err = EPIPE;
} else if (mg_send(c, cmd->op_send.buf, cmd->op_send.len)) {
} else if (c == NULL || (s->is_udp && !c->is_client)) {
cmd->result = -1, cmd->err = s->is_udp ? EDESTADDRREQ : EPIPE;
} else if (s->is_udp && bsd_udp_too_big(c, cmd->op_send.len)) {
cmd->result = -1, cmd->err = EMSGSIZE;
} else if ((s->is_udp ? bsd_udp_send(c, c->rem.addr.ip4, c->rem.port,
cmd->op_send.buf, cmd->op_send.len)
: mg_send(c, cmd->op_send.buf,
cmd->op_send.len))) {
cmd->result = (int) cmd->op_send.len;
} else if (s->nonblock || (cmd->op_send.flags & MSG_DONTWAIT)) {
cmd->result = -1, cmd->err = EAGAIN;
@@ -728,16 +784,20 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
case BSD_OP_SENDTO:
if ((s = bsd_get(cmd->op_sendto.fd)) == NULL) {
cmd->result = -1, cmd->err = EBADF;
} else if (!s->is_udp) {
cmd->result = -1, cmd->err = EOPNOTSUPP;
} else if ((c = s->c) == NULL || s->err != 0 || c->is_closing ||
c->is_draining) {
cmd->result = -1, cmd->err = EPIPE;
} else if (c->is_client) {
cmd->result = -1, cmd->err = EISCONN;
} else {
const struct sockaddr_in *sa =
(const struct sockaddr_in *) cmd->op_sendto.addr;
c->rem.port = sa->sin_port;
c->rem.addr.ip4 = sa->sin_addr.s_addr;
c->rem.is_ip6 = false;
if (mg_send(c, cmd->op_sendto.buf, cmd->op_sendto.len)) {
if (bsd_udp_too_big(c, cmd->op_sendto.len)) {
cmd->result = -1, cmd->err = EMSGSIZE;
} else if (bsd_udp_send(c, sa->sin_addr.s_addr, sa->sin_port,
cmd->op_sendto.buf, cmd->op_sendto.len)) {
cmd->result = (int) cmd->op_sendto.len;
} else if (s->nonblock || (cmd->op_sendto.flags & MSG_DONTWAIT)) {
cmd->result = -1, cmd->err = EAGAIN;
@@ -754,6 +814,9 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
if ((s = bsd_get(cmd->op_recvfrom.fd)) == NULL) {
cmd->result = -1, cmd->err = EBADF;
return true;
} else if (!s->is_udp) {
cmd->result = -1, cmd->err = EOPNOTSUPP;
return true;
}
if ((c = s->c) == NULL) {
cmd->result = s->closed && s->err == 0 ? 0 : -1;
@@ -761,18 +824,11 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
return true;
}
if (c->recv.len > 0) {
size_t n = cmd->op_recvfrom.len;
if (c->recv.len < n) n = c->recv.len;
memcpy(cmd->op_recvfrom.buf, c->recv.buf, n);
if (!(cmd->op_recvfrom.flags & MSG_PEEK)) mg_iobuf_del(&c->recv, 0, n);
if (cmd->op_recvfrom.src != NULL && cmd->op_recvfrom.addrlen != NULL) {
struct sockaddr_in *sa = (struct sockaddr_in *) cmd->op_recvfrom.src;
sa->sin_family = AF_INET;
sa->sin_port = c->rem.port;
sa->sin_addr.s_addr = c->rem.addr.ip4;
*cmd->op_recvfrom.addrlen = sizeof(*sa);
}
cmd->result = (int) n;
cmd->result = (int) bsd_recv(s, cmd->op_recvfrom.buf,
cmd->op_recvfrom.len,
cmd->op_recvfrom.flags,
cmd->op_recvfrom.src,
cmd->op_recvfrom.addrlen);
return true;
}
if (s->err != 0) {
@@ -1668,21 +1724,6 @@ static void mg_dash_handle_add(struct mg_connection *c, struct mg_dash *dash,
}
}
// Firmware upload callback for mg_http_stream_body(). Uses *p as a flag:
// non-NULL means an OTA session is open and must be closed by mg_ota_end()
static bool otacb(struct mg_http_message *hm, struct mg_str *data, void **p) {
if (hm != NULL) { // Start
if (!mg_ota_begin(hm->body.len)) return false;
*p = (void *) (ptrdiff_t) 1;
return true;
} else if (data != NULL) { // Next chunk
return mg_ota_write(data->buf, data->len);
}
if (*p == NULL) return false; // End. Begin failed, nothing to finish
*p = NULL;
return mg_ota_end();
}
static void mg_serve_fs(struct mg_connection *c, struct mg_http_message *hm,
struct mg_dash_user *u, bool stream) {
struct mg_dash *dash = (struct mg_dash *) c->fn_data;
@@ -1729,9 +1770,9 @@ void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
mg_serve_fs(c, hm, u, true);
return; // Important - the serve_fs() call can invalidate hm
} else if (mg_match(hm->uri, mg_str("/api/ota"), NULL)) {
// Authenticated above. If streaming started, the stream handler owns
// the connection and c->data, so skip the marker check below
if (mg_http_stream_body(c, hm, otacb, NULL)) return;
// Authenticated above. Streaming owns the connection and c->data.
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
return;
}
if (d->marker != '\0') mg_log_http_req(c, hm);
} else if (ev == MG_EV_HTTP_MSG && d->marker != '\0') {
@@ -3641,66 +3682,12 @@ struct mg_fs mg_fs_posix = {p_stat, p_list, p_open, p_close, p_read,
#if MG_ENABLE_HEALTH
#if MG_ARCH == MG_ARCH_ESP32
// reusable for other platforms, currently ESP32-only
struct mg_health_coredump {
const void *ptr;
size_t poffset;
size_t size;
};
static struct mg_health_coredump s_coredump;
#endif
bool mg_health_coredump_begin(size_t *size) {
#if MG_ARCH == MG_ARCH_ESP32 && CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
const esp_partition_t *p = esp_partition_find_first(
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_COREDUMP, NULL);
size_t address = 0, len = 0;
esp_err_t err = esp_core_dump_image_get(&address, &len);
if (size == NULL || s_coredump.ptr != NULL || err != ESP_OK || p == NULL)
return false;
s_coredump.ptr = p;
s_coredump.poffset = address - p->address;
s_coredump.size = len;
*size = len;
return true;
#else
(void) size;
return false;
#endif
}
bool mg_health_coredump_read(size_t offset, void *buf, size_t *len) {
#if MG_ARCH == MG_ARCH_ESP32 && CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
const esp_partition_t *p = (const esp_partition_t *) s_coredump.ptr;
if (p == NULL || offset > s_coredump.size)
return false;
if (*len > s_coredump.size - offset) *len = s_coredump.size - offset;
return esp_partition_read(p, s_coredump.poffset + offset, buf, *len) == ESP_OK;
#else
(void) offset, (void) buf, (void) len;
return false;
#endif
}
bool mg_health_coredump_end(void) {
#if MG_ARCH == MG_ARCH_ESP32 && CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
bool ok = s_coredump.ptr != NULL;
memset(&s_coredump, 0, sizeof(s_coredump));
return ok;
#else
return false;
#endif
}
// The one health record. Lives in RAM that survives a warm reset, see
// MG_HEALTH_RAM and the .mg_health region in the linker script
struct mg_health mg_health_record MG_HEALTH_RAM;
// STM32 H7, H5, C5 support. Needs the _estack symbol from the linker script
#if defined(STM32H7_DEV_ID) || defined(STM32H5) || defined(STM32C5)
// STM32 H723 support. Needs the _estack symbol from the linker script
#if MG_ENABLE_HEALTH && (defined(STM32H7_DEV_ID) || defined(STM32H5) || defined(STM32C5))
// Fault handler body. Runs in exception context: no printf, no malloc, no
// blocking calls. Copies the unwind registers and raw stack into the health
// record, then resets.
@@ -3742,57 +3729,52 @@ void BusFault_Handler(void) __attribute__((alias("HardFault_Handler")));
void UsageFault_Handler(void) __attribute__((alias("HardFault_Handler")));
#endif
// Start a new boot: validate the record. Called by MG_HEALTH_INIT()
// Start a new boot: validate the record, count the boot, store the reset
// reason. The current boot moves to prev_* slots first, so the previous
// boot's uptime and reason stay readable. Called by MG_HEALTH_INIT()
void mg_health_init(void) {
char magic[] = MG_HEALTH_MAGIC;
// struct mg_health *h = mg_health_get();
if (!mg_health_valid()) { // First boot, or RAM lost its contents
memset(&mg_health_record, 0, sizeof(mg_health_record));
memcpy(mg_health_record.magic, magic, sizeof(mg_health_record.magic));
}
#if MG_ARCH == MG_ARCH_ESP32
#if CONFIG_IDF_TARGET_ARCH_XTENSA
mg_health_record.arch = (uint16_t) MG_HEALTH_ARCH_XTENSA;
#elif CONFIG_IDF_TARGET_ARCH_RISCV
mg_health_record.arch = (uint16_t) MG_HEALTH_ARCH_RISCV;
#endif
esp_reset_reason_t rr = esp_reset_reason();
mg_health_record.reset_reason = esp2mg(rr);
#if CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH == 0
MG_ERROR(("MG_HEALTH is enabled but CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=n"));
#else
#if 0 && MG_ARCH == MG_ARCH_ESP32 && CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
{
// After a crash, copy the backtrace from the core dump in flash: the same
// addresses the panic handler prints as "Backtrace: PC:SP PC:SP ...".
// The core dump stays in flash, so check the reset reason, otherwise an
// old crash is reported on every boot
esp_core_dump_summary_t *s = calloc(1, sizeof(*s));
static esp_core_dump_summary_t s; // Big on RISC-V, keep it off the stack
esp_reset_reason_t rr = esp_reset_reason();
uint32_t *out = mg_health_record.data;
size_t n = 0;
size_t n = 0, max = MG_HEALTH_DATA_SIZE;
if (rr != ESP_RST_PANIC && rr != ESP_RST_INT_WDT && rr != ESP_RST_TASK_WDT)
return; // Not a crash that went through the panic handler
if (max == 0 || esp_core_dump_get_summary(&s) != ESP_OK) return;
mg_health_record.cpuid = 42; // ESP32
mg_health_record.reset_reason = MG_HEALTH_RESET_FAULT;
memset(out, 0, sizeof(mg_health_record.data));
if (s == NULL) return;
if ((rr != ESP_RST_PANIC && rr != ESP_RST_INT_WDT &&
rr != ESP_RST_TASK_WDT) // Not a crash caught by the panic handler
|| esp_core_dump_get_summary(s) != ESP_OK) {
free(s);
return;
}
out[n++] = s->exc_pc;
memcpy(&out[n], s->exc_task, 16), n += 4;
out[n++] = s.exc_pc;
#if CONFIG_IDF_TARGET_ARCH_XTENSA
out[n++] = s->ex_info.exc_a[0];
out[n++] = s->ex_info.exc_a[1];
size_t len = s->exc_bt_info.depth;
if (len > MG_HEALTH_DATA_SIZE - n) len = (MG_HEALTH_DATA_SIZE - n);
memcpy(&out[n], s->exc_bt_info.bt, 4 * len); // sizeof(uint32_t)
for (uint32_t i = 0; i < s.exc_bt_info.depth && n < max; i++) {
if (s.exc_bt_info.bt[i] != s.exc_pc) out[n++] = s.exc_bt_info.bt[i];
}
#elif CONFIG_IDF_TARGET_ARCH_RISCV
out[n++] = s->ex_info.exc_ra;
out[n++] = s->ex_info.exc_sp;
size_t len = s->exc_bt_info.dump_size / 4 ; // sizeof(uint32_t)
if (len > MG_HEALTH_DATA_SIZE - n) len = (MG_HEALTH_DATA_SIZE - n);
memcpy(&out[n], s->exc_bt_info.stackdump, 4 * len);
// No unwinder: take the return address, then words on the stack that
// point into executable memory (a heuristic, may include stale values)
if (n < max) out[n++] = s.ex_info.ra;
for (uint32_t i = 0; i + 4 <= s.exc_bt_info.dump_size && n < max; i += 4) {
uint32_t w;
memcpy(&w, &s.exc_bt_info.stackdump[i], sizeof(w)); // Byte array
if (esp_ptr_executable((void *) (uintptr_t) w)) out[n++] = w;
}
#endif
}
#endif
free(s);
#endif // CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
#endif // MG_ARCH_ESP32
}
static size_t print_uint32(void (*fn)(char, void *), void *arg, va_list *ap) {
@@ -3806,17 +3788,11 @@ static size_t print_uint32(void (*fn)(char, void *), void *arg, va_list *ap) {
size_t mg_print_crash_record(void (*fn)(char, void *), void *arg, va_list *ap) {
size_t len = 0;
if (mg_health_valid() &&
(mg_health_record.reset_reason == (uint16_t) MG_HEALTH_RESET_FAULT
#if MG_ARCH == MG_ARCH_ESP32
|| mg_health_record.reset_reason == (uint16_t) MG_HEALTH_RESET_WATCHDOG
#endif
)) {
mg_health_record.reset_reason == MG_HEALTH_RESET_FAULT) {
size_t n = sizeof(mg_health_record.data) / sizeof(mg_health_record.data[0]);
len += mg_xprintf(fn, arg, "{%m:%hhu,%m:%hu,%m:%hu,%m:%u,%m:[%M]}", //
MG_ESC("version"), mg_health_record.magic[3], //
MG_ESC("arch"), mg_health_record.arch, //
MG_ESC("reason"), mg_health_record.reset_reason, //
MG_ESC("cpuid"), mg_health_record.cpuid, //
len += mg_xprintf(fn, arg, "{%m:%hhu,%m:%u,%m:[%M]}", //
MG_ESC("version"), mg_health_record.magic[3], //
MG_ESC("cpuid"), mg_health_record.cpuid, //
MG_ESC("data"), print_uint32, n, mg_health_record.data);
} else {
len += mg_xprintf(fn, arg, "{}");
@@ -3824,7 +3800,6 @@ size_t mg_print_crash_record(void (*fn)(char, void *), void *arg, va_list *ap) {
(void) ap;
return len;
}
#endif
#ifdef MG_ENABLE_LINES
#line 1 "src/http.c"
@@ -4710,8 +4685,8 @@ void mg_http_serve_upload(struct mg_connection *c, struct mg_http_message *hm,
fs->rm(path); // MG_FS_WRITE appends, not truncates: drop any old file
if ((fd = mg_fs_open(fs, path, MG_FS_WRITE)) == NULL) {
MG_ERROR(("Cannot open %s for upload", path));
} else if (!mg_http_stream_body(c, hm, serve_upload_cb, fd)) {
mg_fs_close(fd);
} else {
mg_http_stream_body(c, hm, serve_upload_cb, fd);
}
}
}
@@ -4845,6 +4820,7 @@ struct mg_upload_state {
size_t received; // Number of body bytes received so far
void *user_data; // Passed to cb, free for cb to use
bool failed; // Set when cb returned false
bool finished; // End callback has been called
};
// Event handler installed by mg_http_stream_body(). Feeds the body to the
@@ -4852,18 +4828,18 @@ struct mg_upload_state {
// handler: the connection is closed after the reply.
static void mg_stream_handler(struct mg_connection *c, int ev, void *ev_data) {
struct mg_upload_state *us = (struct mg_upload_state *) c->data;
bool (*fn)(struct mg_http_message *, struct mg_str *, void **) =
bool (*fn)(struct mg_http_message *, struct mg_str *, void **) =
(bool (*)(struct mg_http_message *, struct mg_str *, void **))
(ptrdiff_t) c->fn_data;
(void) ev_data;
if (ev == MG_EV_CLOSE || ev == MG_EV_ERROR) {
if (us->received < us->expected) {
if (!us->finished) {
// Connection dropped mid-upload. Let cb clean up, e.g. call mg_ota_end()
us->received = us->expected; // MG_EV_ERROR is followed by MG_EV_CLOSE
us->finished = true; // MG_EV_ERROR is followed by MG_EV_CLOSE
fn(NULL, NULL, &us->user_data);
}
} else if (ev == MG_EV_READ && !c->is_draining) {
if (c->recv.len > 0 && us->received < us->expected) {
} else if (ev == MG_EV_READ && !c->is_draining && !us->finished) {
if (!us->failed && c->recv.len > 0 && us->received < us->expected) {
// Got some uploaded data. Feed by 512-byte aligned chunks, for OTA
size_t left = us->expected - us->received, r = c->recv.len;
size_t aligned = r < left ? MG_ROUND_DOWN(r, 512) : left;
@@ -4874,10 +4850,13 @@ static void mg_stream_handler(struct mg_connection *c, int ev, void *ev_data) {
us->received += aligned;
mg_iobuf_del(&c->recv, 0, aligned); // Shift the unprocessed tail
}
if (us->received >= us->expected) {
if (!us->finished && (us->failed || us->received >= us->expected)) {
// Finished upload. Always call cb, even after error, so it can clean up
us->finished = true;
us->failed |= !fn(NULL, NULL, &us->user_data);
if (!c->is_client) {
if (c->is_client && us->failed) {
mg_error(c, "Body streaming failed");
} else if (!c->is_client) {
mg_http_reply(c, us->failed ? 500 : 200, NULL, "%zu %s\n",
us->received, us->failed ? "fail" : "ok");
}
@@ -4886,48 +4865,54 @@ static void mg_stream_handler(struct mg_connection *c, int ev, void *ev_data) {
}
}
bool mg_http_stream_body(struct mg_connection *c, struct mg_http_message *hm,
void mg_http_stream_body(struct mg_connection *c, struct mg_http_message *hm,
bool (*cb)(struct mg_http_message *, struct mg_str *,
void **user_data),
void **),
void *user_data) {
struct mg_upload_state *us = (struct mg_upload_state *) c->data;
struct mg_str *te = mg_http_get_header(hm, "Transfer-Encoding");
bool handled = false;
int code = 0;
size_t head_len = hm->head.len;
// Catch upload requests early, without buffering whole body. Called on the
// MG_EV_HTTP_HDRS event: we've received all HTTP headers but not necessarily
// the full HTTP body
if (hm->body.len == (size_t) ~0 ||
(te != NULL && mg_strcasecmp(*te, mg_str("chunked")) == 0)) {
// Unknown length or chunked, cannot stream. Leave it to the caller
} else if (!c->is_client &&
mg_strcmp(hm->method, mg_str("POST")) != 0 &&
mg_strcmp(hm->method, mg_str("PUT")) != 0) {
// Not an upload request. Leave it to the caller
if (!c->is_client && mg_strcmp(hm->method, mg_str("POST")) != 0 &&
mg_strcmp(hm->method, mg_str("PUT")) != 0) {
code = 405;
} else if (mg_http_get_header(hm, "Transfer-Encoding") != NULL) {
code = 400;
} else if (hm->body.len == (size_t) ~0 ||
(!c->is_client && mg_http_get_header(hm, "Content-Length") == NULL)) {
code = 411;
} else if (sizeof(*us) > sizeof(c->data)) {
if (!c->is_client) { // Nobody to reply to on a client connection
c->pfn = NULL;
mg_http_reply(c, 500, NULL, "data too small\n");
c->is_draining = 1;
cb(NULL, NULL, &user_data); // Let cb release user_data, start skipped
handled = true;
code = 500;
}
c->pfn = NULL; // Take ownership even when validation fails
if (code != 0) {
c->fn = NULL;
c->fn_data = NULL;
mg_iobuf_del(&c->recv, 0, c->recv.len);
cb(NULL, NULL, &user_data); // Release resources even if start was skipped
if (c->is_client) {
mg_error(c, "%s", mg_http_status_code_str(code));
} else {
mg_http_reply(c, code, code == 405 ? "Allow: POST, PUT\r\n" : NULL, "");
}
c->is_draining = 1;
} else {
c->pfn = NULL; // Silence HTTP protocol handler
memset(us, 0, sizeof(*us));
us->expected = hm->body.len; // Store number of bytes we expect
us->user_data = user_data;
c->fn = mg_stream_handler;
c->fn_data = (void *) (ptrdiff_t) cb;
us->failed = !cb(hm, NULL, &us->user_data); // Call before deleting headers
if (mg_http_get_header(hm, "Expect") != NULL) {
if (!c->is_client && !us->failed && !us->finished &&
mg_http_get_header(hm, "Expect") != NULL) {
mg_http_reply(c, 100, NULL, ""); // If curl expects, we continue
}
mg_iobuf_del(&c->recv, 0, hm->head.len); // Delete HTTP headers
c->fn = mg_stream_handler; // We own the connection from now
c->fn_data = (void *) (ptrdiff_t) cb;
mg_iobuf_del(&c->recv, 0, head_len); // Delete HTTP headers
mg_call(c, MG_EV_READ, &c->recv.len); // Process the body bytes we have
handled = true;
}
return handled;
}
static bool is_hex_digit(int c) {
@@ -5852,7 +5837,7 @@ size_t mg_l2_driver_output(struct mg_tcpip_if *ifp, size_t len) {
#define MGI_802154_DATA (125 - MGI_802154_HDR)
size_t mg_l2_802154_tx(struct mg_tcpip_if *ifp, const void *buf, size_t len,
size_t (*send)(const uint8_t *, size_t,
size_t (*send_fn)(const uint8_t *, size_t,
struct mg_tcpip_if *)) {
struct mg_l2_802154_data *d =
(struct mg_l2_802154_data *) ifp->driver_data;
@@ -5870,7 +5855,7 @@ size_t mg_l2_802154_tx(struct mg_tcpip_if *ifp, const void *buf, size_t len,
f[12] = m[2], f[13] = m[1], f[14] = m[0]; // Little endian EUI-64
f[15] = d->tag, f[16] = (uint8_t) ((i << 4) | last);
memcpy(f + MGI_802154_HDR, p + i * MGI_802154_DATA, n);
if (send(f, MGI_802154_HDR + n, ifp) == 0) return 0;
if (send_fn(f, MGI_802154_HDR + n, ifp) == 0) return 0;
}
return len;
}
@@ -7404,7 +7389,6 @@ static void mg_mdash_fn(struct mg_connection *c, int ev, void *ev_data) {
}
mg_iobuf_free(&io);
} else if (ev == MG_EV_CLOSE) {
mg_health_coredump_end();
mg_mdash_conn = NULL;
MG_INFO(("disconnected, ram %lu", 0));
}
@@ -7462,35 +7446,6 @@ static void mg_mdash_rpc_ota_end(struct mg_rpc_req *r) {
// MG_ESC("percent"));
}
static void rpc_coredump_begin(struct mg_rpc_req *r) {
size_t size = 0;
if (mg_health_coredump_begin(&size)) {
mg_rpc_ok(r, "{%m:%llu}", MG_ESC("size"), (uint64_t) size);
} else {
mg_rpc_err(r, 500, "%m", MG_ESC("failed"));
}
}
static void rpc_coredump_read(struct mg_rpc_req *r) {
uint8_t buf[768];
size_t len = sizeof(buf);
size_t offset = (size_t) mg_json_get_long(r->frame, "$.params.offset", -1);
if (mg_health_coredump_read(offset, buf, &len)) {
mg_rpc_ok(r, "{%m:\"%M\"}", MG_ESC("data"), mg_print_base64,
(int) len, buf);
} else {
mg_rpc_err(r, 500, "%m", MG_ESC("failed"));
}
}
static void rpc_coredump_end(struct mg_rpc_req *r) {
if (mg_health_coredump_end()) {
mg_rpc_ok(r, "true");
} else {
mg_rpc_err(r, 500, "%m", MG_ESC("failed"));
}
}
void mg_mdash_init(struct mg_mgr *mgr) {
MG_INFO(("Initialising mdash agent, key [%s]", MG_MDASH_KEY));
mg_rpc_add(&mgr->rpcs, mg_str("RPC.List"), mg_rpc_list, NULL);
@@ -7498,9 +7453,6 @@ void mg_mdash_init(struct mg_mgr *mgr) {
mg_rpc_add(&mgr->rpcs, mg_str("OTA.Begin"), mg_mdash_rpc_ota_begin, NULL);
mg_rpc_add(&mgr->rpcs, mg_str("OTA.Write"), mg_mdash_rpc_ota_write, NULL);
mg_rpc_add(&mgr->rpcs, mg_str("OTA.End"), mg_mdash_rpc_ota_end, NULL);
mg_rpc_add(&mgr->rpcs, mg_str("CoreDump.Begin"), rpc_coredump_begin, NULL);
mg_rpc_add(&mgr->rpcs, mg_str("CoreDump.Read"), rpc_coredump_read, NULL);
mg_rpc_add(&mgr->rpcs, mg_str("CoreDump.End"), rpc_coredump_end, NULL);
}
void mg_mdash_poll(struct mg_mgr *mgr) {
@@ -9378,6 +9330,36 @@ 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);
#if MG_ENABLE_BSD_SOCKETS
static uint8_t *sendto_cache_find(struct mg_tcpip_if *ifp, uint32_t ip) {
struct mg_tcpip_sendto_entry *a;
for (a = ifp->sendto; a < ifp->sendto + MG_TCPIP_SENDTO_CACHE_SIZE; a++) {
if (a->ip == ip) return (uint8_t *) &a->addr;
}
return NULL;
}
static void sendto_cache_add(struct mg_tcpip_if *ifp, uint32_t ip,
const uint8_t *addr) {
struct mg_tcpip_sendto_entry *a;
for (a = ifp->sendto; a < ifp->sendto + MG_TCPIP_SENDTO_CACHE_SIZE; a++) {
if (a->ip == ip) return; // already cached, skip
if (a->ip == 0) break; // not present, first empty slot
}
if (a == ifp->sendto + MG_TCPIP_SENDTO_CACHE_SIZE) { // no room
memmove(ifp->sendto, ifp->sendto + 1,
sizeof(ifp->sendto[0]) * (MG_TCPIP_SENDTO_CACHE_SIZE - 1));
a--; // make room and point to last entry (just made)
}
a->ip = ip; // populate this entry
memcpy(&a->addr, addr, sizeof(a->addr));
}
static void sendto_cache_clear(struct mg_tcpip_if *ifp) {
memset(ifp->sendto, 0, sizeof(ifp->sendto));
}
#endif
// 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) {
@@ -9983,30 +9965,57 @@ static void proto_error(struct mg_connection *c, bool r, const char *f, ...) {
static bool rx_udp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
struct mg_connection *c = getpeer(ifp->mgr, pkt, true);
struct connstate *s;
struct mg_addr rem;
uint8_t *l2addr;
size_t len = pkt->pay.len;
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;
if (c->is_bsd && c->is_client &&
(c->rem.port != pkt->udp->sport ||
!MG_IP6MATCH(c->rem.addr.ip6, pkt->ip6->src)))
return true;
rem.addr.ip6[0] = pkt->ip6->src[0],
rem.addr.ip6[1] = pkt->ip6->src[1], rem.is_ip6 = true;
} else
#endif
{
if (!udpcsum_ok(pkt->ip, pkt->udp)) return false;
c->rem.addr.ip4 = pkt->ip->src;
if (c->is_bsd && c->is_client &&
(c->rem.port != pkt->udp->sport || c->rem.addr.ip4 != pkt->ip->src))
return true;
rem.addr.ip4 = pkt->ip->src, rem.is_ip6 = false;
}
if ((l2addr = get_return_l2addr(ifp, &c->rem, true, pkt)) == NULL)
rem.port = pkt->udp->sport;
if (!c->is_bsd || c->is_client) c->rem = rem;
if ((l2addr = get_return_l2addr(ifp, &rem, true, pkt)) == NULL)
return false; // safety net for lousy networks
memcpy(s->mac, l2addr, sizeof(s->mac));
if (!c->is_bsd || c->is_client) memcpy(s->mac, l2addr, sizeof(s->mac));
#if MG_ENABLE_BSD_SOCKETS
if (c->is_bsd && !rem.is_ip6 && MG_IPISLOCAL(ifp, rem.addr.ip4))
sendto_cache_add(ifp, rem.addr.ip4, l2addr);
#endif
#if MG_ENABLE_BSD_SOCKETS
if (c->is_bsd) len += sizeof(struct mg_bsd_udp_frame);
#endif
if (c->recv.len >= MG_MAX_RECV_SIZE) {
proto_error(c, false, "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)) {
} else if (c->recv.size - c->recv.len < len &&
!mg_iobuf_resize(&c->recv, c->recv.len + len)) {
proto_error(c, false, "oom");
} else {
#if MG_ENABLE_BSD_SOCKETS
if (c->is_bsd) {
struct mg_bsd_udp_frame *h =
(struct mg_bsd_udp_frame *) &c->recv.buf[c->recv.len];
h->ip = rem.addr.ip4;
h->port = rem.port;
h->len = (uint16_t) pkt->pay.len;
c->recv.len += sizeof(*h);
}
#endif
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);
@@ -11059,6 +11068,9 @@ void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) {
static void close_conn(struct mg_connection *c) {
struct connstate *s = (struct connstate *) (c + 1);
#if MG_ENABLE_BSD_SOCKETS
if (c->is_bsd && c->is_udp) sendto_cache_clear(c->mgr->ifp);
#endif
mg_iobuf_free(&s->txq);
mg_close_conn(c);
}
@@ -11080,6 +11092,11 @@ static void send_syn(struct mg_connection *c) {
static void l2addr_resolved(struct mg_connection *c) {
if (c->is_udp) {
c->is_connecting = 0;
#if MG_ENABLE_BSD_SOCKETS
if (c->is_bsd)
sendto_cache_add(c->mgr->ifp, c->rem.addr.ip4,
((struct connstate *) (c + 1))->mac);
#endif
mg_call(c, MG_EV_CONNECT, NULL);
} else {
send_syn(c);
@@ -11094,20 +11111,25 @@ void mg_connect_resolved(struct mg_connection *c) {
s->retransmit = ifp->enable_tcp_retransmit;
s->txq.align = MG_TCPIP_TXQ_ALIGN;
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 (c->loc.port == 0) {
if (ifp->eport < MG_EPHEMERAL_PORT_BASE)
ifp->eport = MG_EPHEMERAL_PORT_BASE;
c->loc.port = mg_htons(ifp->eport++);
} // otherwise retain an existing bound port
#if MG_ENABLE_IPV6
if (c->rem.is_ip6) {
if (c->rem.addr.ip6[0] == ifp->ip6ll[0]) { // same local LAN, use ll
if (c->loc.addr.ip6[0] == 0 && c->loc.addr.ip6[1] == 0 &&
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
} else if (c->loc.addr.ip6[0] == 0 && c->loc.addr.ip6[1] == 0) {
// Use global address
c->loc.addr.ip6[0] = ifp->ip6[0], c->loc.addr.ip6[1] = ifp->ip6[1];
}
} // otherwise retain an existing bound address
c->loc.is_ip6 = true;
} else
#endif
{
c->loc.addr.ip4 = ifp->ip;
if (c->loc.addr.ip4 == 0) 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));
@@ -11138,11 +11160,20 @@ void mg_connect_resolved(struct mg_connection *c) {
uint32_t rem_ip = c->rem.addr.ip4;
if (ifp->ip && MG_IPISLOCAL(ifp, rem_ip) &&
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;
// We're in the same LAN
#if MG_ENABLE_BSD_SOCKETS
if (c->is_bsd && c->is_udp && // see if we already have L2 data
(l2addr = sendto_cache_find(ifp, rem_ip)) != NULL) {
memcpy(s->mac, l2addr, sizeof(s->mac));
l2addr_resolved(c);
} else
#endif
{ // 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) {
memcpy(s->mac, ifp->gwmac, sizeof(s->mac));
l2addr_resolved(c);
@@ -11162,13 +11193,36 @@ bool mg_open_listener(struct mg_connection *c, const char *url) {
}
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);
long len;
size_t off = 0;
bool empty = false;
#if MG_ENABLE_BSD_SOCKETS
struct mg_bsd_udp_frame *h;
if (c->is_bsd && c->is_udp) {
if (c->is_connecting) return;
h = (struct mg_bsd_udp_frame *) c->send.buf;
assert(c->send.len >= sizeof(*h));
assert(h->len <= c->send.len - sizeof(*h));
c->rem.port = h->port;
c->rem.is_ip6 = false;
if (!c->is_client && c->rem.addr.ip4 != h->ip) {
c->rem.addr.ip4 = h->ip;
mg_connect_resolved(c);
if (c->is_connecting) return;
}
len = mg_io_send(c, h + 1, h->len);
off = sizeof(*h), empty = h->len == 0;
} else
#endif
{
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) {
proto_error(c, false, "tx err");
} else if (len > 0) {
mg_iobuf_del(&c->send, 0, (size_t) len);
} else if (len > 0 || empty) {
mg_iobuf_del(&c->send, 0, off + (size_t) len);
mg_call(c, MG_EV_WRITE, &len);
}
}
@@ -11528,9 +11582,7 @@ static void s_firmware_fn(struct mg_connection *c, int ev, void *ev_data) {
(unsigned long) hm->body.len, (unsigned long) s_ota->size);
} else {
MG_DEBUG(("Beginning OTA (%lu bytes)", (unsigned long) s_ota->size));
if (!mg_http_stream_body(c, hm, s_firmware_cb, NULL)) {
mg_error(c, "Cannot stream firmware");
}
mg_http_stream_body(c, hm, s_firmware_cb, NULL);
}
} else if (ev == MG_EV_ERROR) {
s_ota->fn((char *) ev_data);
@@ -11563,6 +11615,21 @@ void mg_ota_url_check(struct mg_mgr *mgr, const char *json_url,
}
}
// Uses *p as a flag: non-NULL means an OTA session is open and must be
// closed by mg_ota_end()
bool mg_http_ota_cb(struct mg_http_message *hm, struct mg_str *data, void **p) {
if (hm != NULL) { // Start
if (!mg_ota_begin(hm->body.len)) return false;
*p = (void *) (ptrdiff_t) 1;
return true;
} else if (data != NULL) { // Next chunk
return mg_ota_write(data->buf, data->len);
}
if (*p == NULL) return false; // End. Begin failed, nothing to finish
*p = NULL;
return mg_ota_end();
}
void mg_ota_poll(struct mg_mgr *mgr) {
static uint64_t t = 5000; // Fire first time 5 sec after boot
@@ -15464,7 +15531,11 @@ static long recv_raw(struct mg_connection *c, void *buf, size_t len) {
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));
if (n > 0) {
struct mg_addr rem;
tomgaddr(&usa, &rem, slen != sizeof(usa.sin));
c->rem = rem;
}
} else {
n = recv(FD(c), (char *) buf, len, MSG_NONBLOCKING);
}
@@ -29490,8 +29561,9 @@ uint64_t mg_ntohll(uint64_t net) {
}
void mg_delayms(unsigned int ms) {
uint64_t to = mg_millis() + ms + 1;
while (mg_millis() < to) (void) 0;
uint64_t now, to = mg_millis() + ms + 1;
while ((now = mg_millis()) < to)
if (now + ms + 1 < to) to = now + ms + 1;
}
#if MG_ENABLE_CUSTOM_CALLOC
@@ -34618,7 +34690,7 @@ static bool st67w6_init(uint8_t *mac) {
}
// 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
MG_VERBOSE(("%.*s", s_at_resp_len, at_resp));
// BT-ENABLED DEPENDENCY
// MODULE DEPENDENCY
+64 -99
View File
@@ -253,11 +253,8 @@ extern "C" {
#include <esp_system.h>
#include <esp_mac.h>
#include <esp_idf_version.h>
#if MG_ENABLE_HEALTH
#include <esp_memory_utils.h>
#include <esp_core_dump.h>
#include <esp_partition.h>
#endif
// #include <esp_memory_utils.h> // For health record coredumps
// #include <esp_core_dump.h> // For health record coredumps
#define MG_PATH_MAX 128
@@ -2727,29 +2724,32 @@ int mg_http_status(const struct mg_http_message *hm);
// Streams a POST/PUT body to a callback as it arrives, without buffering it.
// Call it from an MG_EV_HTTP_HDRS handler, after matching the request, e.g.
// with mg_match(). Returns true if it took over the connection, false if the
// request is not POST/PUT, is chunked, or has no Content-Length.
// with mg_match(). Always takes over the connection. Invalid requests receive
// an HTTP error; client-side failures call mg_error(). Replies and connection
// draining are handled internally, including callback failures.
//
// Callback upload_fn(hm, data, user_data) is called:
// - at start: hm is set. Open the file, set *user_data.
// - per chunk: data is set, 512 byte aligned except the last. Not called
// again after a failure.
// - at end: both NULL. Always called once, also when the connection drops
// early, so release resources here. A truncated body looks like a complete
// one: validate before committing.
// Return true on success, false on error.
// - at end: both NULL. Always called exactly once, including validation
// failures before start and connections that drop early. Release resources
// here. A truncated body looks like a complete one: validate before
// committing.
// The callback returns true on success, false on error. A failure stops
// streaming immediately and calls the end callback before closing.
// Example:
// if (ev == MG_EV_HTTP_HDRS && mg_match(hm->uri, mg_str("/fs/*"), NULL) {
// mg_http_stream_body(c, hm, upload, NULL));
// if (ev == MG_EV_HTTP_HDRS && mg_match(hm->uri, mg_str("/fs/*"), NULL)) {
// mg_http_stream_body(c, hm, upload, NULL);
// }
// Full example:
// tutorials/http/file-upload-single-post
// Related APIs:
// mg_http_listen(), mg_http_next_multipart()
// Notes:
// After it returns true, this function hijacks the connection. Existing event
// handler not called, hm is invalid, c->data and c->fn_data are modified
bool mg_http_stream_body(struct mg_connection *c, struct mg_http_message *hm,
// The existing event handler is no longer called. hm is invalid after this
// call; c->data and c->fn_data are reserved for streaming state.
void mg_http_stream_body(struct mg_connection *c, struct mg_http_message *hm,
bool (*upload_fn)(struct mg_http_message *,
struct mg_str *, void **user_data),
void *user_data);
@@ -4223,9 +4223,8 @@ bool mg_ota_end(void);
// ```c
// if (ev == MG_EV_HTTP_HDRS) {
// struct mg_http_message *hm = (struct mg_http_message *) ev_data;
// if (mg_match(hm->uri, mg_str("/ota"), NULL) &&
// !mg_http_stream_body(c, hm, mg_http_ota_cb, NULL)) {
// mg_http_reply(c, 400, "", "POST firmware with Content-Length\n");
// if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
// mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
// }
// }
// ```
@@ -4374,8 +4373,6 @@ void mg_ota_poll(struct mg_mgr *);
#if MG_ENABLE_HEALTH
#ifndef MG_HEALTH_MAGIC
#define MG_HEALTH_MAGIC {'M', 'G', 'H', '3'} // '3' is the layout version
#endif
@@ -4384,15 +4381,13 @@ void mg_ota_poll(struct mg_mgr *);
#define MG_HEALTH_RAM __attribute__((section(".mg_health")))
#endif
#ifndef MG_HEALTH_DATA_SIZE
#if (defined(STM32H7_DEV_ID) || defined(STM32H5)) || defined(STM32C5)
#if !defined(MG_HEALTH_DATA_SIZE) && (defined(STM32H7_DEV_ID) || defined(STM32H5) || defined(STM32C5))
#define MG_HEALTH_DATA_SIZE 250
#elif MG_ARCH == MG_ARCH_ESP32
#define MG_HEALTH_DATA_SIZE 23
#else
#endif
#ifndef MG_HEALTH_DATA_SIZE
#define MG_HEALTH_DATA_SIZE 10
#endif
#endif // MG_HEALTH_DATA_SIZE
// Places the record in RAM that survives a reset. Define in mongoose_config.h
// to match the linker script, e.g. __attribute__((section(".mg_health")))
@@ -4400,13 +4395,6 @@ void mg_ota_poll(struct mg_mgr *);
#define MG_HEALTH_RAM
#endif
// CPU core architecture
enum mg_health_arch {
MG_HEALTH_ARCH_ARM = 0,
MG_HEALTH_ARCH_XTENSA,
MG_HEALTH_ARCH_RISCV
};
// Why the device restarted. Hardware usually reports several flags at once,
// so these are ordered by how much they tell you: the most specific cause a
// device reports wins
@@ -4423,8 +4411,7 @@ enum mg_health_reason {
struct mg_health {
char magic[4]; // MG_HEALTH_MAGIC when the record holds valid data
uint16_t reset_reason; // enum mg_health_reason that started this boot
uint16_t arch; // enum mg_health_arch, core architecture (ARM, etc)
uint32_t reset_reason; // enum mg_health_reason that started this boot
uint32_t cpuid; // ARM CPU ID
uint32_t data[MG_HEALTH_DATA_SIZE];
};
@@ -4457,72 +4444,9 @@ static inline const char *mg_health_reason_str(int reason) {
}
}
#if MG_ARCH == MG_ARCH_ESP32
#ifndef CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
#define CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH 0
#endif
#ifndef ESP_PARTITION_SUBTYPE_DATA_COREDUMP
#define ESP_PARTITION_SUBTYPE_DATA_COREDUMP 0x03
#endif
// clang-format off
static inline uint16_t esp2mg(esp_reset_reason_t rr) {
switch(rr) {
case ESP_RST_POWERON: return (uint16_t) MG_HEALTH_RESET_POWER;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 2)
case ESP_RST_PWR_GLITCH:
#endif
case ESP_RST_BROWNOUT: return (uint16_t) MG_HEALTH_RESET_BROWNOUT;
case ESP_RST_EXT: return (uint16_t) MG_HEALTH_RESET_PIN;
case ESP_RST_SW: return (uint16_t) MG_HEALTH_RESET_SOFTWARE;
case ESP_RST_DEEPSLEEP: return (uint16_t) MG_HEALTH_RESET_LOWPOWER;
case ESP_RST_INT_WDT:
case ESP_RST_TASK_WDT:
case ESP_RST_WDT: return (uint16_t) MG_HEALTH_RESET_WATCHDOG;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 2)
case ESP_RST_CPU_LOCKUP:
#endif
case ESP_RST_PANIC: return (uint16_t) MG_HEALTH_RESET_FAULT;
default: break;
}
return (uint16_t) MG_HEALTH_RESET_UNKNOWN;
}
// clang-format on
#endif
#define MG_HEALTH_INIT() mg_health_init()
extern size_t mg_print_crash_record(mg_pfn_t, void *, va_list *);
extern void mg_health_init(void);
extern bool mg_health_coredump_begin(size_t *size);
extern bool mg_health_coredump_read(size_t offset, void *buf, size_t *len);
extern bool mg_health_coredump_end(void);
#else
#define MG_HEALTH_INIT()
static inline int mg_health_reason(void) {
return 0;
}
static inline const char *mg_health_reason_str(int reason) {
(void) reason;
return "unknown";
}
static inline size_t mg_print_crash_record(mg_pfn_t fn, void *arg,
va_list *ap) {
(void) ap;
return mg_xprintf(fn, arg, "{}");
}
static inline bool mg_health_coredump_begin(size_t *size) {
(void) size;
return false;
}
static inline bool mg_health_coredump_read(size_t offset, void *buf,
size_t *len) {
(void) offset, (void) buf, (void) len;
return false;
}
static inline bool mg_health_coredump_end(void) {
return false;
}
#endif
// Cloud connector API to https://mdash.net - device management cloud
@@ -4696,6 +4620,17 @@ struct mg_tcpip_driver {
typedef void (*mg_tcpip_event_handler_t)(struct mg_tcpip_if *ifp, int ev,
void *ev_data);
#if MG_ENABLE_BSD_SOCKETS
#ifndef MG_TCPIP_SENDTO_CACHE_SIZE
#define MG_TCPIP_SENDTO_CACHE_SIZE 4
#endif
struct mg_tcpip_sendto_entry {
uint32_t ip;
struct mg_l2addr addr;
};
#endif
// 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.
@@ -4758,6 +4693,9 @@ struct mg_tcpip_if {
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)
#if MG_ENABLE_BSD_SOCKETS
struct mg_tcpip_sendto_entry sendto[MG_TCPIP_SENDTO_CACHE_SIZE]; // Local IPv4 neighbors
#endif
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
@@ -4969,10 +4907,37 @@ void mg_bsd_poll(struct mg_mgr *);
#ifndef EPIPE
#define EPIPE 32
#endif
#ifndef EOPNOTSUPP
#define EOPNOTSUPP 95
#endif
#ifndef EISCONN
#define EISCONN 106
#endif
#ifndef EDESTADDRREQ
#define EDESTADDRREQ 121
#endif
#ifndef EMSGSIZE
#define EMSGSIZE 90
#endif
#ifndef EPROTONOSUPPORT
#define EPROTONOSUPPORT 93
#endif
#if defined(__DCC__)
#pragma pack(1)
#else
#pragma pack(push, 1)
#endif
struct mg_bsd_udp_frame {
uint32_t ip;
uint16_t port, len;
};
#if defined(__DCC__)
#pragma pack(0)
#else
#pragma pack(pop)
#endif
#if MG_ENABLE_BSD_PROTOTYPES
typedef unsigned int socklen_t;
typedef unsigned long nfds_t;
+2 -5
View File
@@ -22,11 +22,8 @@
#include <esp_system.h>
#include <esp_mac.h>
#include <esp_idf_version.h>
#if MG_ENABLE_HEALTH
#include <esp_memory_utils.h>
#include <esp_core_dump.h>
#include <esp_partition.h>
#endif
// #include <esp_memory_utils.h> // For health record coredumps
// #include <esp_core_dump.h> // For health record coredumps
#define MG_PATH_MAX 128
+86 -30
View File
@@ -1,5 +1,6 @@
#include "bsd.h"
#include "dns.h"
#include "util.h"
#include <stdarg.h>
#if MG_ENABLE_BSD_SOCKETS && MG_ENABLE_FREERTOS
@@ -110,6 +111,57 @@ static struct mg_connection *bsd_new_conn(struct mg_bsd_sock *s) {
return c;
}
// Mirror net_builtin.c::trim_len() logic
static bool bsd_udp_too_big(struct mg_connection *c, size_t len) {
struct mg_tcpip_if *ifp = c->mgr->ifp;
size_t hlen = c->rem.is_ip6 ? 48 : 32; // IP plus UDP headers
size_t cap = ifp->mtu - hlen;
size_t room = ifp->tx.len - (ifp->framesize - ifp->l2mtu) - hlen;
return len > cap || len > room;
}
static bool bsd_udp_send(struct mg_connection *c, uint32_t ip, uint16_t port,
const void *buf, size_t len) {
struct mg_bsd_udp_frame *h;
size_t off = c->send.len, total = sizeof(*h) + len;
if (mg_iobuf_add(&c->send, off, NULL, total) != total) return false;
h = (struct mg_bsd_udp_frame *) (c->send.buf + off);
h->ip = ip;
h->port = port;
h->len = (uint16_t) len;
memcpy(h + 1, buf, len);
return true;
}
static size_t bsd_recv(struct mg_bsd_sock *s, void *buf, size_t len,
int flags, struct sockaddr *src, socklen_t *addrlen) {
struct mg_connection *c = s->c;
size_t n;
if (!s->is_udp) {
n = c->recv.len < len ? c->recv.len : len;
memcpy(buf, c->recv.buf, n);
if (!(flags & MSG_PEEK)) mg_iobuf_del(&c->recv, 0, n);
} else {
struct mg_bsd_udp_frame *h = (struct mg_bsd_udp_frame *) c->recv.buf;
assert(c->recv.len >= sizeof(*h));
assert(h->len <= c->recv.len - sizeof(*h));
n = h->len < len ? h->len : len;
memcpy(buf, h + 1, n);
if (src != NULL && addrlen != NULL) {
struct sockaddr_in *sa = (struct sockaddr_in *) src;
sa->sin_family = AF_INET;
sa->sin_port = h->port;
sa->sin_addr.s_addr = h->ip;
*addrlen = sizeof(*sa);
}
if (!(flags & MSG_PEEK)) mg_iobuf_del(&c->recv, 0, sizeof(*h) + h->len);
}
return n;
}
#if MG_ENABLE_FREERTOS
// Complete operations blocked on c before Mongoose destroys it.
static void bsd_finish_waiters(struct mg_bsd_sock *s, bool error) {
@@ -290,9 +342,9 @@ static int bsd_ready(struct mg_bsd_sock *s) {
return 0;
}
if (s->c->recv.len > 0 || s->c->is_draining) ready |= BSD_READ;
if (!s->is_udp && !s->c->is_connecting && !s->c->is_closing &&
if ((s->is_udp || !s->c->is_connecting) && !s->c->is_closing &&
!s->c->is_draining)
ready |= BSD_WRITE; // Established TCP socket
ready |= BSD_WRITE; // UDP socket or established TCP socket
return ready;
}
@@ -306,7 +358,7 @@ static void bsd_close(struct mg_bsd_sock *s) {
if (t->orphan && t->listener == s) {
if (t->c != NULL) {
t->c->fn_data = NULL;
mg_close_conn(t->c);
t->c->is_draining = 1;
t->c = NULL;
}
memset(t, 0, sizeof(*t)); // Release orphan
@@ -314,7 +366,7 @@ static void bsd_close(struct mg_bsd_sock *s) {
}
if (s->c != NULL) {
s->c->fn_data = NULL;
mg_close_conn(s->c);
s->c->is_draining = 1;
s->c = NULL;
}
memset(s, 0, sizeof(*s)); // Release socket
@@ -493,8 +545,8 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
cmd->result = -1, cmd->err = ECONNREFUSED;
return true;
}
if (!c->is_connecting) {
cmd->result = 0;
if (s->is_udp || !c->is_connecting) {
cmd->result = 0; // UDP may still resolve ARP; TCP waits for SYN/SYN-ACK
return true;
}
if (s->nonblock) {
@@ -518,11 +570,8 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
return true;
}
if (c->recv.len > 0) {
size_t n = cmd->op_recv.len;
if (c->recv.len < n) n = c->recv.len;
memcpy(cmd->op_recv.buf, c->recv.buf, n);
if (!(cmd->op_recv.flags & MSG_PEEK)) mg_iobuf_del(&c->recv, 0, n);
cmd->result = (int) n;
cmd->result = (int) bsd_recv(s, cmd->op_recv.buf, cmd->op_recv.len,
cmd->op_recv.flags, NULL, NULL);
return true;
}
if (s->err != 0) {
@@ -543,10 +592,17 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
case BSD_OP_SEND:
if ((s = bsd_get(cmd->op_send.fd)) == NULL) {
cmd->result = -1, cmd->err = EBADF;
} else if ((c = s->c) == NULL || s->err != 0 || c->is_closing ||
c->is_draining) {
} else if (s->err != 0 || s->closed ||
((c = s->c) != NULL && (c->is_closing || c->is_draining))) {
cmd->result = -1, cmd->err = EPIPE;
} else if (mg_send(c, cmd->op_send.buf, cmd->op_send.len)) {
} else if (c == NULL || (s->is_udp && !c->is_client)) {
cmd->result = -1, cmd->err = s->is_udp ? EDESTADDRREQ : EPIPE;
} else if (s->is_udp && bsd_udp_too_big(c, cmd->op_send.len)) {
cmd->result = -1, cmd->err = EMSGSIZE;
} else if ((s->is_udp ? bsd_udp_send(c, c->rem.addr.ip4, c->rem.port,
cmd->op_send.buf, cmd->op_send.len)
: mg_send(c, cmd->op_send.buf,
cmd->op_send.len))) {
cmd->result = (int) cmd->op_send.len;
} else if (s->nonblock || (cmd->op_send.flags & MSG_DONTWAIT)) {
cmd->result = -1, cmd->err = EAGAIN;
@@ -561,16 +617,20 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
case BSD_OP_SENDTO:
if ((s = bsd_get(cmd->op_sendto.fd)) == NULL) {
cmd->result = -1, cmd->err = EBADF;
} else if (!s->is_udp) {
cmd->result = -1, cmd->err = EOPNOTSUPP;
} else if ((c = s->c) == NULL || s->err != 0 || c->is_closing ||
c->is_draining) {
cmd->result = -1, cmd->err = EPIPE;
} else if (c->is_client) {
cmd->result = -1, cmd->err = EISCONN;
} else {
const struct sockaddr_in *sa =
(const struct sockaddr_in *) cmd->op_sendto.addr;
c->rem.port = sa->sin_port;
c->rem.addr.ip4 = sa->sin_addr.s_addr;
c->rem.is_ip6 = false;
if (mg_send(c, cmd->op_sendto.buf, cmd->op_sendto.len)) {
if (bsd_udp_too_big(c, cmd->op_sendto.len)) {
cmd->result = -1, cmd->err = EMSGSIZE;
} else if (bsd_udp_send(c, sa->sin_addr.s_addr, sa->sin_port,
cmd->op_sendto.buf, cmd->op_sendto.len)) {
cmd->result = (int) cmd->op_sendto.len;
} else if (s->nonblock || (cmd->op_sendto.flags & MSG_DONTWAIT)) {
cmd->result = -1, cmd->err = EAGAIN;
@@ -587,6 +647,9 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
if ((s = bsd_get(cmd->op_recvfrom.fd)) == NULL) {
cmd->result = -1, cmd->err = EBADF;
return true;
} else if (!s->is_udp) {
cmd->result = -1, cmd->err = EOPNOTSUPP;
return true;
}
if ((c = s->c) == NULL) {
cmd->result = s->closed && s->err == 0 ? 0 : -1;
@@ -594,18 +657,11 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
return true;
}
if (c->recv.len > 0) {
size_t n = cmd->op_recvfrom.len;
if (c->recv.len < n) n = c->recv.len;
memcpy(cmd->op_recvfrom.buf, c->recv.buf, n);
if (!(cmd->op_recvfrom.flags & MSG_PEEK)) mg_iobuf_del(&c->recv, 0, n);
if (cmd->op_recvfrom.src != NULL && cmd->op_recvfrom.addrlen != NULL) {
struct sockaddr_in *sa = (struct sockaddr_in *) cmd->op_recvfrom.src;
sa->sin_family = AF_INET;
sa->sin_port = c->rem.port;
sa->sin_addr.s_addr = c->rem.addr.ip4;
*cmd->op_recvfrom.addrlen = sizeof(*sa);
}
cmd->result = (int) n;
cmd->result = (int) bsd_recv(s, cmd->op_recvfrom.buf,
cmd->op_recvfrom.len,
cmd->op_recvfrom.flags,
cmd->op_recvfrom.src,
cmd->op_recvfrom.addrlen);
return true;
}
if (s->err != 0) {
+27
View File
@@ -55,10 +55,37 @@ void mg_bsd_poll(struct mg_mgr *);
#ifndef EPIPE
#define EPIPE 32
#endif
#ifndef EOPNOTSUPP
#define EOPNOTSUPP 95
#endif
#ifndef EISCONN
#define EISCONN 106
#endif
#ifndef EDESTADDRREQ
#define EDESTADDRREQ 121
#endif
#ifndef EMSGSIZE
#define EMSGSIZE 90
#endif
#ifndef EPROTONOSUPPORT
#define EPROTONOSUPPORT 93
#endif
#if defined(__DCC__)
#pragma pack(1)
#else
#pragma pack(push, 1)
#endif
struct mg_bsd_udp_frame {
uint32_t ip;
uint16_t port, len;
};
#if defined(__DCC__)
#pragma pack(0)
#else
#pragma pack(pop)
#endif
#if MG_ENABLE_BSD_PROTOTYPES
typedef unsigned int socklen_t;
typedef unsigned long nfds_t;
+3 -18
View File
@@ -533,21 +533,6 @@ static void mg_dash_handle_add(struct mg_connection *c, struct mg_dash *dash,
}
}
// Firmware upload callback for mg_http_stream_body(). Uses *p as a flag:
// non-NULL means an OTA session is open and must be closed by mg_ota_end()
static bool otacb(struct mg_http_message *hm, struct mg_str *data, void **p) {
if (hm != NULL) { // Start
if (!mg_ota_begin(hm->body.len)) return false;
*p = (void *) (ptrdiff_t) 1;
return true;
} else if (data != NULL) { // Next chunk
return mg_ota_write(data->buf, data->len);
}
if (*p == NULL) return false; // End. Begin failed, nothing to finish
*p = NULL;
return mg_ota_end();
}
static void mg_serve_fs(struct mg_connection *c, struct mg_http_message *hm,
struct mg_dash_user *u, bool stream) {
struct mg_dash *dash = (struct mg_dash *) c->fn_data;
@@ -594,9 +579,9 @@ void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
mg_serve_fs(c, hm, u, true);
return; // Important - the serve_fs() call can invalidate hm
} else if (mg_match(hm->uri, mg_str("/api/ota"), NULL)) {
// Authenticated above. If streaming started, the stream handler owns
// the connection and c->data, so skip the marker check below
if (mg_http_stream_body(c, hm, otacb, NULL)) return;
// Authenticated above. Streaming owns the connection and c->data.
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
return;
}
if (d->marker != '\0') mg_log_http_req(c, hm);
} else if (ev == MG_EV_HTTP_MSG && d->marker != '\0') {
+1 -1
View File
@@ -365,7 +365,7 @@ static bool st67w6_init(uint8_t *mac) {
}
// 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
MG_VERBOSE(("%.*s", s_at_resp_len, at_resp));
// BT-ENABLED DEPENDENCY
// MODULE DEPENDENCY
+37 -103
View File
@@ -1,66 +1,12 @@
#include "config.h"
#include "health.h"
#if MG_ENABLE_HEALTH
#if MG_ARCH == MG_ARCH_ESP32
// reusable for other platforms, currently ESP32-only
struct mg_health_coredump {
const void *ptr;
size_t poffset;
size_t size;
};
static struct mg_health_coredump s_coredump;
#endif
bool mg_health_coredump_begin(size_t *size) {
#if MG_ARCH == MG_ARCH_ESP32 && CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
const esp_partition_t *p = esp_partition_find_first(
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_COREDUMP, NULL);
size_t address = 0, len = 0;
esp_err_t err = esp_core_dump_image_get(&address, &len);
if (size == NULL || s_coredump.ptr != NULL || err != ESP_OK || p == NULL)
return false;
s_coredump.ptr = p;
s_coredump.poffset = address - p->address;
s_coredump.size = len;
*size = len;
return true;
#else
(void) size;
return false;
#endif
}
bool mg_health_coredump_read(size_t offset, void *buf, size_t *len) {
#if MG_ARCH == MG_ARCH_ESP32 && CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
const esp_partition_t *p = (const esp_partition_t *) s_coredump.ptr;
if (p == NULL || offset > s_coredump.size)
return false;
if (*len > s_coredump.size - offset) *len = s_coredump.size - offset;
return esp_partition_read(p, s_coredump.poffset + offset, buf, *len) == ESP_OK;
#else
(void) offset, (void) buf, (void) len;
return false;
#endif
}
bool mg_health_coredump_end(void) {
#if MG_ARCH == MG_ARCH_ESP32 && CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
bool ok = s_coredump.ptr != NULL;
memset(&s_coredump, 0, sizeof(s_coredump));
return ok;
#else
return false;
#endif
}
// The one health record. Lives in RAM that survives a warm reset, see
// MG_HEALTH_RAM and the .mg_health region in the linker script
struct mg_health mg_health_record MG_HEALTH_RAM;
// STM32 H7, H5, C5 support. Needs the _estack symbol from the linker script
#if defined(STM32H7_DEV_ID) || defined(STM32H5) || defined(STM32C5)
// STM32 H723 support. Needs the _estack symbol from the linker script
#if MG_ENABLE_HEALTH && (defined(STM32H7_DEV_ID) || defined(STM32H5) || defined(STM32C5))
// Fault handler body. Runs in exception context: no printf, no malloc, no
// blocking calls. Copies the unwind registers and raw stack into the health
// record, then resets.
@@ -102,57 +48,52 @@ void BusFault_Handler(void) __attribute__((alias("HardFault_Handler")));
void UsageFault_Handler(void) __attribute__((alias("HardFault_Handler")));
#endif
// Start a new boot: validate the record. Called by MG_HEALTH_INIT()
// Start a new boot: validate the record, count the boot, store the reset
// reason. The current boot moves to prev_* slots first, so the previous
// boot's uptime and reason stay readable. Called by MG_HEALTH_INIT()
void mg_health_init(void) {
char magic[] = MG_HEALTH_MAGIC;
// struct mg_health *h = mg_health_get();
if (!mg_health_valid()) { // First boot, or RAM lost its contents
memset(&mg_health_record, 0, sizeof(mg_health_record));
memcpy(mg_health_record.magic, magic, sizeof(mg_health_record.magic));
}
#if MG_ARCH == MG_ARCH_ESP32
#if CONFIG_IDF_TARGET_ARCH_XTENSA
mg_health_record.arch = (uint16_t) MG_HEALTH_ARCH_XTENSA;
#elif CONFIG_IDF_TARGET_ARCH_RISCV
mg_health_record.arch = (uint16_t) MG_HEALTH_ARCH_RISCV;
#endif
esp_reset_reason_t rr = esp_reset_reason();
mg_health_record.reset_reason = esp2mg(rr);
#if CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH == 0
MG_ERROR(("MG_HEALTH is enabled but CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=n"));
#else
#if 0 && MG_ARCH == MG_ARCH_ESP32 && CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
{
// After a crash, copy the backtrace from the core dump in flash: the same
// addresses the panic handler prints as "Backtrace: PC:SP PC:SP ...".
// The core dump stays in flash, so check the reset reason, otherwise an
// old crash is reported on every boot
esp_core_dump_summary_t *s = calloc(1, sizeof(*s));
static esp_core_dump_summary_t s; // Big on RISC-V, keep it off the stack
esp_reset_reason_t rr = esp_reset_reason();
uint32_t *out = mg_health_record.data;
size_t n = 0;
size_t n = 0, max = MG_HEALTH_DATA_SIZE;
if (rr != ESP_RST_PANIC && rr != ESP_RST_INT_WDT && rr != ESP_RST_TASK_WDT)
return; // Not a crash that went through the panic handler
if (max == 0 || esp_core_dump_get_summary(&s) != ESP_OK) return;
mg_health_record.cpuid = 42; // ESP32
mg_health_record.reset_reason = MG_HEALTH_RESET_FAULT;
memset(out, 0, sizeof(mg_health_record.data));
if (s == NULL) return;
if ((rr != ESP_RST_PANIC && rr != ESP_RST_INT_WDT &&
rr != ESP_RST_TASK_WDT) // Not a crash caught by the panic handler
|| esp_core_dump_get_summary(s) != ESP_OK) {
free(s);
return;
}
out[n++] = s->exc_pc;
memcpy(&out[n], s->exc_task, 16), n += 4;
out[n++] = s.exc_pc;
#if CONFIG_IDF_TARGET_ARCH_XTENSA
out[n++] = s->ex_info.exc_a[0];
out[n++] = s->ex_info.exc_a[1];
size_t len = s->exc_bt_info.depth;
if (len > MG_HEALTH_DATA_SIZE - n) len = (MG_HEALTH_DATA_SIZE - n);
memcpy(&out[n], s->exc_bt_info.bt, 4 * len); // sizeof(uint32_t)
for (uint32_t i = 0; i < s.exc_bt_info.depth && n < max; i++) {
if (s.exc_bt_info.bt[i] != s.exc_pc) out[n++] = s.exc_bt_info.bt[i];
}
#elif CONFIG_IDF_TARGET_ARCH_RISCV
out[n++] = s->ex_info.exc_ra;
out[n++] = s->ex_info.exc_sp;
size_t len = s->exc_bt_info.dump_size / 4 ; // sizeof(uint32_t)
if (len > MG_HEALTH_DATA_SIZE - n) len = (MG_HEALTH_DATA_SIZE - n);
memcpy(&out[n], s->exc_bt_info.stackdump, 4 * len);
// No unwinder: take the return address, then words on the stack that
// point into executable memory (a heuristic, may include stale values)
if (n < max) out[n++] = s.ex_info.ra;
for (uint32_t i = 0; i + 4 <= s.exc_bt_info.dump_size && n < max; i += 4) {
uint32_t w;
memcpy(&w, &s.exc_bt_info.stackdump[i], sizeof(w)); // Byte array
if (esp_ptr_executable((void *) (uintptr_t) w)) out[n++] = w;
}
#endif
}
#endif
free(s);
#endif // CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
#endif // MG_ARCH_ESP32
}
static size_t print_uint32(void (*fn)(char, void *), void *arg, va_list *ap) {
@@ -166,17 +107,11 @@ static size_t print_uint32(void (*fn)(char, void *), void *arg, va_list *ap) {
size_t mg_print_crash_record(void (*fn)(char, void *), void *arg, va_list *ap) {
size_t len = 0;
if (mg_health_valid() &&
(mg_health_record.reset_reason == (uint16_t) MG_HEALTH_RESET_FAULT
#if MG_ARCH == MG_ARCH_ESP32
|| mg_health_record.reset_reason == (uint16_t) MG_HEALTH_RESET_WATCHDOG
#endif
)) {
mg_health_record.reset_reason == MG_HEALTH_RESET_FAULT) {
size_t n = sizeof(mg_health_record.data) / sizeof(mg_health_record.data[0]);
len += mg_xprintf(fn, arg, "{%m:%hhu,%m:%hu,%m:%hu,%m:%u,%m:[%M]}", //
MG_ESC("version"), mg_health_record.magic[3], //
MG_ESC("arch"), mg_health_record.arch, //
MG_ESC("reason"), mg_health_record.reset_reason, //
MG_ESC("cpuid"), mg_health_record.cpuid, //
len += mg_xprintf(fn, arg, "{%m:%hhu,%m:%u,%m:[%M]}", //
MG_ESC("version"), mg_health_record.magic[3], //
MG_ESC("cpuid"), mg_health_record.cpuid, //
MG_ESC("data"), print_uint32, n, mg_health_record.data);
} else {
len += mg_xprintf(fn, arg, "{}");
@@ -184,4 +119,3 @@ size_t mg_print_crash_record(void (*fn)(char, void *), void *arg, va_list *ap) {
(void) ap;
return len;
}
#endif
+5 -80
View File
@@ -5,8 +5,6 @@
#include "arch.h"
#include "printf.h"
#if MG_ENABLE_HEALTH
#ifndef MG_HEALTH_MAGIC
#define MG_HEALTH_MAGIC {'M', 'G', 'H', '3'} // '3' is the layout version
#endif
@@ -15,15 +13,13 @@
#define MG_HEALTH_RAM __attribute__((section(".mg_health")))
#endif
#ifndef MG_HEALTH_DATA_SIZE
#if (defined(STM32H7_DEV_ID) || defined(STM32H5)) || defined(STM32C5)
#if !defined(MG_HEALTH_DATA_SIZE) && (defined(STM32H7_DEV_ID) || defined(STM32H5) || defined(STM32C5))
#define MG_HEALTH_DATA_SIZE 250
#elif MG_ARCH == MG_ARCH_ESP32
#define MG_HEALTH_DATA_SIZE 23
#else
#endif
#ifndef MG_HEALTH_DATA_SIZE
#define MG_HEALTH_DATA_SIZE 10
#endif
#endif // MG_HEALTH_DATA_SIZE
// Places the record in RAM that survives a reset. Define in mongoose_config.h
// to match the linker script, e.g. __attribute__((section(".mg_health")))
@@ -31,13 +27,6 @@
#define MG_HEALTH_RAM
#endif
// CPU core architecture
enum mg_health_arch {
MG_HEALTH_ARCH_ARM = 0,
MG_HEALTH_ARCH_XTENSA,
MG_HEALTH_ARCH_RISCV
};
// Why the device restarted. Hardware usually reports several flags at once,
// so these are ordered by how much they tell you: the most specific cause a
// device reports wins
@@ -54,8 +43,7 @@ enum mg_health_reason {
struct mg_health {
char magic[4]; // MG_HEALTH_MAGIC when the record holds valid data
uint16_t reset_reason; // enum mg_health_reason that started this boot
uint16_t arch; // enum mg_health_arch, core architecture (ARM, etc)
uint32_t reset_reason; // enum mg_health_reason that started this boot
uint32_t cpuid; // ARM CPU ID
uint32_t data[MG_HEALTH_DATA_SIZE];
};
@@ -88,69 +76,6 @@ static inline const char *mg_health_reason_str(int reason) {
}
}
#if MG_ARCH == MG_ARCH_ESP32
#ifndef CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
#define CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH 0
#endif
#ifndef ESP_PARTITION_SUBTYPE_DATA_COREDUMP
#define ESP_PARTITION_SUBTYPE_DATA_COREDUMP 0x03
#endif
// clang-format off
static inline uint16_t esp2mg(esp_reset_reason_t rr) {
switch(rr) {
case ESP_RST_POWERON: return (uint16_t) MG_HEALTH_RESET_POWER;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 2)
case ESP_RST_PWR_GLITCH:
#endif
case ESP_RST_BROWNOUT: return (uint16_t) MG_HEALTH_RESET_BROWNOUT;
case ESP_RST_EXT: return (uint16_t) MG_HEALTH_RESET_PIN;
case ESP_RST_SW: return (uint16_t) MG_HEALTH_RESET_SOFTWARE;
case ESP_RST_DEEPSLEEP: return (uint16_t) MG_HEALTH_RESET_LOWPOWER;
case ESP_RST_INT_WDT:
case ESP_RST_TASK_WDT:
case ESP_RST_WDT: return (uint16_t) MG_HEALTH_RESET_WATCHDOG;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 2)
case ESP_RST_CPU_LOCKUP:
#endif
case ESP_RST_PANIC: return (uint16_t) MG_HEALTH_RESET_FAULT;
default: break;
}
return (uint16_t) MG_HEALTH_RESET_UNKNOWN;
}
// clang-format on
#endif
#define MG_HEALTH_INIT() mg_health_init()
extern size_t mg_print_crash_record(mg_pfn_t, void *, va_list *);
extern void mg_health_init(void);
extern bool mg_health_coredump_begin(size_t *size);
extern bool mg_health_coredump_read(size_t offset, void *buf, size_t *len);
extern bool mg_health_coredump_end(void);
#else
#define MG_HEALTH_INIT()
static inline int mg_health_reason(void) {
return 0;
}
static inline const char *mg_health_reason_str(int reason) {
(void) reason;
return "unknown";
}
static inline size_t mg_print_crash_record(mg_pfn_t fn, void *arg,
va_list *ap) {
(void) ap;
return mg_xprintf(fn, arg, "{}");
}
static inline bool mg_health_coredump_begin(size_t *size) {
(void) size;
return false;
}
static inline bool mg_health_coredump_read(size_t offset, void *buf,
size_t *len) {
(void) offset, (void) buf, (void) len;
return false;
}
static inline bool mg_health_coredump_end(void) {
return false;
}
#endif
+43 -33
View File
@@ -879,8 +879,8 @@ void mg_http_serve_upload(struct mg_connection *c, struct mg_http_message *hm,
fs->rm(path); // MG_FS_WRITE appends, not truncates: drop any old file
if ((fd = mg_fs_open(fs, path, MG_FS_WRITE)) == NULL) {
MG_ERROR(("Cannot open %s for upload", path));
} else if (!mg_http_stream_body(c, hm, serve_upload_cb, fd)) {
mg_fs_close(fd);
} else {
mg_http_stream_body(c, hm, serve_upload_cb, fd);
}
}
}
@@ -1014,6 +1014,7 @@ struct mg_upload_state {
size_t received; // Number of body bytes received so far
void *user_data; // Passed to cb, free for cb to use
bool failed; // Set when cb returned false
bool finished; // End callback has been called
};
// Event handler installed by mg_http_stream_body(). Feeds the body to the
@@ -1021,18 +1022,18 @@ struct mg_upload_state {
// handler: the connection is closed after the reply.
static void mg_stream_handler(struct mg_connection *c, int ev, void *ev_data) {
struct mg_upload_state *us = (struct mg_upload_state *) c->data;
bool (*fn)(struct mg_http_message *, struct mg_str *, void **) =
bool (*fn)(struct mg_http_message *, struct mg_str *, void **) =
(bool (*)(struct mg_http_message *, struct mg_str *, void **))
(ptrdiff_t) c->fn_data;
(void) ev_data;
if (ev == MG_EV_CLOSE || ev == MG_EV_ERROR) {
if (us->received < us->expected) {
if (!us->finished) {
// Connection dropped mid-upload. Let cb clean up, e.g. call mg_ota_end()
us->received = us->expected; // MG_EV_ERROR is followed by MG_EV_CLOSE
us->finished = true; // MG_EV_ERROR is followed by MG_EV_CLOSE
fn(NULL, NULL, &us->user_data);
}
} else if (ev == MG_EV_READ && !c->is_draining) {
if (c->recv.len > 0 && us->received < us->expected) {
} else if (ev == MG_EV_READ && !c->is_draining && !us->finished) {
if (!us->failed && c->recv.len > 0 && us->received < us->expected) {
// Got some uploaded data. Feed by 512-byte aligned chunks, for OTA
size_t left = us->expected - us->received, r = c->recv.len;
size_t aligned = r < left ? MG_ROUND_DOWN(r, 512) : left;
@@ -1043,10 +1044,13 @@ static void mg_stream_handler(struct mg_connection *c, int ev, void *ev_data) {
us->received += aligned;
mg_iobuf_del(&c->recv, 0, aligned); // Shift the unprocessed tail
}
if (us->received >= us->expected) {
if (!us->finished && (us->failed || us->received >= us->expected)) {
// Finished upload. Always call cb, even after error, so it can clean up
us->finished = true;
us->failed |= !fn(NULL, NULL, &us->user_data);
if (!c->is_client) {
if (c->is_client && us->failed) {
mg_error(c, "Body streaming failed");
} else if (!c->is_client) {
mg_http_reply(c, us->failed ? 500 : 200, NULL, "%zu %s\n",
us->received, us->failed ? "fail" : "ok");
}
@@ -1055,48 +1059,54 @@ static void mg_stream_handler(struct mg_connection *c, int ev, void *ev_data) {
}
}
bool mg_http_stream_body(struct mg_connection *c, struct mg_http_message *hm,
void mg_http_stream_body(struct mg_connection *c, struct mg_http_message *hm,
bool (*cb)(struct mg_http_message *, struct mg_str *,
void **user_data),
void **),
void *user_data) {
struct mg_upload_state *us = (struct mg_upload_state *) c->data;
struct mg_str *te = mg_http_get_header(hm, "Transfer-Encoding");
bool handled = false;
int code = 0;
size_t head_len = hm->head.len;
// Catch upload requests early, without buffering whole body. Called on the
// MG_EV_HTTP_HDRS event: we've received all HTTP headers but not necessarily
// the full HTTP body
if (hm->body.len == (size_t) ~0 ||
(te != NULL && mg_strcasecmp(*te, mg_str("chunked")) == 0)) {
// Unknown length or chunked, cannot stream. Leave it to the caller
} else if (!c->is_client &&
mg_strcmp(hm->method, mg_str("POST")) != 0 &&
mg_strcmp(hm->method, mg_str("PUT")) != 0) {
// Not an upload request. Leave it to the caller
if (!c->is_client && mg_strcmp(hm->method, mg_str("POST")) != 0 &&
mg_strcmp(hm->method, mg_str("PUT")) != 0) {
code = 405;
} else if (mg_http_get_header(hm, "Transfer-Encoding") != NULL) {
code = 400;
} else if (hm->body.len == (size_t) ~0 ||
(!c->is_client && mg_http_get_header(hm, "Content-Length") == NULL)) {
code = 411;
} else if (sizeof(*us) > sizeof(c->data)) {
if (!c->is_client) { // Nobody to reply to on a client connection
c->pfn = NULL;
mg_http_reply(c, 500, NULL, "data too small\n");
c->is_draining = 1;
cb(NULL, NULL, &user_data); // Let cb release user_data, start skipped
handled = true;
code = 500;
}
c->pfn = NULL; // Take ownership even when validation fails
if (code != 0) {
c->fn = NULL;
c->fn_data = NULL;
mg_iobuf_del(&c->recv, 0, c->recv.len);
cb(NULL, NULL, &user_data); // Release resources even if start was skipped
if (c->is_client) {
mg_error(c, "%s", mg_http_status_code_str(code));
} else {
mg_http_reply(c, code, code == 405 ? "Allow: POST, PUT\r\n" : NULL, "");
}
c->is_draining = 1;
} else {
c->pfn = NULL; // Silence HTTP protocol handler
memset(us, 0, sizeof(*us));
us->expected = hm->body.len; // Store number of bytes we expect
us->user_data = user_data;
c->fn = mg_stream_handler;
c->fn_data = (void *) (ptrdiff_t) cb;
us->failed = !cb(hm, NULL, &us->user_data); // Call before deleting headers
if (mg_http_get_header(hm, "Expect") != NULL) {
if (!c->is_client && !us->failed && !us->finished &&
mg_http_get_header(hm, "Expect") != NULL) {
mg_http_reply(c, 100, NULL, ""); // If curl expects, we continue
}
mg_iobuf_del(&c->recv, 0, hm->head.len); // Delete HTTP headers
c->fn = mg_stream_handler; // We own the connection from now
c->fn_data = (void *) (ptrdiff_t) cb;
mg_iobuf_del(&c->recv, 0, head_len); // Delete HTTP headers
mg_call(c, MG_EV_READ, &c->recv.len); // Process the body bytes we have
handled = true;
}
return handled;
}
static bool is_hex_digit(int c) {
+14 -11
View File
@@ -205,29 +205,32 @@ int mg_http_status(const struct mg_http_message *hm);
// Streams a POST/PUT body to a callback as it arrives, without buffering it.
// Call it from an MG_EV_HTTP_HDRS handler, after matching the request, e.g.
// with mg_match(). Returns true if it took over the connection, false if the
// request is not POST/PUT, is chunked, or has no Content-Length.
// with mg_match(). Always takes over the connection. Invalid requests receive
// an HTTP error; client-side failures call mg_error(). Replies and connection
// draining are handled internally, including callback failures.
//
// Callback upload_fn(hm, data, user_data) is called:
// - at start: hm is set. Open the file, set *user_data.
// - per chunk: data is set, 512 byte aligned except the last. Not called
// again after a failure.
// - at end: both NULL. Always called once, also when the connection drops
// early, so release resources here. A truncated body looks like a complete
// one: validate before committing.
// Return true on success, false on error.
// - at end: both NULL. Always called exactly once, including validation
// failures before start and connections that drop early. Release resources
// here. A truncated body looks like a complete one: validate before
// committing.
// The callback returns true on success, false on error. A failure stops
// streaming immediately and calls the end callback before closing.
// Example:
// if (ev == MG_EV_HTTP_HDRS && mg_match(hm->uri, mg_str("/fs/*"), NULL) {
// mg_http_stream_body(c, hm, upload, NULL));
// if (ev == MG_EV_HTTP_HDRS && mg_match(hm->uri, mg_str("/fs/*"), NULL)) {
// mg_http_stream_body(c, hm, upload, NULL);
// }
// Full example:
// tutorials/http/file-upload-single-post
// Related APIs:
// mg_http_listen(), mg_http_next_multipart()
// Notes:
// After it returns true, this function hijacks the connection. Existing event
// handler not called, hm is invalid, c->data and c->fn_data are modified
bool mg_http_stream_body(struct mg_connection *c, struct mg_http_message *hm,
// The existing event handler is no longer called. hm is invalid after this
// call; c->data and c->fn_data are reserved for streaming state.
void mg_http_stream_body(struct mg_connection *c, struct mg_http_message *hm,
bool (*upload_fn)(struct mg_http_message *,
struct mg_str *, void **user_data),
void *user_data);
+2 -2
View File
@@ -11,7 +11,7 @@
#define MGI_802154_DATA (125 - MGI_802154_HDR)
size_t mg_l2_802154_tx(struct mg_tcpip_if *ifp, const void *buf, size_t len,
size_t (*send)(const uint8_t *, size_t,
size_t (*send_fn)(const uint8_t *, size_t,
struct mg_tcpip_if *)) {
struct mg_l2_802154_data *d =
(struct mg_l2_802154_data *) ifp->driver_data;
@@ -29,7 +29,7 @@ size_t mg_l2_802154_tx(struct mg_tcpip_if *ifp, const void *buf, size_t len,
f[12] = m[2], f[13] = m[1], f[14] = m[0]; // Little endian EUI-64
f[15] = d->tag, f[16] = (uint8_t) ((i << 4) | last);
memcpy(f + MGI_802154_HDR, p + i * MGI_802154_DATA, n);
if (send(f, MGI_802154_HDR + n, ifp) == 0) return 0;
if (send_fn(f, MGI_802154_HDR + n, ifp) == 0) return 0;
}
return len;
}
-33
View File
@@ -89,7 +89,6 @@ static void mg_mdash_fn(struct mg_connection *c, int ev, void *ev_data) {
}
mg_iobuf_free(&io);
} else if (ev == MG_EV_CLOSE) {
mg_health_coredump_end();
mg_mdash_conn = NULL;
MG_INFO(("disconnected, ram %lu", 0));
}
@@ -147,35 +146,6 @@ static void mg_mdash_rpc_ota_end(struct mg_rpc_req *r) {
// MG_ESC("percent"));
}
static void rpc_coredump_begin(struct mg_rpc_req *r) {
size_t size = 0;
if (mg_health_coredump_begin(&size)) {
mg_rpc_ok(r, "{%m:%llu}", MG_ESC("size"), (uint64_t) size);
} else {
mg_rpc_err(r, 500, "%m", MG_ESC("failed"));
}
}
static void rpc_coredump_read(struct mg_rpc_req *r) {
uint8_t buf[768];
size_t len = sizeof(buf);
size_t offset = (size_t) mg_json_get_long(r->frame, "$.params.offset", -1);
if (mg_health_coredump_read(offset, buf, &len)) {
mg_rpc_ok(r, "{%m:\"%M\"}", MG_ESC("data"), mg_print_base64,
(int) len, buf);
} else {
mg_rpc_err(r, 500, "%m", MG_ESC("failed"));
}
}
static void rpc_coredump_end(struct mg_rpc_req *r) {
if (mg_health_coredump_end()) {
mg_rpc_ok(r, "true");
} else {
mg_rpc_err(r, 500, "%m", MG_ESC("failed"));
}
}
void mg_mdash_init(struct mg_mgr *mgr) {
MG_INFO(("Initialising mdash agent, key [%s]", MG_MDASH_KEY));
mg_rpc_add(&mgr->rpcs, mg_str("RPC.List"), mg_rpc_list, NULL);
@@ -183,9 +153,6 @@ void mg_mdash_init(struct mg_mgr *mgr) {
mg_rpc_add(&mgr->rpcs, mg_str("OTA.Begin"), mg_mdash_rpc_ota_begin, NULL);
mg_rpc_add(&mgr->rpcs, mg_str("OTA.Write"), mg_mdash_rpc_ota_write, NULL);
mg_rpc_add(&mgr->rpcs, mg_str("OTA.End"), mg_mdash_rpc_ota_end, NULL);
mg_rpc_add(&mgr->rpcs, mg_str("CoreDump.Begin"), rpc_coredump_begin, NULL);
mg_rpc_add(&mgr->rpcs, mg_str("CoreDump.Read"), rpc_coredump_read, NULL);
mg_rpc_add(&mgr->rpcs, mg_str("CoreDump.End"), rpc_coredump_end, NULL);
}
void mg_mdash_poll(struct mg_mgr *mgr) {
+125 -23
View File
@@ -609,6 +609,36 @@ 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);
#if MG_ENABLE_BSD_SOCKETS
static uint8_t *sendto_cache_find(struct mg_tcpip_if *ifp, uint32_t ip) {
struct mg_tcpip_sendto_entry *a;
for (a = ifp->sendto; a < ifp->sendto + MG_TCPIP_SENDTO_CACHE_SIZE; a++) {
if (a->ip == ip) return (uint8_t *) &a->addr;
}
return NULL;
}
static void sendto_cache_add(struct mg_tcpip_if *ifp, uint32_t ip,
const uint8_t *addr) {
struct mg_tcpip_sendto_entry *a;
for (a = ifp->sendto; a < ifp->sendto + MG_TCPIP_SENDTO_CACHE_SIZE; a++) {
if (a->ip == ip) return; // already cached, skip
if (a->ip == 0) break; // not present, first empty slot
}
if (a == ifp->sendto + MG_TCPIP_SENDTO_CACHE_SIZE) { // no room
memmove(ifp->sendto, ifp->sendto + 1,
sizeof(ifp->sendto[0]) * (MG_TCPIP_SENDTO_CACHE_SIZE - 1));
a--; // make room and point to last entry (just made)
}
a->ip = ip; // populate this entry
memcpy(&a->addr, addr, sizeof(a->addr));
}
static void sendto_cache_clear(struct mg_tcpip_if *ifp) {
memset(ifp->sendto, 0, sizeof(ifp->sendto));
}
#endif
// 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) {
@@ -1214,30 +1244,57 @@ static void proto_error(struct mg_connection *c, bool r, const char *f, ...) {
static bool rx_udp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
struct mg_connection *c = getpeer(ifp->mgr, pkt, true);
struct connstate *s;
struct mg_addr rem;
uint8_t *l2addr;
size_t len = pkt->pay.len;
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;
if (c->is_bsd && c->is_client &&
(c->rem.port != pkt->udp->sport ||
!MG_IP6MATCH(c->rem.addr.ip6, pkt->ip6->src)))
return true;
rem.addr.ip6[0] = pkt->ip6->src[0],
rem.addr.ip6[1] = pkt->ip6->src[1], rem.is_ip6 = true;
} else
#endif
{
if (!udpcsum_ok(pkt->ip, pkt->udp)) return false;
c->rem.addr.ip4 = pkt->ip->src;
if (c->is_bsd && c->is_client &&
(c->rem.port != pkt->udp->sport || c->rem.addr.ip4 != pkt->ip->src))
return true;
rem.addr.ip4 = pkt->ip->src, rem.is_ip6 = false;
}
if ((l2addr = get_return_l2addr(ifp, &c->rem, true, pkt)) == NULL)
rem.port = pkt->udp->sport;
if (!c->is_bsd || c->is_client) c->rem = rem;
if ((l2addr = get_return_l2addr(ifp, &rem, true, pkt)) == NULL)
return false; // safety net for lousy networks
memcpy(s->mac, l2addr, sizeof(s->mac));
if (!c->is_bsd || c->is_client) memcpy(s->mac, l2addr, sizeof(s->mac));
#if MG_ENABLE_BSD_SOCKETS
if (c->is_bsd && !rem.is_ip6 && MG_IPISLOCAL(ifp, rem.addr.ip4))
sendto_cache_add(ifp, rem.addr.ip4, l2addr);
#endif
#if MG_ENABLE_BSD_SOCKETS
if (c->is_bsd) len += sizeof(struct mg_bsd_udp_frame);
#endif
if (c->recv.len >= MG_MAX_RECV_SIZE) {
proto_error(c, false, "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)) {
} else if (c->recv.size - c->recv.len < len &&
!mg_iobuf_resize(&c->recv, c->recv.len + len)) {
proto_error(c, false, "oom");
} else {
#if MG_ENABLE_BSD_SOCKETS
if (c->is_bsd) {
struct mg_bsd_udp_frame *h =
(struct mg_bsd_udp_frame *) &c->recv.buf[c->recv.len];
h->ip = rem.addr.ip4;
h->port = rem.port;
h->len = (uint16_t) pkt->pay.len;
c->recv.len += sizeof(*h);
}
#endif
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);
@@ -2290,6 +2347,9 @@ void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) {
static void close_conn(struct mg_connection *c) {
struct connstate *s = (struct connstate *) (c + 1);
#if MG_ENABLE_BSD_SOCKETS
if (c->is_bsd && c->is_udp) sendto_cache_clear(c->mgr->ifp);
#endif
mg_iobuf_free(&s->txq);
mg_close_conn(c);
}
@@ -2311,6 +2371,11 @@ static void send_syn(struct mg_connection *c) {
static void l2addr_resolved(struct mg_connection *c) {
if (c->is_udp) {
c->is_connecting = 0;
#if MG_ENABLE_BSD_SOCKETS
if (c->is_bsd)
sendto_cache_add(c->mgr->ifp, c->rem.addr.ip4,
((struct connstate *) (c + 1))->mac);
#endif
mg_call(c, MG_EV_CONNECT, NULL);
} else {
send_syn(c);
@@ -2325,20 +2390,25 @@ void mg_connect_resolved(struct mg_connection *c) {
s->retransmit = ifp->enable_tcp_retransmit;
s->txq.align = MG_TCPIP_TXQ_ALIGN;
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 (c->loc.port == 0) {
if (ifp->eport < MG_EPHEMERAL_PORT_BASE)
ifp->eport = MG_EPHEMERAL_PORT_BASE;
c->loc.port = mg_htons(ifp->eport++);
} // otherwise retain an existing bound port
#if MG_ENABLE_IPV6
if (c->rem.is_ip6) {
if (c->rem.addr.ip6[0] == ifp->ip6ll[0]) { // same local LAN, use ll
if (c->loc.addr.ip6[0] == 0 && c->loc.addr.ip6[1] == 0 &&
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
} else if (c->loc.addr.ip6[0] == 0 && c->loc.addr.ip6[1] == 0) {
// Use global address
c->loc.addr.ip6[0] = ifp->ip6[0], c->loc.addr.ip6[1] = ifp->ip6[1];
}
} // otherwise retain an existing bound address
c->loc.is_ip6 = true;
} else
#endif
{
c->loc.addr.ip4 = ifp->ip;
if (c->loc.addr.ip4 == 0) 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));
@@ -2369,11 +2439,20 @@ void mg_connect_resolved(struct mg_connection *c) {
uint32_t rem_ip = c->rem.addr.ip4;
if (ifp->ip && MG_IPISLOCAL(ifp, rem_ip) &&
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;
// We're in the same LAN
#if MG_ENABLE_BSD_SOCKETS
if (c->is_bsd && c->is_udp && // see if we already have L2 data
(l2addr = sendto_cache_find(ifp, rem_ip)) != NULL) {
memcpy(s->mac, l2addr, sizeof(s->mac));
l2addr_resolved(c);
} else
#endif
{ // 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) {
memcpy(s->mac, ifp->gwmac, sizeof(s->mac));
l2addr_resolved(c);
@@ -2393,13 +2472,36 @@ bool mg_open_listener(struct mg_connection *c, const char *url) {
}
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);
long len;
size_t off = 0;
bool empty = false;
#if MG_ENABLE_BSD_SOCKETS
struct mg_bsd_udp_frame *h;
if (c->is_bsd && c->is_udp) {
if (c->is_connecting) return;
h = (struct mg_bsd_udp_frame *) c->send.buf;
assert(c->send.len >= sizeof(*h));
assert(h->len <= c->send.len - sizeof(*h));
c->rem.port = h->port;
c->rem.is_ip6 = false;
if (!c->is_client && c->rem.addr.ip4 != h->ip) {
c->rem.addr.ip4 = h->ip;
mg_connect_resolved(c);
if (c->is_connecting) return;
}
len = mg_io_send(c, h + 1, h->len);
off = sizeof(*h), empty = h->len == 0;
} else
#endif
{
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) {
proto_error(c, false, "tx err");
} else if (len > 0) {
mg_iobuf_del(&c->send, 0, (size_t) len);
} else if (len > 0 || empty) {
mg_iobuf_del(&c->send, 0, off + (size_t) len);
mg_call(c, MG_EV_WRITE, &len);
}
}
+14
View File
@@ -26,6 +26,17 @@ struct mg_tcpip_driver {
typedef void (*mg_tcpip_event_handler_t)(struct mg_tcpip_if *ifp, int ev,
void *ev_data);
#if MG_ENABLE_BSD_SOCKETS
#ifndef MG_TCPIP_SENDTO_CACHE_SIZE
#define MG_TCPIP_SENDTO_CACHE_SIZE 4
#endif
struct mg_tcpip_sendto_entry {
uint32_t ip;
struct mg_l2addr addr;
};
#endif
// 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.
@@ -88,6 +99,9 @@ struct mg_tcpip_if {
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)
#if MG_ENABLE_BSD_SOCKETS
struct mg_tcpip_sendto_entry sendto[MG_TCPIP_SENDTO_CACHE_SIZE]; // Local IPv4 neighbors
#endif
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
+16 -3
View File
@@ -208,9 +208,7 @@ static void s_firmware_fn(struct mg_connection *c, int ev, void *ev_data) {
(unsigned long) hm->body.len, (unsigned long) s_ota->size);
} else {
MG_DEBUG(("Beginning OTA (%lu bytes)", (unsigned long) s_ota->size));
if (!mg_http_stream_body(c, hm, s_firmware_cb, NULL)) {
mg_error(c, "Cannot stream firmware");
}
mg_http_stream_body(c, hm, s_firmware_cb, NULL);
}
} else if (ev == MG_EV_ERROR) {
s_ota->fn((char *) ev_data);
@@ -243,6 +241,21 @@ void mg_ota_url_check(struct mg_mgr *mgr, const char *json_url,
}
}
// Uses *p as a flag: non-NULL means an OTA session is open and must be
// closed by mg_ota_end()
bool mg_http_ota_cb(struct mg_http_message *hm, struct mg_str *data, void **p) {
if (hm != NULL) { // Start
if (!mg_ota_begin(hm->body.len)) return false;
*p = (void *) (ptrdiff_t) 1;
return true;
} else if (data != NULL) { // Next chunk
return mg_ota_write(data->buf, data->len);
}
if (*p == NULL) return false; // End. Begin failed, nothing to finish
*p = NULL;
return mg_ota_end();
}
void mg_ota_poll(struct mg_mgr *mgr) {
static uint64_t t = 5000; // Fire first time 5 sec after boot
+2 -3
View File
@@ -80,9 +80,8 @@ bool mg_ota_end(void);
// ```c
// if (ev == MG_EV_HTTP_HDRS) {
// struct mg_http_message *hm = (struct mg_http_message *) ev_data;
// if (mg_match(hm->uri, mg_str("/ota"), NULL) &&
// !mg_http_stream_body(c, hm, mg_http_ota_cb, NULL)) {
// mg_http_reply(c, 400, "", "POST firmware with Content-Length\n");
// if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
// mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
// }
// }
// ```
+5 -1
View File
@@ -299,7 +299,11 @@ static long recv_raw(struct mg_connection *c, void *buf, size_t len) {
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));
if (n > 0) {
struct mg_addr rem;
tomgaddr(&usa, &rem, slen != sizeof(usa.sin));
c->rem = rem;
}
} else {
n = recv(FD(c), (char *) buf, len, MSG_NONBLOCKING);
}
+3 -2
View File
@@ -261,8 +261,9 @@ uint64_t mg_ntohll(uint64_t net) {
}
void mg_delayms(unsigned int ms) {
uint64_t to = mg_millis() + ms + 1;
while (mg_millis() < to) (void) 0;
uint64_t now, to = mg_millis() + ms + 1;
while ((now = mg_millis()) < to)
if (now + ms + 1 < to) to = now + ms + 1;
}
#if MG_ENABLE_CUSTOM_CALLOC
+9 -6
View File
@@ -13,7 +13,7 @@ else
C_WARN ?= -Wno-deprecated
endif
.PHONY: all bsd bsd_test bsd_client bsd_nortos bsd_nortos_test shm_tap_bridge \
.PHONY: all bsd bsd_test bsd_client bsd_udp bsd_nortos bsd_nortos_test shm_tap_bridge \
shm_port_bridge bsd_s390 bsd_nortos_s390 clean
TEST_SOURCES = bsd_shm_test.c
@@ -47,11 +47,11 @@ DOCKER ?= $(DOCKER_BIN) run --platform linux/amd64 --net=host --rm $(ENV) -v $(R
CFLAGS_MONGOOSE += -DMG_ARCH=MG_ARCH_CUSTOM
all bsd: shm_tap_bridge bsd_client FreeRTOS-Kernel bsd_test
all bsd: shm_tap_bridge bsd_client bsd_udp FreeRTOS-Kernel bsd_test
ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./bsd_test $(ARGS)
# also
bsd_nortos: shm_tap_bridge bsd_client bsd_nortos_test
bsd_nortos: shm_tap_bridge bsd_client bsd_udp bsd_nortos_test
ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./bsd_nortos_test $(ARGS)
# requires shm_tap_bridge to be running
@@ -70,6 +70,9 @@ bsd_nortos_test:
bsd_client: bsd_client.c
$(CC) bsd_client.c -pthread -o $@
bsd_udp: bsd_udp.c
$(CC) bsd_udp.c -o $@
# bridge shm queue <--> TAP interface
shm_tap_bridge: shm_tap_bridge.c shm_queue.h
$(CC) shm_tap_bridge.c -pthread -o $@
@@ -79,7 +82,7 @@ bsd_s390: CFLAGS_MONGOOSE += -DMG_ENABLE_BSD_PROTOTYPES=0
bsd_s390: ASAN=
bsd_s390: CC = $(DOCKER) mdashnet/s390 cc
bsd_s390: RUN = $(DOCKER) --init mdashnet/s390
bsd_s390: shm_port_bridge bsd_client FreeRTOS-Kernel bsd_test
bsd_s390: shm_port_bridge bsd_client bsd_udp FreeRTOS-Kernel bsd_test
$(RUN) sh -c './shm_port_bridge & pid=$$!; sleep 1; ./bsd_test; rc=$$?; kill $$pid >/dev/null 2>&1; wait $$pid >/dev/null 2>&1; exit $$rc'
#also
@@ -87,14 +90,14 @@ bsd_nortos_s390: CFLAGS_MONGOOSE += -DMG_ENABLE_BSD_PROTOTYPES=0
bsd_nortos_s390: ASAN=
bsd_nortos_s390: CC = $(DOCKER) mdashnet/s390 cc
bsd_nortos_s390: RUN = $(DOCKER) --init mdashnet/s390
bsd_nortos_s390: shm_port_bridge bsd_client bsd_nortos_test
bsd_nortos_s390: shm_port_bridge bsd_client bsd_udp bsd_nortos_test
$(RUN) sh -c './shm_port_bridge & pid=$$!; sleep 1; ./bsd_nortos_test; rc=$$?; kill $$pid >/dev/null 2>&1; wait $$pid >/dev/null 2>&1; exit $$rc'
shm_port_bridge: shm_port_bridge.c shm_queue.h
$(CC) shm_port_bridge.c $(CFLAGS) -pthread -o $@
clean: # Cleanup. Delete built program and all build artifacts
rm -rf bsd_test bsd_client shm_port_bridge *.o *.obj FreeRTOS-Kernel
rm -rf bsd_test bsd_client bsd_udp shm_port_bridge *.o *.obj FreeRTOS-Kernel
FreeRTOS-Kernel: # FreeRTOS sources
git clone -q -c advice.detachedHead=false --depth 1 -b V11.2.0 https://github.com/FreeRTOS/FreeRTOS-Kernel $@
+55
View File
@@ -0,0 +1,55 @@
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
#define PORT 1235
int main(int argc, char *argv[]) {
struct sockaddr_in sa;
struct timeval timeout;
char buf[8];
ssize_t n;
int fd, rogue;
if (argc != 2) return 1;
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(PORT);
if (inet_pton(AF_INET, argv[1], &sa.sin_addr) != 1) return 1;
timeout.tv_sec = 5, timeout.tv_usec = 0;
if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0 ||
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) != 0 ||
connect(fd, (struct sockaddr *) &sa, sizeof(sa)) != 0 ||
send(fd, "hello", 5, 0) != 5 || recv(fd, buf, sizeof(buf), 0) != 5 ||
memcmp(buf, "ready", 5) != 0)
goto fail;
if ((rogue = socket(AF_INET, SOCK_DGRAM, 0)) < 0 ||
setsockopt(rogue, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) !=
0 ||
send(fd, "one", 3, 0) != 3 ||
sendto(rogue, "two2", 4, 0, (struct sockaddr *) &sa, sizeof(sa)) != 4 ||
recv(fd, buf, sizeof(buf), 0) != 4 || memcmp(buf, "peer", 4) != 0 ||
recv(rogue, buf, sizeof(buf), 0) != 5 || memcmp(buf, "rogue", 5) != 0 ||
send(fd, "truncate", 8, 0) != 8 || send(fd, "peek", 4, 0) != 4 ||
recv(fd, buf, sizeof(buf), 0) != 4 || memcmp(buf, "more", 4) != 0 ||
recv(fd, buf, sizeof(buf), 0) != 3 || memcmp(buf, "one", 3) != 0 ||
recv(fd, buf, sizeof(buf), 0) != 4 || memcmp(buf, "two2", 4) != 0 ||
recv(fd, buf, sizeof(buf), 0) != 0 ||
sendto(rogue, "rogue", 5, 0, (struct sockaddr *) &sa, sizeof(sa)) != 5 ||
recv(fd, buf, sizeof(buf), 0) != 7 || memcmp(buf, "checked", 7) != 0 ||
send(fd, "echo", 4, 0) != 4 || recv(fd, buf, sizeof(buf), 0) != 4 ||
memcmp(buf, "echo", 4) != 0)
goto fail_close_rogue;
close(rogue);
close(fd);
return 0;
fail_close_rogue:
close(rogue);
fail:
close(fd);
return 1;
}
+230 -9
View File
@@ -33,6 +33,175 @@ static int s_abort = 0;
#define DASHBOARD(x) \
printf("HEALTH_DASHBOARD\t\"%s\": %s,\n", x, s_error ? "false" : "true")
#define UDP_PORT 1235
#if MG_ENABLE_FREERTOS
static volatile bool s_tx_held, s_tx_waits;
#endif
static int open_udp(void) {
struct sockaddr_in sa;
char buf;
int fd;
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(UDP_PORT);
sa.sin_addr.s_addr = INADDR_ANY;
ASSERT((fd = socket(AF_INET, SOCK_DGRAM, 0)) >= 0);
ASSERT(bind(fd, (struct sockaddr *) &sa, sizeof(sa)) == 0);
ASSERT(send(fd, &buf, sizeof(buf), 0) < 0 && errno == EDESTADDRREQ);
ASSERT(fcntl(fd, F_SETFL, O_NONBLOCK) == 0);
return fd;
}
static void check_udp_oversize(struct mg_mgr *mgr, int fd,
const struct sockaddr *dst, socklen_t dstlen) {
static char buf[1600];
size_t len = mgr->ifp->mtu + 1;
ASSERT(len <= sizeof(buf));
if (dst == NULL) {
ASSERT(send(fd, buf, len, 0) < 0 && errno == EMSGSIZE);
} else {
ASSERT(sendto(fd, buf, len, 0, dst, dstlen) < 0 && errno == EMSGSIZE);
}
}
static void udp_exchange(struct mg_mgr *mgr, int fd,
void (*progress)(struct mg_mgr *)) {
struct sockaddr_in peer, other, rogue = {0};
socklen_t peerlen, otherlen;
uint64_t deadline;
struct timeval zero = {0, 0};
struct pollfd p;
fd_set r, w, e;
bool one;
char buf[8];
ssize_t n;
for (;;) {
peerlen = sizeof(peer);
n = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *) &peer, &peerlen);
if (n >= 0) break;
ASSERT(errno == EAGAIN);
progress(mgr);
}
ASSERT(n == 5 && memcmp(buf, "hello", 5) == 0);
ASSERT(sendto(fd, "ready", 5, 0, (struct sockaddr *) &peer, peerlen) ==
5);
deadline = mg_millis() + 100;
while (mg_millis() < deadline) progress(mgr); // Queue both UDP datagrams
do {
otherlen = sizeof(other);
n = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *) &other,
&otherlen);
if (n < 0) {
ASSERT(errno == EAGAIN);
progress(mgr);
}
} while (n < 0);
one = n == 3;
if (one) {
ASSERT(memcmp(buf, "one", 3) == 0 && other.sin_port == peer.sin_port);
} else {
ASSERT(n == 4 && memcmp(buf, "two2", 4) == 0 &&
other.sin_port != peer.sin_port);
}
if (other.sin_port != peer.sin_port) rogue = other;
do {
otherlen = sizeof(other);
n = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *) &other,
&otherlen);
if (n < 0) {
ASSERT(errno == EAGAIN);
progress(mgr);
}
} while (n < 0);
if (one) {
ASSERT(n == 4 && memcmp(buf, "two2", 4) == 0 &&
other.sin_port != peer.sin_port);
} else {
ASSERT(n == 3 && memcmp(buf, "one", 3) == 0 &&
other.sin_port == peer.sin_port);
}
if (other.sin_port != peer.sin_port) rogue = other;
check_udp_oversize(mgr, fd, (struct sockaddr *) &peer, peerlen);
ASSERT(sendto(fd, "peer", 4, 0, (struct sockaddr *) &peer, peerlen) == 4);
ASSERT(sendto(fd, "rogue", 5, 0, (struct sockaddr *) &rogue,
sizeof(rogue)) == 5);
deadline = mg_millis() + 100;
while (mg_millis() < deadline) progress(mgr);
ASSERT(connect(fd, (struct sockaddr *) &peer, peerlen) == 0);
ASSERT(sendto(fd, "no", 2, 0, (struct sockaddr *) &peer, peerlen) < 0 &&
errno == EISCONN);
check_udp_oversize(mgr, fd, NULL, 0);
ASSERT(send(fd, "more", 4, 0) == 4);
deadline = mg_millis() + 100;
while (mg_millis() < deadline) progress(mgr); // Queue both UDP datagrams
do {
p.fd = fd, p.events = POLLIN | POLLOUT, p.revents = 0;
ASSERT(poll(&p, 1, 0) == 1);
if (!(p.revents & POLLIN)) progress(mgr);
} while (!(p.revents & POLLIN));
ASSERT(p.revents == (POLLIN | POLLOUT));
FD_ZERO(&r);
FD_ZERO(&w);
FD_ZERO(&e);
FD_SET(fd, &r);
FD_SET(fd, &w);
FD_SET(fd, &e);
ASSERT(select(fd + 1, &r, &w, &e, &zero) == 1);
ASSERT(FD_ISSET(fd, &r) && FD_ISSET(fd, &w) && !FD_ISSET(fd, &e));
do {
n = recv(fd, buf, 3, 0);
if (n < 0) {
ASSERT(errno == EAGAIN);
progress(mgr);
}
} while (n < 0);
ASSERT(n == 3 && memcmp(buf, "tru", 3) == 0);
do {
n = recv(fd, buf, sizeof(buf), MSG_PEEK);
if (n < 0) {
ASSERT(errno == EAGAIN);
progress(mgr);
}
} while (n < 0);
ASSERT(n == 4 && memcmp(buf, "peek", 4) == 0);
ASSERT(recv(fd, buf, sizeof(buf), 0) == 4 && memcmp(buf, "peek", 4) == 0);
p.events = POLLIN | POLLOUT, p.revents = 0;
ASSERT(poll(&p, 1, 0) == 1 && p.revents == POLLOUT);
#if MG_ENABLE_FREERTOS
s_tx_waits = false, s_tx_held = true;
#endif
ASSERT(send(fd, "one", 3, 0) == 3);
ASSERT(send(fd, "two2", 4, 0) == 4);
ASSERT(send(fd, "", 0, 0) == 0);
#if MG_ENABLE_FREERTOS
while (!s_tx_waits) progress(mgr); // MG_IO_WAIT keeps all records queued
s_tx_held = false;
#endif
deadline = mg_millis() + 100;
while (mg_millis() < deadline) progress(mgr); // Let rogue arrive
ASSERT(recv(fd, buf, sizeof(buf), MSG_DONTWAIT) < 0 && errno == EAGAIN);
while ((n = send(fd, "checked", 7, 0)) < 0) {
ASSERT(errno == EAGAIN);
progress(mgr);
}
ASSERT(n == 7);
for (;;) {
n = recv(fd, buf, sizeof(buf), 0);
if (n >= 0) break;
ASSERT(errno == EAGAIN);
progress(mgr);
}
ASSERT(n == 4 && memcmp(buf, "echo", 4) == 0);
ASSERT(send(fd, "echo", 4, 0) == 4);
progress(mgr); // Drain the last queued datagram before close
ASSERT(close(fd) == 0);
}
static pid_t start_client(const char *ip, const char *connections) {
char *cmd[4];
pid_t pid;
@@ -46,6 +215,18 @@ static pid_t start_client(const char *ip, const char *connections) {
return pid;
}
static pid_t start_udp(const char *ip) {
char *cmd[3];
pid_t pid;
extern char **environ;
cmd[0] = (char *) "./bsd_udp";
cmd[1] = (char *) ip;
cmd[2] = NULL;
ASSERT(posix_spawn(&pid, cmd[0], NULL, NULL, cmd, environ) == 0);
return pid;
}
static bool client_done(pid_t pid, int *status) {
pid_t r = waitpid(pid, status, WNOHANG);
ASSERT(r >= 0);
@@ -60,12 +241,17 @@ static void check_client(int status) {
static int open_listener(int backlog, bool nonblock) {
int lfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in sa;
char buf;
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(1234);
sa.sin_addr.s_addr = INADDR_ANY;
ASSERT(lfd >= 0);
ASSERT(sendto(lfd, &buf, sizeof(buf), 0, (struct sockaddr *) &sa,
sizeof(sa)) < 0 && errno == EOPNOTSUPP);
ASSERT(recvfrom(lfd, &buf, sizeof(buf), 0, NULL, NULL) < 0 &&
errno == EOPNOTSUPP);
ASSERT(bind(lfd, (struct sockaddr *) &sa, sizeof(sa)) == 0);
ASSERT(listen(lfd, backlog) == 0);
if (nonblock) ASSERT(fcntl(lfd, F_SETFL, O_NONBLOCK) == 0);
@@ -130,11 +316,12 @@ static volatile bool s_select_waiting, s_select_done;
static volatile bool s_poll_waiting, s_poll_done;
static volatile bool s_ready_waiting, s_ready_done;
static volatile bool s_backlog_ready, s_backlog_full, s_backlog_accept;
static volatile bool s_tx_held, s_tx_waits, s_tx_fail, s_tx_error;
static volatile bool s_tx_fail, s_tx_error;
static volatile bool s_send_waiting;
static volatile bool s_send_done, s_send_expect_error;
static volatile bool s_recv_start, s_recv_waiting, s_recv_done;
static volatile bool s_recv_expect_error;
static volatile bool s_udp_ready, s_udp_done;
static size_t (*s_tx)(const void *, size_t, struct mg_tcpip_if *);
struct client_data {
@@ -152,6 +339,7 @@ static void select_task(void *args);
static void readiness_task(void *args);
static void backlog_task(void *args);
static void tx_gate_task(void *args);
static void udp_task(void *args);
struct atask_args {
bool listener_nonblock;
@@ -306,6 +494,21 @@ static void tx_gate_task(void *args) {
(void) args;
}
static void udp_delay(struct mg_mgr *mgr) {
(void) mgr;
vTaskDelay(1);
}
static void udp_task(void *args) {
struct mg_mgr *mgr = (struct mg_mgr *) args;
int fd = open_udp();
s_udp_ready = true;
udp_exchange(mgr, fd, udp_delay);
s_udp_done = true;
vTaskDelete(NULL);
}
static void recv_wait_task(void *args) {
char buf;
int fd = (int) (uintptr_t) args;
@@ -519,12 +722,22 @@ static void task2(void *args) {
struct mg_mgr *mgr = (struct mg_mgr *)args; // Event manager
char *ip;
int status;
pid_t first, second;
pid_t first, second, udp;
vTaskDelay(pdMS_TO_TICKS(500)); // 500 ms
s_error = false;
s_wait = false;
ip = mg_mprintf("%M", mg_print_ip4, &mgr->ifp->ip);
s_udp_ready = s_udp_done = false;
ASSERT(xTaskCreate(udp_task, "udp", 512, mgr, configMAX_PRIORITIES - 1,
NULL) == pdPASS);
while (!s_udp_ready) vTaskDelay(1);
udp = start_udp(ip);
while (!client_done(udp, &status)) vTaskDelay(1);
check_client(status);
while (!s_udp_done) vTaskDelay(1);
DASHBOARD("udp");
s_poll_waiting = s_poll_done = false;
ASSERT(xTaskCreate(poll_task, "poll", 512, NULL,
configMAX_PRIORITIES - 1, NULL) == pdPASS);
@@ -555,7 +768,6 @@ static void task2(void *args) {
DASHBOARD("select_child_write");
DASHBOARD("poll_child_write");
#if 0
s_error = false;
s_backlog_ready = s_backlog_full = s_backlog_accept = false;
ASSERT(xTaskCreate(backlog_task, "backlog", 512, NULL,
@@ -570,12 +782,6 @@ static void task2(void *args) {
while (!client_done(first, &status)) vTaskDelay(1);
check_client(status);
DASHBOARD("listen_backlog");
#else
(void) first;
(void) second;
(void) status;
(void) backlog_task;
#endif
s_error = false;
// atask at higher priority than task1
@@ -859,6 +1065,20 @@ static void nortos_echo(struct mg_mgr *mgr, struct nortos_echo_client *clients,
nortos_wait_client(mgr, pid);
}
static void udp_poll(struct mg_mgr *mgr) {
mg_mgr_poll(mgr, 0);
}
static void nortos_udp(struct mg_mgr *mgr, const char *ip) {
pid_t pid;
int fd = open_udp();
pid = start_udp(ip);
udp_exchange(mgr, fd, udp_poll);
nortos_wait_client(mgr, pid);
DASHBOARD("nortos_udp");
}
static void nortos_test(struct mg_mgr *mgr) {
static const char bootstrap = '\0';
static const char grow[512] = {0};
@@ -960,6 +1180,7 @@ static void nortos_test(struct mg_mgr *mgr) {
ASSERT(close(fd) == 0);
mg_mgr_poll(mgr, 0);
DASHBOARD("nortos_nonblocking_connect");
nortos_udp(mgr, ip);
mg_free(ip);
}
+108 -6
View File
@@ -3158,8 +3158,6 @@ static bool su_cb(struct mg_http_message *hm, struct mg_str *data, void **p) {
static void su(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_HTTP_HDRS) {
mg_http_stream_body(c, (struct mg_http_message *) ev_data, su_cb, NULL);
} else if (ev == MG_EV_HTTP_MSG) { // Only for requests that were not streamed
mg_http_reply(c, 400, "", "not streamed\n");
}
}
@@ -3194,8 +3192,7 @@ static void cr(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_CONNECT) {
mg_printf(c, "GET / HTTP/1.0\r\n\r\n");
} else if (ev == MG_EV_HTTP_HDRS) {
ASSERT(mg_http_stream_body(c, (struct mg_http_message *) ev_data, cr_cb,
NULL));
mg_http_stream_body(c, (struct mg_http_message *) ev_data, cr_cb, NULL);
}
}
@@ -3208,12 +3205,103 @@ static void cs(struct mg_connection *c, int ev, void *ev_data) {
(void) ev_data;
}
struct stream_test_state {
int fail_at, starts, chunks, ends;
};
static bool stream_test_cb(struct mg_http_message *hm, struct mg_str *data,
void **p) {
struct stream_test_state *s = (struct stream_test_state *) *p;
ASSERT(s != NULL);
if (hm != NULL) {
s->starts++;
return s->fail_at != 1;
} else if (data != NULL) {
s->chunks++;
return s->fail_at != 2;
}
s->ends++;
*p = NULL;
return s->fail_at != 3;
}
static void stream_test_original(struct mg_connection *c, int ev, void *data) {
(void) c, (void) ev, (void) data;
ASSERT(false); // Streaming must take ownership even when validation fails
}
static void test_http_stream_errors(void) {
static const struct {
const char *request;
int client, fail_at, body_size, code, starts, chunks;
} cases[] = {
{"GET / HTTP/1.1\r\nContent-Length: 0\r\n\r\n", 0, 0, 0, 405, 0, 0},
{"POST / HTTP/1.1\r\n\r\n", 0, 0, 0, 411, 0, 0},
{"POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n",
0, 0, 0, 400, 0, 0},
{"POST / HTTP/1.1\r\nContent-Length: 3\r\n\r\nabc", 0, 0, 0, 200, 1, 1},
{"PUT / HTTP/1.1\r\nContent-Length: 0\r\n\r\n", 0, 0, 0, 200, 1, 0},
// Start failure must reply without waiting for the body or sending 100.
{"POST / HTTP/1.1\r\nContent-Length: 1000000\r\nExpect: 100-continue\r\n\r\n",
0, 1, 0, 500, 1, 0},
// Write failure must also reply before the announced body is complete.
{"POST / HTTP/1.1\r\nContent-Length: 1000000\r\n\r\n",
0, 2, 512, 500, 1, 1},
{"POST / HTTP/1.1\r\nContent-Length: 3\r\n\r\nabc", 0, 3, 0, 500, 1, 1},
// Clients close on errors and never send an HTTP error response.
{"HTTP/1.1 200 OK\r\n\r\n", 1, 0, 0, 411, 0, 0},
{"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n",
1, 0, 0, 400, 0, 0},
{"HTTP/1.1 200 OK\r\nContent-Length: 1000000\r\n\r\n",
1, 1, 0, 500, 1, 0},
{"HTTP/1.1 200 OK\r\nContent-Length: 1000000\r\n\r\n",
1, 2, 512, 500, 1, 1},
{"HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nabc", 1, 3, 0, 500, 1, 1},
{"HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nabc", 1, 0, 0, 200, 1, 1},
{"HTTP/1.1 204 No Content\r\n\r\n", 1, 0, 0, 200, 1, 0},
};
size_t i;
for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
struct mg_connection c;
struct mg_http_message hm;
struct stream_test_state state = {0, 0, 0, 0};
char body[512];
memset(&c, 0, sizeof(c));
memset(body, 'x', sizeof(body));
state.fail_at = cases[i].fail_at;
c.is_client = cases[i].client != 0;
c.fn = c.pfn = stream_test_original;
mg_iobuf_add(&c.recv, 0, cases[i].request, strlen(cases[i].request));
mg_iobuf_add(&c.recv, c.recv.len, body, (size_t) cases[i].body_size);
ASSERT(mg_http_parse((char *) c.recv.buf, c.recv.len, &hm) > 0);
mg_http_stream_body(&c, &hm, stream_test_cb, &state);
ASSERT(c.pfn == NULL && c.is_draining);
ASSERT(state.starts == cases[i].starts && state.chunks == cases[i].chunks);
ASSERT(state.ends == 1);
if (c.is_client) {
ASSERT(c.send.len == 0);
ASSERT(c.is_closing == (cases[i].code != 200));
} else {
ASSERT(mg_http_parse((char *) c.send.buf, c.send.len, &hm) > 0);
ASSERT(mg_http_status(&hm) == cases[i].code);
ASSERT(hm.message.len == c.send.len); // Exactly one response
}
mg_call(&c, MG_EV_READ, NULL);
mg_error(&c, "stream cleanup test");
mg_call(&c, MG_EV_CLOSE, NULL);
ASSERT(state.ends == 1); // Error followed by close must not clean up twice
mg_iobuf_free(&c.recv);
mg_iobuf_free(&c.send);
}
}
static void test_http_upload(void) {
struct mg_mgr mgr;
const char *url = "http://127.0.0.1:12352";
int i;
const char *s = "ok (8 foo\nbar\n)\n";
test_http_stream_errors();
mg_mgr_init(&mgr);
mg_http_listen(&mgr, url, us, NULL);
mg_http_connect(&mgr, url, uc, (void *) &s);
@@ -3257,7 +3345,7 @@ static void test_http_upload(void) {
ASSERT(mgr.conns == NULL);
}
// mg_http_stream_body: chunked request is not streamed, caller replies "400"
// mg_http_stream_body: chunked request rejected with "400", cleanup still runs
{
char buf[FETCH_BUF_SIZE];
s_su_done = s_su_end = 0;
@@ -3268,7 +3356,7 @@ static void test_http_upload(void) {
"POST /su_chunked.txt HTTP/1.1\r\n"
"Transfer-Encoding: chunked\r\n\r\n"
"3\r\nfoo\r\n0\r\n\r\n") == 400);
ASSERT(s_su_done == 0 && s_su_end == 0); // The callback was never called
ASSERT(s_su_done == 1 && s_su_end == 1); // End called without start
ASSERT(fopen("su_chunked.txt", "r") == NULL);
mg_mgr_free(&mgr);
ASSERT(mgr.conns == NULL);
@@ -5010,6 +5098,20 @@ static void test_dash(void) {
"Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 200);
ASSERT(cmpbody(buf, login_admin_expected) == 0);
// OTA uses streaming and enforces the dashboard's authentication.
ASSERT(fetch(&mgr, buf, url,
"POST /api/ota HTTP/1.0\nContent-Length: 1\n\nx") == 403);
ASSERT(fetch(&mgr, buf, url,
"GET /api/ota HTTP/1.0\nContent-Length: 0\n"
"Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 405);
ASSERT(fetch(&mgr, buf, url,
"POST /ota HTTP/1.0\nContent-Length: 1\n"
"Authorization: Basic YWRtaW46YWRtaW4=\n\nx") == 404);
ASSERT(fetch(&mgr, buf, url,
"POST /api/ota HTTP/1.0\nContent-Length: 1\n"
"Authorization: Basic YWRtaW46YWRtaW4=\n\nx") == 200);
ASSERT(cmpbody(buf, "1 ok\n") == 0);
// set3 is admin-only
ASSERT(fetch(&mgr, buf, url,
"GET /api/get/set3 HTTP/1.0\n"
+3 -1
View File
@@ -1,9 +1,11 @@
THISDIR = $(realpath $(CURDIR))
ROOTDIR = $(realpath $(CURDIR)/../../..)
DOCKER ?= docker run --rm $(DA) -v $(ROOTDIR):$(ROOTDIR) -w $(THISDIR) espressif/idf
CMD ?= idf.py build
CMD ?= idf.py -DCFLAGS_EXTRA="$(CFLAGS_EXTRA)" build
PORT ?= /dev/ttyUSB0
CFLAGS_EXTRA ?= -DMG_TLS=MG_TLS_BUILTIN
all: example
example:
@@ -12,3 +12,5 @@ idf_component_register(SRCS "main.c"
espcoredump
nvs_flash
app_update)
separate_arguments(CFLAGS_EXTRA)
component_compile_options(${CFLAGS_EXTRA})
+6 -3
View File
@@ -44,6 +44,11 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
.key = mg_str(TLS_KEY)};
mg_tls_init(c, &opts);
} else if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
size_t total = heap_caps_get_total_size(MALLOC_CAP_DEFAULT);
@@ -65,10 +70,8 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
}
}
extern void wifi_init(const char *ssid, const char *pass);
void app_main(void) {
MG_HEALTH_INIT();
void wifi_init(const char *ssid, const char *pass);
wifi_init(WIFI_SSID, WIFI_PASS);
struct mg_mgr mgr;
@@ -1,6 +1 @@
#define MG_ARCH MG_ARCH_ESP32
#define MG_ENABLE_HEALTH 1
#define MG_TLS MG_TLS_BUILTIN
//#define MG_TLS MG_TLS_MBED
+6 -1
View File
@@ -9,7 +9,12 @@ static void t_fn(void *arg) { // Tick periodically
}
static void fn(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_HTTP_MSG) mg_http_reply(c, 200, "", "hi\n");
if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) mg_http_reply(c, 200, "", "hi\n");
(void) ev_data;
}
+6 -1
View File
@@ -9,7 +9,12 @@ static void t_fn(void *arg) { // Tick periodically
}
static void fn(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_HTTP_MSG) mg_http_reply(c, 200, "", "hi\n");
if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) mg_http_reply(c, 200, "", "hi\n");
(void) ev_data;
}
+6 -6
View File
@@ -3,24 +3,24 @@
extern void mgmp_init(void);
extern void mgmp_poll(int);
STATIC mp_obj_t mongoose_init(void) {
static mp_obj_t mongoose_init(void) {
mgmp_init();
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_0(mongoose_init_obj, mongoose_init);
static MP_DEFINE_CONST_FUN_OBJ_0(mongoose_init_obj, mongoose_init);
STATIC mp_obj_t mongoose_poll(mp_obj_t t_obj) {
static mp_obj_t mongoose_poll(mp_obj_t t_obj) {
mgmp_poll(mp_obj_get_int(t_obj));
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mongoose_poll_obj, mongoose_poll);
static MP_DEFINE_CONST_FUN_OBJ_1(mongoose_poll_obj, mongoose_poll);
STATIC const mp_rom_map_elem_t mongoose_module_globals_table[] = {
static const mp_rom_map_elem_t mongoose_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_mongoose) },
{ MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&mongoose_init_obj) },
{ MP_ROM_QSTR(MP_QSTR_poll), MP_ROM_PTR(&mongoose_poll_obj) },
};
STATIC MP_DEFINE_CONST_DICT(mongoose_module_globals, mongoose_module_globals_table);
static MP_DEFINE_CONST_DICT(mongoose_module_globals, mongoose_module_globals_table);
const mp_obj_module_t mongoose_module = {
.base = { &mp_type_module },
+1 -1
View File
@@ -120,7 +120,7 @@ void mg_mqtt_poll(struct mg_mgr *mgr) {
// Reconnect if connection is closed, and send MQTT PINGs to keep
// the connection alive or to detect connection loss
if (mg_timer_expired(&timer, MQTT_RECONNECT_PERIOD_MS, mg_now())) {
if (mg_timer_expired(&timer, MQTT_RECONNECT_PERIOD_MS, mg_millis())) {
if (s_mqtt_conn == NULL) {
struct mg_mqtt_opts opts = {
.clean = true,
@@ -65,6 +65,11 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
.key = mg_str(TLS_KEY)};
mg_tls_init(c, &opts);
} else if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
@@ -89,6 +89,11 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
.key = mg_str(TLS_KEY)};
mg_tls_init(c, &opts);
} else if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
@@ -97,6 +97,11 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
.key = mg_str(TLS_KEY)};
mg_tls_init(c, &opts);
} else if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
+5
View File
@@ -47,6 +47,11 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
.key = mg_str(TLS_KEY)};
mg_tls_init(c, &opts);
} else if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
+5
View File
@@ -65,6 +65,11 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
.key = mg_str(TLS_KEY)};
mg_tls_init(c, &opts);
} else if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
+5
View File
@@ -61,6 +61,11 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
.key = mg_str(TLS_KEY)};
mg_tls_init(c, &opts);
} else if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
+5
View File
@@ -65,6 +65,11 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
.key = mg_str(TLS_KEY)};
mg_tls_init(c, &opts);
} else if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
+6 -1
View File
@@ -5,7 +5,12 @@
#include "mongoose.h"
static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
uint64_t tick = to_ms_since_boot(get_absolute_time());
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
+6 -1
View File
@@ -33,7 +33,12 @@ static void driver_init(struct mg_mgr *mgr) {
}
static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
uint64_t tick = to_ms_since_boot(get_absolute_time());
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
+6 -1
View File
@@ -58,7 +58,12 @@ static void driver_init(struct mg_mgr *mgr) {
}
static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
uint64_t tick = to_ms_since_boot(get_absolute_time());
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
+5
View File
@@ -39,6 +39,11 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
.key = mg_str(TLS_KEY)};
mg_tls_init(c, &opts);
} else if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
uint64_t tick = to_ms_since_boot(get_absolute_time());
+5
View File
@@ -82,6 +82,11 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
.key = mg_str(TLS_KEY)};
mg_tls_init(c, &opts);
} else if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
uint64_t tick = to_ms_since_boot(get_absolute_time());
+5
View File
@@ -195,6 +195,11 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
.key = mg_str(TLS_KEY)};
mg_tls_init(c, &opts);
} else if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
uint64_t tick = to_ms_since_boot(get_absolute_time());
@@ -63,6 +63,11 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
.key = mg_str(TLS_KEY)};
mg_tls_init(c, &opts);
} else if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
@@ -66,7 +66,12 @@ static bool usb_poll(struct mg_tcpip_if *ifp, bool s1) {
}
static void fn(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_HTTP_MSG) {
if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/api/debug"), NULL)) {
int level = mg_json_get_long(hm->body, "$.level", MG_LL_DEBUG);
@@ -68,6 +68,11 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
.key = mg_str(TLS_KEY)};
mg_tls_init(c, &opts);
} else if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
@@ -28,7 +28,12 @@ bool mg_random(void *buf, size_t len) { // Use on-board RNG
static void fn(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_HTTP_MSG) {
if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
(void) hm;
mg_http_reply(c, 200, "", "hi\n");
@@ -66,7 +66,12 @@ static bool usb_poll(struct mg_tcpip_if *ifp, bool s1) {
}
static void fn(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_HTTP_MSG) {
if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/api/debug"), NULL)) {
int level = mg_json_get_long(hm->body, "$.level", MG_LL_DEBUG);
@@ -68,6 +68,11 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
.key = mg_str(TLS_KEY)};
mg_tls_init(c, &opts);
} else if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
@@ -68,6 +68,11 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
.key = mg_str(TLS_KEY)};
mg_tls_init(c, &opts);
} else if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
@@ -47,7 +47,12 @@ static void timer_fn(void *arg) {
static void fn(struct mg_connection *c, int ev, void *ev_data) {
struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->fn_data;
if (ev == MG_EV_HTTP_MSG) {
if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/api/hello"),
NULL)) { // Request to /api/hello
@@ -68,6 +68,11 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
.key = mg_str(TLS_KEY)};
mg_tls_init(c, &opts);
} else if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
@@ -89,7 +89,12 @@ static void log_fn(char ch, void *param) {
}
static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_HTTP_MSG) {
if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
mg_http_reply(c, 200, "", "{%m:%lu}\n", MG_ESC("tick"), HAL_GetTick());
@@ -6,7 +6,6 @@
#include "mongoose.h"
#include <stdio.h>
#include <stdlib.h>
struct echo_server {
@@ -43,12 +42,12 @@ int echo_init(uint16_t port) {
if (fd < 0 || !echo_nonblock(fd) ||
bind(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0 || listen(fd, 3) < 0) {
printf("BSD echo init: %d\n", errno);
MG_ERROR(("BSD echo init: %d", errno));
if (fd >= 0) close(fd);
return 0;
}
s_server.sock = fd;
printf("BSD echo server on port %hu\n", port);
MG_INFO(("BSD echo server on port %hu", port));
return 1;
}
@@ -80,7 +79,7 @@ void echo_poll(void) {
if (fd >= 0) {
c = (struct echo_client *) calloc(1, sizeof(*c));
if (c == NULL || !echo_nonblock(fd)) {
printf("BSD echo accept: %d\n", c == NULL ? ENOMEM : errno);
MG_ERROR(("BSD echo accept: %d", c == NULL ? ENOMEM : errno));
close(fd);
free(c);
} else {
@@ -44,7 +44,12 @@ bool mg_random(void *buf, size_t len) {
}
static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_HTTP_MSG) {
if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
mg_http_reply(c, 200, "", "{%m:%llu}\n", MG_ESC("tick"), hal_get_tick());
@@ -43,7 +43,12 @@ bool mg_random(void *buf, size_t len) {
}
static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_HTTP_MSG) {
if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
mg_http_reply(c, 200, "", "{%m:%llu}\n", MG_ESC("tick"), hal_get_tick());
@@ -0,0 +1,36 @@
# Copyright (c) 2026 Cesanta Software Limited
# Environment setup: https://mongoose.ws/docs/getting-started/build-environment/
MGDIR = ../../../..
CFLAGS = -W -Wall -Wextra -Wundef -Wshadow -Wdouble-promotion
CFLAGS += -Wformat-truncation -fno-common -Wconversion -Wno-sign-conversion
CFLAGS += -g3 -Os -ffunction-sections -fdata-sections
CFLAGS += -I. -I$(MGDIR) -Icmsis_core/CMSIS/Core/Include -Icmsis_h7/Include
CFLAGS += -mcpu=cortex-m7 -mthumb -mfloat-abi=hard -mfpu=fpv5-d16 $(CFLAGS_EXTRA)
LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs nosys.specs -lc -lgcc -Wl,--gc-sections -Wl,-Map=$@.map
SOURCES = main.c hal.c udp.c
SOURCES += cmsis_h7/Source/Templates/gcc/startup_stm32h723xx.s
SOURCES += $(MGDIR)/mongoose.c
all build example: firmware.bin
firmware.elf: cmsis_core cmsis_h7 hal.h link.ld Makefile $(SOURCES) $(MGDIR)/mongoose.h mongoose_config.h
arm-none-eabi-gcc $(SOURCES) $(CFLAGS) $(CFLAGS_EXTRA) $(LDFLAGS) -o $@
firmware.bin: firmware.elf
arm-none-eabi-objcopy -O binary $< $@
@echo
@echo "To flash, run 'make flash', or use STM32CubeProgrammer"
flash: firmware.bin
STM32_Programmer_CLI -c port=swd -w firmware.elf -hardRst
cmsis_core:
git clone -q -c advice.detachedHead=false --depth 1 -b 5.9.0 https://github.com/ARM-software/CMSIS_5 $@
cmsis_h7:
git clone -q -c advice.detachedHead=false --depth 1 -b v1.10.6 https://github.com/STMicroelectronics/cmsis_device_h7 $@
clean:
rm -rf firmware.* cmsis_*
@@ -0,0 +1,125 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#include "hal.h"
bool hal_timer_expired(volatile uint64_t *t, uint64_t period, uint64_t now) {
uint64_t diff = now - *t; // Wrap-safe elapsed time since last expiry
if (period == 0) return false; // Avoid division by zero
if (diff < period) return false; // Period has not elapsed yet
*t += (diff / period) * period; // Preserve cadence, skip missed periods
return true;
}
static volatile uint64_t s_ticks; // Milliseconds since boot
void SysTick_Handler(void) { // SyStick IRQ handler, triggered every 1ms
s_ticks++;
}
uint64_t hal_get_tick(void) {
return s_ticks;
};
uint32_t SystemCoreClock = 160000000;
void SystemInit(void) { // Called automatically by startup code
hal_system_init(); // Enable FPU
hal_clock_init();
SysTick_Config(SystemCoreClock / 1000); // Sys tick every 1ms
}
void ExitRun0Mode(void) {
}
struct stat;
__attribute__((weak)) int _fstat(int fd, struct stat *st) {
(void) fd, (void) st;
return -1;
}
extern unsigned char _end[]; // End of data section, start of heap. See link.ld
static unsigned char *s_current_heap_end = _end;
size_t hal_ram_used(void) {
return (size_t) (s_current_heap_end - _end);
}
size_t hal_ram_free(void) {
unsigned char endofstack;
return (size_t) (&endofstack - s_current_heap_end);
}
void *_sbrk(int incr) {
unsigned char *prev_heap;
unsigned char *heap_end = (unsigned char *) ((size_t) &heap_end - 256);
prev_heap = s_current_heap_end;
// Check how much space we got from the heap end to the stack end
if (s_current_heap_end + incr > heap_end) return (void *) -1;
s_current_heap_end += incr;
return prev_heap;
}
__attribute__((weak)) int _open(const char *path) {
(void) path;
return -1;
}
__attribute__((weak)) int _close(int fd) {
(void) fd;
return -1;
}
__attribute__((weak)) int _isatty(int fd) {
(void) fd;
return 1;
}
__attribute__((weak)) int _lseek(int fd, int ptr, int dir) {
(void) fd, (void) ptr, (void) dir;
return 0;
}
__attribute__((weak)) void _exit(int status) {
(void) status;
for (;;) asm volatile("BKPT #0");
}
__attribute__((weak)) void _kill(int pid, int sig) {
(void) pid, (void) sig;
}
__attribute__((weak)) int _getpid(void) {
return -1;
}
__attribute__((weak)) int _write(int fd, char *ptr, int len) {
(void) fd, (void) ptr, (void) len;
return -1;
}
__attribute__((weak)) int _read(int fd, char *ptr, int len) {
(void) fd, (void) ptr, (void) len;
return -1;
}
__attribute__((weak)) int _link(const char *a, const char *b) {
(void) a, (void) b;
return -1;
}
__attribute__((weak)) int _unlink(const char *a) {
(void) a;
return -1;
}
__attribute__((weak)) int _stat(const char *path, struct stat *st) {
(void) path, (void) st;
return -1;
}
__attribute__((weak)) int mkdir(const char *path, int mode) {
(void) path, (void) mode;
return -1;
}
__attribute__((weak)) void _init(void) {
}
@@ -0,0 +1,225 @@
// Copyright (c) 2022-2026 Cesanta Software Limited
// Datasheet: RM0468, devboard manual: UM2407
// https://www.st.com/resource/en/reference_manual/rm0468-stm32h723733-stm32h725735-and-stm32h730-value-line-advanced-armbased-32bit-mcus-stmicroelectronics.pdf
// https://www.st.com/resource/en/user_manual/um2407-stm32h7-nucleo144-boards-mb1364-stmicroelectronics.pdf
// Alternate functions: https://www.st.com/resource/en/datasheet/stm32h723.pdf
#pragma once
#include <stm32h723xx.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
extern bool hal_timer_expired(volatile uint64_t *t, uint64_t prd, uint64_t now);
extern uint64_t hal_get_tick(void);
extern size_t hal_ram_free(void);
extern size_t hal_ram_used(void);
extern uint32_t SystemCoreClock;
#define BIT(x) (1UL << (x))
#define CLRSET(reg, clear, set) ((reg) = ((reg) & ~(clear)) | (set))
#define PIN(bank, num) ((((bank) - 'A') << 8) | (num))
#define PINNO(pin) (pin & 255)
#define PINBANK(pin) (pin >> 8)
#define HAL_ETH_PINS PIN('A', 1), PIN('A', 2), PIN('A', 7), \
PIN('B', 13), PIN('C', 1), PIN('C', 4), \
PIN('C', 5), PIN('G', 11), PIN('G', 13)
// System clock (2.1, Figure 1; 8.5, Figure 45; 8.5.5, Figure 47; 8.5.6, Figure
// 49; 8.5.8 Table 56; datasheet) CPU_FREQUENCY <= 550 MHz; hclk = CPU_FREQUENCY
// / HAL_HPRE ; hclk <= 275 MHz; APB clocks <= 137.5 MHz. D1 domain bus matrix (and
// so flash) runs at hclk frequency. Configure flash latency (WS) in accordance
// to hclk freq (4.3.8, Table 16) The Ethernet controller is in D2 domain and
// runs at hclk frequency
enum {
HAL_D1CPRE = 1, // actual divisor value
HAL_HPRE = 2, // actual divisor value
HAL_D1PPRE = 4, // register values, divisor value = BIT(value - 3) = / 2
HAL_D2PPRE1 = 4,
HAL_D2PPRE2 = 4,
HAL_D3PPRE = 4
};
// PLL1_P: odd division factors are not allowed (8.7.12) (except for '1')
enum { PLL1_HSI = 64, PLL1_M = 32, PLL1_N = 225, PLL1_P = 1 };
#define HAL_FLASH_LATENCY 0x33 // WRHIGHFREQ LATENCY
#define SYS_FREQUENCY ((PLL1_HSI * PLL1_N / PLL1_M / PLL1_P / HAL_D1CPRE) * 1000000)
#define AHB_FREQUENCY (SYS_FREQUENCY / HAL_HPRE)
#define APB2_FREQUENCY (AHB_FREQUENCY / (BIT(HAL_D2PPRE2 - 3)))
#define APB1_FREQUENCY (AHB_FREQUENCY / (BIT(HAL_D2PPRE1 - 3)))
enum { HAL_GPIO_MODE_INPUT, HAL_GPIO_MODE_OUTPUT, HAL_GPIO_MODE_AF, HAL_GPIO_MODE_ANALOG };
enum { HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_OTYPE_OPEN_DRAIN };
enum { HAL_GPIO_SPEED_LOW, HAL_GPIO_SPEED_MEDIUM, HAL_GPIO_SPEED_HIGH, HAL_GPIO_SPEED_INSANE };
enum { HAL_GPIO_PULL_NONE, HAL_GPIO_PULL_UP, HAL_GPIO_PULL_DOWN };
#define HAL_GPIO(N) ((GPIO_TypeDef *) (0x40000000 + 0x18020000UL + 0x400 * (N)))
static GPIO_TypeDef *hal_gpio_bank(uint16_t pin) {
return HAL_GPIO(PINBANK(pin));
}
static inline void hal_gpio_toggle(uint16_t pin) {
GPIO_TypeDef *gpio = hal_gpio_bank(pin);
uint32_t mask = BIT(PINNO(pin));
gpio->BSRR = mask << (gpio->ODR & mask ? 16 : 0);
}
static inline int hal_gpio_read(uint16_t pin) {
return hal_gpio_bank(pin)->IDR & BIT(PINNO(pin)) ? 1 : 0;
}
static inline void hal_gpio_write(uint16_t pin, bool val) {
GPIO_TypeDef *gpio = hal_gpio_bank(pin);
gpio->BSRR = BIT(PINNO(pin)) << (val ? 0 : 16);
}
static inline void hal_gpio_init(uint16_t pin, uint8_t mode, uint8_t type,
uint8_t speed, uint8_t pull, uint8_t af) {
GPIO_TypeDef *gpio = hal_gpio_bank(pin);
uint8_t n = (uint8_t) (PINNO(pin));
RCC->AHB4ENR |= BIT(PINBANK(pin)); // Enable GPIO clock
CLRSET(gpio->OTYPER, 1UL << n, ((uint32_t) type) << n);
CLRSET(gpio->OSPEEDR, 3UL << (n * 2), ((uint32_t) speed) << (n * 2));
CLRSET(gpio->PUPDR, 3UL << (n * 2), ((uint32_t) pull) << (n * 2));
CLRSET(gpio->AFR[n >> 3], 15UL << ((n & 7) * 4),
((uint32_t) af) << ((n & 7) * 4));
CLRSET(gpio->MODER, 3UL << (n * 2), ((uint32_t) mode) << (n * 2));
}
static inline void hal_gpio_input(uint16_t pin) {
hal_gpio_init(pin, HAL_GPIO_MODE_INPUT, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH,
HAL_GPIO_PULL_NONE, 0);
}
static inline void hal_gpio_output(uint16_t pin) {
hal_gpio_init(pin, HAL_GPIO_MODE_OUTPUT, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH,
HAL_GPIO_PULL_NONE, 0);
}
// D2 Kernel clock (8.7.21) USART1 defaults to pclk2 (APB2), while USART2,3
// default to pclk1 (APB1). Even if using other kernel clocks, the APBx clocks
// must be enabled for CPU access, as the kernel clock drives the BRR, not the
// APB bus interface
static inline void hal_uart_init(USART_TypeDef *uart, uint16_t tx,
uint16_t rx, unsigned long baud) {
uint8_t af = 7; // Alternate function
uint32_t freq = 0; // Bus frequency. UART1 is on APB2, rest on APB1
if (uart == USART1) freq = APB2_FREQUENCY, RCC->APB2ENR |= BIT(4);
if (uart == USART2) freq = APB1_FREQUENCY, RCC->APB1LENR |= BIT(17);
if (uart == USART3) freq = APB1_FREQUENCY, RCC->APB1LENR |= BIT(18);
#if 0 // CONSTANT BAUD RATE FOR REMOTE DEBUGGING WHILE SETTING THE PLL
CLRSET(RCC->D2CCIP2R, 7 << 3, 3 << 3); // use HSI for UART1
freq = 64000000;
#endif
hal_gpio_init(tx, HAL_GPIO_MODE_AF, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH, 0, af);
hal_gpio_init(rx, HAL_GPIO_MODE_AF, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH, 0, af);
uart->CR1 = 0; // Disable this UART
uart->BRR = freq / baud; // Set baud rate
uart->CR1 = BIT(0) | BIT(2) | BIT(3); // Set UE, RE, TE
}
static inline void hal_uart_write_byte(USART_TypeDef *uart, uint8_t byte) {
uart->TDR = byte;
while ((uart->ISR & USART_ISR_TXE_TXFNF) == 0) (void) 0;
}
static inline void hal_uart_write_buf(USART_TypeDef *uart, char *buf, size_t len) {
while (len-- > 0) hal_uart_write_byte(uart, *(uint8_t *) buf++);
}
static inline int hal_uart_read_ready(USART_TypeDef *uart) {
return uart->ISR & BIT(5); // If RXNE bit is set, data is ready
}
static inline uint8_t hal_uart_read_byte(USART_TypeDef *uart) {
return (uint8_t) (uart->RDR & 255);
}
// RNG clock (rng_clk) defaults to HSI48 and is characterized at this frequency.
// Enable at SystemInit. (STM32CubeMX) It won't work with a 275MHz (AHB)
static inline void hal_rng_init(void) {
RCC->AHB2ENR |= RCC_AHB2ENR_RNGEN; // Enable RNG AHB clock (hal_rng_hclk)
// 39.6.2 Table 320 init
RNG->CR = RNG_CR_CONDRST | (0x0F << RNG_CR_RNG_CONFIG1_Pos) |
(0 << RNG_CR_RNG_CONFIG2_Pos) | (0xD << RNG_CR_RNG_CONFIG3_Pos);
RNG->HTCR = 0x17590abc;
RNG->HTCR = 0xaa74;
RNG->CR &= ~RNG_CR_CONDRST ;
while (RNG->CR & RNG_CR_CONDRST) (void) 0; // 39.7.1
RNG->CR |= RNG_CR_RNGEN; // Enable RNG
}
static inline uint32_t hal_rng_read(void) {
while ((RNG->SR & RNG_SR_DRDY) == 0) (void) 0;
return RNG->DR;
}
// Hw pull-ups on PHY RXD0,1,DV to enable autonegotiation
static inline void hal_ethernet_init(void) {
// Initialise Ethernet. Enable MAC GPIO pins, see
// https://www.st.com/resource/en/user_manual/um2407-stm32h7-nucleo144-boards-mb1364-stmicroelectronics.pdf
uint16_t pins[] = {HAL_ETH_PINS};
for (size_t i = 0; i < sizeof(pins) / sizeof(pins[0]); i++) {
hal_gpio_init(pins[i], HAL_GPIO_MODE_AF, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_INSANE,
HAL_GPIO_PULL_NONE, 11); // 11 is the Ethernet function
}
NVIC_EnableIRQ(ETH_IRQn); // Setup Ethernet IRQ handler
CLRSET(SYSCFG->PMCR, 7 << 21, 4 << 21); // Use RMII (12.3.1)
RCC->AHB1ENR |= BIT(15) | BIT(16) | BIT(17); // Enable Ethernet clocks
}
static inline unsigned int hal_div2prescval(unsigned int div) {
// 0 --> /1; 8 --> /2 ... 11 --> /16; 12 --> /64 ... 15 --> /512
if (div == 1) return 0;
if (div > 16) div /= 2;
unsigned int val = 7;
while (div >>= 1) ++val;
return val;
}
static inline unsigned int hal_pllrge(unsigned int f) {
unsigned int val = 0;
while (f >>= 1) ++val;
return val - 1;
}
// Enable RTC/BKP APB clock and backup domain write access.
// Must be called before accessing RTC backup registers or IWDG.
static inline void hal_backup_domain_init(void) {
RCC->APB4ENR |= RCC_APB4ENR_RTCAPBEN; // enable RTC/BKP APB clock
PWR->CR1 |= PWR_CR1_DBP; // enable backup domain write access
}
static inline void hal_clock_init(void) {
hal_backup_domain_init();
PWR->CR3 |= PWR_CR3_LDOEN; // select LDO (reset value)
while ((PWR->CSR1 & PWR_CSR1_ACTVOSRDY) == 0) (void) 0; // ACTVOSRDY
PWR->D3CR &= ~PWR_D3CR_VOS_Msk; // Select VOS0
while ((PWR->D3CR & PWR_D3CR_VOSRDY) == 0) (void) 0; // VOSRDY
CLRSET(
RCC->D1CFGR, (0x0F << 8) | (7 << 4) | (0x0F << 0),
(hal_div2prescval(HAL_D1CPRE) << 8) | (HAL_D1PPRE << 4) | (hal_div2prescval(HAL_HPRE) << 0));
RCC->D2CFGR = (HAL_D2PPRE2 << 8) | (HAL_D2PPRE1 << 4);
RCC->D3CFGR = (HAL_D3PPRE << 4);
CLRSET(RCC->PLLCFGR, 3 << 2,
hal_pllrge(PLL1_HSI / PLL1_M)
<< 2); // keep reset config (DIVP1EN, !PLL1VCOSEL), PLL1RGE
CLRSET(RCC->PLL1DIVR, (0x7F << 9) | (0x1FF << 0),
((PLL1_P - 1) << 9) | ((PLL1_N - 1) << 0)); // Set PLL1_P PLL1_N
CLRSET(RCC->PLLCKSELR, 0x3F << 4,
PLL1_M << 4); // Set PLL1_M (source defaults to HSI)
RCC->CR |= RCC_CR_PLL1ON | RCC_CR_HSI48ON; // Enable PLL1 and HSI48 (for RNG)
while ((RCC->CR & RCC_CR_PLL1RDY) == 0) (void) 0; // Wait until done
RCC->CFGR |= (3 << 0); // Set clock source to PLL1
while ((RCC->CFGR & (7 << 3)) != (3 << 3)) (void) 0; // Wait until done
FLASH->ACR = HAL_FLASH_LATENCY; // default is larger
RCC->APB4ENR |= RCC_APB4ENR_SYSCFGEN; // Enable SYSCFG
while ((RCC->CR & RCC_CR_HSI48RDY) == 0) (void) 0; // Make sure HSI48 is ready
SystemCoreClock = SYS_FREQUENCY; // Update SystemCoreClock global var
SysTick_Config(SystemCoreClock / 1000); // Sys tick every 1ms
}
static inline void hal_system_init(void) {
SCB->CPACR |= ((3UL << 10 * 2) | (3UL << 11 * 2)); // Enable FPU
__DSB();
__ISB();
}
@@ -0,0 +1,32 @@
ENTRY(Reset_Handler);
MEMORY {
flash(rx) : ORIGIN = 0x08000000, LENGTH = 1024k
dtcmram (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
ram_d1 (rwx) : ORIGIN = 0x24000000, LENGTH = 320K
ram_d2 (rwx) : ORIGIN = 0x30000000, LENGTH = 32K
ram_d3 (rwx) : ORIGIN = 0x38000000, LENGTH = 16K
itcmram (rwx) : ORIGIN = 0x00000000, LENGTH = 64K
}
_estack = ORIGIN(ram_d1) + LENGTH(ram_d1);
SECTIONS {
.vectors : { KEEP(*(.isr_vector)) } > flash
.text : { *(.text* .text.*) } > flash
.rodata : { *(.rodata*) } > flash
/* Note the .iram section, for functions copied to RAM. Required for MG_IRAM OTA support */
.data : { _sdata = .; *(.first_data) *(.iram .iram* .iram.*) *(.data SORT(.data.*)) _edata = .; } > ram_d1 AT > flash
_sidata = LOADADDR(.data);
.bss : { _sbss = .; *(.bss SORT(.bss.*) COMMON) _ebss = .; } > ram_d1
.eth_ram : { *(.eth_ram .eth_ram*) } > ram_d1 AT > flash
/* Health record. NOLOAD and in unused DTCM, so startup code neither copies
nor zeroes it and the contents survive a warm reset */
.mg_health (NOLOAD) : { KEEP(*(.mg_health .mg_health*)) } > dtcmram
. = ALIGN(8);
_end = .;
}
@@ -0,0 +1,69 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#include "hal.h"
#include "mongoose.h"
int udp_init(uint16_t port);
void udp_poll(void);
#ifndef UART_DEBUG
#define UART_DEBUG USART3
#define UART_DEBUG_TX_PIN PIN('D', 8)
#define UART_DEBUG_RX_PIN PIN('D', 9)
#else
#define UART_DEBUG_TX_PIN PIN('A', 9)
#define UART_DEBUG_RX_PIN PIN('A', 10)
#endif
#define LED1 PIN('B', 0)
#define LED2 PIN('E', 1)
#define LED3 PIN('B', 14)
static void log_fn(char ch, void *param) {
hal_uart_write_buf(param, &ch, 1);
}
static void blink_task(void) {
static uint64_t blink_timer = 0;
if (hal_timer_expired(&blink_timer, 500, hal_get_tick())) {
hal_gpio_toggle(LED2);
}
}
uint64_t mg_millis(void) {
return hal_get_tick();
}
bool mg_random(void *buf, size_t len) {
for (size_t n = 0; n < len; n += sizeof(uint32_t)) {
uint32_t r = hal_rng_read();
memcpy((char *) buf + n, &r, n + sizeof(r) > len ? len - n : sizeof(r));
}
return true;
}
int main(void) {
struct mg_mgr mgr;
hal_clock_init();
hal_uart_init(UART_DEBUG, UART_DEBUG_TX_PIN, UART_DEBUG_RX_PIN, 115200);
mg_log_set_fn(log_fn, UART_DEBUG);
hal_rng_init();
hal_ethernet_init();
hal_gpio_output(LED1);
hal_gpio_output(LED2);
hal_gpio_output(LED3);
MG_INFO(("Initialised. CPU clock: %lu MHz", SystemCoreClock / 1000000));
mg_mgr_init(&mgr);
udp_init(1234);
for (;;) {
mg_mgr_poll(&mgr, 0);
udp_poll();
blink_task();
}
return 0;
}
@@ -0,0 +1,37 @@
#pragma once
#include "hal.h"
// See https://mongoose.ws/docs/getting-started/build-options/
#define MG_ARCH MG_ARCH_ARMGCC
#define MG_TLS MG_TLS_BUILTIN
#define MG_OTA MG_OTA_STM32H7
#define MG_ENABLE_TCPIP 1
#define MG_ENABLE_BSD_SOCKETS 1
#define MG_ENABLE_CUSTOM_MILLIS 1
#define MG_ENABLE_CUSTOM_RANDOM 1
#define MG_ENABLE_DRIVER_STM32H 1
#define MG_ENABLE_PACKED_FS 1
#define MG_ETH_RAM __attribute__((section(".eth_ram")))
// #define MG_DRIVER_MDC_CR 4 // RMII MDC clock divider, from 0 to 5
// #define MG_TCPIP_PHY_ADDR 0 // PHY address
// For static IP configuration, define MG_TCPIP_{IP,MASK,GW}
// By default, those are set to zero, meaning that DHCP is used
//
// #define MG_TCPIP_IP MG_IPV4(192, 168, 0, 10) // IP
// #define MG_TCPIP_GW MG_IPV4(192, 168, 0, 1) // Gateway
// #define MG_TCPIP_MASK MG_IPV4(255, 255, 255, 0) // Netmask
// Construct MAC address from the MCU unique ID
#define MGUID ((uint32_t *) UID_BASE)
#define MG_SET_MAC_ADDRESS(mac) \
do { \
mac[0] = 2; \
mac[1] = MGUID[0] & 255; \
mac[2] = (MGUID[0] >> 10) & 255; \
mac[3] = (MGUID[0] >> 19) & 255; \
mac[4] = MGUID[1] & 255; \
mac[5] = MGUID[2] & 255; \
} while (0)
@@ -0,0 +1,68 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#include "mongoose.h"
struct udp_server {
int fd;
bool pending;
struct sockaddr_in peer;
socklen_t peerlen;
size_t len;
char buf[128];
};
static struct udp_server s_server = {.fd = -1};
static int udp_nonblock(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
return flags >= 0 && fcntl(fd, F_SETFL, flags | O_NONBLOCK) == 0;
}
int udp_init(uint16_t port) {
struct sockaddr_in sa = {
.sin_family = AF_INET,
.sin_port = htons(port),
.sin_addr = {.s_addr = INADDR_ANY},
};
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0 || !udp_nonblock(fd) ||
bind(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) {
MG_ERROR(("BSD UDP init: %d", errno));
if (fd >= 0) close(fd);
return 0;
}
s_server.fd = fd;
MG_INFO(("BSD UDP echo server on port %hu", port));
return 1;
}
void udp_poll(void) {
struct udp_server *s = &s_server;
struct pollfd p;
ssize_t n;
if (s->fd < 0) return;
p.fd = s->fd, p.events = s->pending ? POLLOUT : POLLIN, p.revents = 0;
if (poll(&p, 1, 0) <= 0) return;
if (s->pending) {
n = sendto(s->fd, s->buf, s->len, 0, (struct sockaddr *) &s->peer,
s->peerlen);
if (n == (ssize_t) s->len) {
s->pending = false;
} else if (n < 0 && errno != EAGAIN) {
MG_ERROR(("BSD UDP send: %d", errno));
s->pending = false;
}
} else {
s->peerlen = sizeof(s->peer);
n = recvfrom(s->fd, s->buf, sizeof(s->buf), 0,
(struct sockaddr *) &s->peer, &s->peerlen);
if (n >= 0) {
s->len = (size_t) n, s->pending = true;
} else if (errno != EAGAIN) {
MG_ERROR(("BSD UDP recv: %d", errno));
}
}
}
@@ -68,6 +68,11 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
.key = mg_str(TLS_KEY)};
mg_tls_init(c, &opts);
} else if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
@@ -68,6 +68,11 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
.key = mg_str(TLS_KEY)};
mg_tls_init(c, &opts);
} else if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
@@ -64,6 +64,11 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
.key = mg_str(TLS_KEY)};
mg_tls_init(c, &opts);
} else if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
@@ -60,6 +60,11 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
.key = mg_str(TLS_KEY)};
mg_tls_init(c, &opts);
} else if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
@@ -68,6 +68,11 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
.key = mg_str(TLS_KEY)};
mg_tls_init(c, &opts);
} else if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
@@ -68,6 +68,11 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
.key = mg_str(TLS_KEY)};
mg_tls_init(c, &opts);
} else if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
@@ -68,6 +68,11 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
.key = mg_str(TLS_KEY)};
mg_tls_init(c, &opts);
} else if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
@@ -63,7 +63,12 @@ static bool usb_poll(struct mg_tcpip_if *ifp, bool s1) {
}
static void fn(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_HTTP_MSG) {
if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/api/debug"), NULL)) {
int level = mg_json_get_long(hm->body, "$.level", MG_LL_DEBUG);
@@ -30,7 +30,12 @@ static void timer_fn(void *arg) {
// https://mongoose.ws/documentation/#2-minute-integration-guide
static void fn(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_HTTP_MSG) {
if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
mg_http_reply(c, 200, "", "ok %p\r\n", hm);
}
+5
View File
@@ -14,6 +14,11 @@ static void wcb(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_ACCEPT && c->is_tls) {
struct mg_tls_opts opts = {.cert = (char *) s_ssl_cert, .key = (char *) s_ssl_key};
mg_tls_init(c, &opts);
} else if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
}
} else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = ev_data;
MG_INFO(("%.*s %.*s %ld", (int) hm->method.len, hm->method.buf,