From 3319ce78d3f4fa274ce24da6d87aa4d16e95c201 Mon Sep 17 00:00:00 2001 From: Sergey Lyubka Date: Tue, 22 Oct 2024 01:25:19 +0100 Subject: [PATCH 1/2] Simplify OTA API --- mongoose.c | 107 +++++++++++++++++++++++++++++++++++++++---- mongoose.h | 14 ++++++ src/device_stm32h5.c | 40 +++++++++++----- src/flash.c | 65 ++++++++++++++++++++++++++ src/flash.h | 13 ++++++ src/ota.h | 1 + test/Makefile | 2 +- 7 files changed, 220 insertions(+), 22 deletions(-) create mode 100644 src/flash.c create mode 100644 src/flash.h diff --git a/mongoose.c b/mongoose.c index 284f666267..e895c9c900 100644 --- a/mongoose.c +++ b/mongoose.c @@ -756,10 +756,11 @@ MG_IRAM void mg_device_reset(void) { #ifdef MG_ENABLE_LINES #line 1 "src/device_stm32h5.c" #endif +// -#if MG_DEVICE == MG_DEVICE_STM32H5 +#if MG_OTA == MG_OTA_STM32H5 #define FLASH_BASE 0x40022000 // Base address of the flash controller #define FLASH_KEYR (FLASH_BASE + 0x4) // See RM0481 7.11 @@ -771,6 +772,7 @@ MG_IRAM void mg_device_reset(void) { #define FLASH_OPTSR_CUR (FLASH_BASE + 0x50) #define FLASH_OPTSR_PRG (FLASH_BASE + 0x54) +#if 0 void *mg_flash_start(void) { return (void *) 0x08000000; } @@ -786,6 +788,7 @@ size_t mg_flash_write_align(void) { int mg_flash_bank(void) { return MG_REG(FLASH_OPTCR) & MG_BIT(31) ? 2 : 1; } +#endif static void flash_unlock(void) { static bool unlocked = false; @@ -824,14 +827,14 @@ static bool flash_bank_is_swapped(void) { return MG_REG(FLASH_OPTCR) & MG_BIT(31); // RM0481 7.11.8 } -bool mg_flash_erase(void *location) { +static bool mg_stm32h5_erase(void *location) { bool ok = false; if (flash_page_start(location) == false) { MG_ERROR(("%p is not on a sector boundary")); } else { uintptr_t diff = (char *) location - (char *) mg_flash_start(); uint32_t sector = diff / mg_flash_sector_size(); - uint32_t saved_cr = MG_REG(FLASH_NSCR); // Save CR value + uint32_t saved_cr = MG_REG(FLASH_NSCR); // Save CR value flash_unlock(); flash_clear_err(); MG_REG(FLASH_NSCR) = 0; @@ -847,12 +850,12 @@ bool mg_flash_erase(void *location) { MG_DEBUG(("Erase sector %lu @ %p: %s. CR %#lx SR %#lx", sector, location, ok ? "ok" : "fail", MG_REG(FLASH_NSCR), MG_REG(FLASH_NSSR))); // mg_hexdump(location, 32); - MG_REG(FLASH_NSCR) = saved_cr; // Restore saved CR + MG_REG(FLASH_NSCR) = saved_cr; // Restore saved CR } return ok; } -bool mg_flash_swap_bank(void) { +static bool mg_stm32h5_swap(void) { uint32_t desired = flash_bank_is_swapped() ? 0 : MG_BIT(31); flash_unlock(); flash_clear_err(); @@ -864,7 +867,7 @@ bool mg_flash_swap_bank(void) { return true; } -bool mg_flash_write(void *addr, const void *buf, size_t len) { +static bool mg_stm32h5_write(void *addr, const void *buf, size_t len) { if ((len % mg_flash_write_align()) != 0) { MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align())); return false; @@ -879,7 +882,7 @@ bool mg_flash_write(void *addr, const void *buf, size_t len) { // MG_DEBUG(("Starting flash write %lu bytes @ %p", len, addr)); MG_REG(FLASH_NSCR) = MG_BIT(1); // Set programming flag while (ok && src < end) { - if (flash_page_start(dst) && mg_flash_erase(dst) == false) break; + if (flash_page_start(dst) && mg_stm32h5_erase(dst) == false) break; *(volatile uint32_t *) dst++ = *src++; flash_wait(); if (flash_is_err()) ok = false; @@ -892,9 +895,24 @@ bool mg_flash_write(void *addr, const void *buf, size_t len) { return ok; } -void mg_device_reset(void) { - // SCB->AIRCR = ((0x5fa << SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk); - *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; +static struct mg_flash s_mg_flash_stm32h5 = { + (void *) 0x08000000, // Start + 2 * 1024 * 1024, // Size, 2Mb + 16, // Align, 128 bit + mg_stm32h5_write, + mg_stm32h5_swap, +}; + +bool mg_ota_begin(size_t new_firmware_size) { + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_stm32h5); +} + +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_stm32h5); +} + +bool mg_ota_end(void) { + return mg_ota_flash_end(&s_mg_flash_stm32h5); } #endif @@ -1365,6 +1383,75 @@ void mg_error(struct mg_connection *c, const char *fmt, ...) { mg_call(c, MG_EV_ERROR, buf); // Let user handler override it } +#ifdef MG_ENABLE_LINES +#line 1 "src/flash.c" +#endif + + + + + +static char *s_addr; // Current address to write to +static size_t s_size; // Firmware size to flash. In-progress indicator +static uint32_t s_crc32; // Firmware checksum + +bool mg_ota_flash_begin(size_t new_firmware_size, struct mg_flash *flash) { + bool ok = false; + if (s_size) { + MG_ERROR(("OTA already in progress. Call mg_ota_end()")); + } else { + size_t half = flash->size / 2; + s_crc32 = 0; + s_addr = (char *) flash->start + half; + MG_DEBUG(("FW %lu bytes, max %lu", new_firmware_size, half)); + if (new_firmware_size < half) { + ok = true; + s_size = new_firmware_size; + MG_INFO(("Starting OTA, firmware size %lu", s_size)); + } else { + MG_ERROR(("Firmware %lu is too big to fit %lu", new_firmware_size, half)); + } + } + return ok; +} + +bool mg_ota_flash_write(const void *buf, size_t len, struct mg_flash *flash) { + bool ok = false; + if (s_size == 0) { + MG_ERROR(("OTA is not started, call mg_ota_begin()")); + } else { + size_t len_aligned_down = MG_ROUND_DOWN(len, flash->align); + if (len_aligned_down) ok = flash->write_fn(s_addr, buf, len_aligned_down); + if (len_aligned_down < len) { + size_t left = len - len_aligned_down; + char tmp[flash->align]; + memset(tmp, 0xff, sizeof(tmp)); + memcpy(tmp, (char *) buf + len_aligned_down, left); + ok = flash->write_fn(s_addr + len_aligned_down, tmp, sizeof(tmp)); + } + s_crc32 = mg_crc32(s_crc32, (char *) buf, len); // Update CRC + MG_DEBUG(("%#x %p %lu -> %d", s_addr - len, buf, len, ok)); + s_addr += len; + } + return ok; +} + +bool mg_ota_flash_end(struct mg_flash *flash) { + char *base = (char *) flash->start + flash->size / 2; + bool ok = false; + if (s_size) { + size_t size = (size_t) (s_addr - base); + uint32_t crc32 = mg_crc32(0, base, s_size); + if (size == s_size && crc32 == s_crc32) ok = true; + MG_DEBUG(("CRC: %x/%x, size: %lu/%lu, status: %s", s_crc32, crc32, s_size, + size, ok ? "ok" : "fail")); + s_size = 0; + if (ok) ok = flash->swap_fn(); + } + MG_INFO(("Finishing OTA: %s", ok ? "ok" : "fail")); + return ok; +} + #ifdef MG_ENABLE_LINES #line 1 "src/fmt.c" #endif diff --git a/mongoose.h b/mongoose.h index 727e5557a5..52a454fc4a 100644 --- a/mongoose.h +++ b/mongoose.h @@ -2643,6 +2643,7 @@ void mg_rpc_list(struct mg_rpc_req *r); #define MG_OTA_NONE 0 // No OTA support #define MG_OTA_FLASH 1 // OTA via an internal flash #define MG_OTA_ESP32 2 // ESP32 OTA implementation +#define MG_OTA_STM32H5 3 // STM32H5 OTA implementation #define MG_OTA_CUSTOM 100 // Custom implementation #ifndef MG_OTA @@ -2676,6 +2677,19 @@ size_t mg_ota_size(int firmware); // Firmware size bool mg_ota_commit(void); // Commit current firmware bool mg_ota_rollback(void); // Rollback to the previous firmware MG_IRAM void mg_ota_boot(void); // Bootloader function + + +struct mg_flash { + void *start; // Address at which flash starts + size_t size; // Flash size + size_t align; // Write alignment + bool (*write_fn)(void *, const void *, size_t); // Write function + bool (*swap_fn)(void); // Swap partitions +}; + +bool mg_ota_flash_begin(size_t new_firmware_size, struct mg_flash *flash); +bool mg_ota_flash_write(const void *buf, size_t len, struct mg_flash *flash); +bool mg_ota_flash_end(struct mg_flash *flash); // Copyright (c) 2023 Cesanta Software Limited // All rights reserved diff --git a/src/device_stm32h5.c b/src/device_stm32h5.c index e0b1d4b5cb..8da8fe45e0 100644 --- a/src/device_stm32h5.c +++ b/src/device_stm32h5.c @@ -1,7 +1,8 @@ -#include "device.h" +// #include "device.h" +#include "flash.h" #include "log.h" -#if MG_DEVICE == MG_DEVICE_STM32H5 +#if MG_OTA == MG_OTA_STM32H5 #define FLASH_BASE 0x40022000 // Base address of the flash controller #define FLASH_KEYR (FLASH_BASE + 0x4) // See RM0481 7.11 @@ -13,6 +14,7 @@ #define FLASH_OPTSR_CUR (FLASH_BASE + 0x50) #define FLASH_OPTSR_PRG (FLASH_BASE + 0x54) +#if 0 void *mg_flash_start(void) { return (void *) 0x08000000; } @@ -28,6 +30,7 @@ size_t mg_flash_write_align(void) { int mg_flash_bank(void) { return MG_REG(FLASH_OPTCR) & MG_BIT(31) ? 2 : 1; } +#endif static void flash_unlock(void) { static bool unlocked = false; @@ -66,14 +69,14 @@ static bool flash_bank_is_swapped(void) { return MG_REG(FLASH_OPTCR) & MG_BIT(31); // RM0481 7.11.8 } -bool mg_flash_erase(void *location) { +static bool mg_stm32h5_erase(void *location) { bool ok = false; if (flash_page_start(location) == false) { MG_ERROR(("%p is not on a sector boundary")); } else { uintptr_t diff = (char *) location - (char *) mg_flash_start(); uint32_t sector = diff / mg_flash_sector_size(); - uint32_t saved_cr = MG_REG(FLASH_NSCR); // Save CR value + uint32_t saved_cr = MG_REG(FLASH_NSCR); // Save CR value flash_unlock(); flash_clear_err(); MG_REG(FLASH_NSCR) = 0; @@ -89,12 +92,12 @@ bool mg_flash_erase(void *location) { MG_DEBUG(("Erase sector %lu @ %p: %s. CR %#lx SR %#lx", sector, location, ok ? "ok" : "fail", MG_REG(FLASH_NSCR), MG_REG(FLASH_NSSR))); // mg_hexdump(location, 32); - MG_REG(FLASH_NSCR) = saved_cr; // Restore saved CR + MG_REG(FLASH_NSCR) = saved_cr; // Restore saved CR } return ok; } -bool mg_flash_swap_bank(void) { +static bool mg_stm32h5_swap(void) { uint32_t desired = flash_bank_is_swapped() ? 0 : MG_BIT(31); flash_unlock(); flash_clear_err(); @@ -106,7 +109,7 @@ bool mg_flash_swap_bank(void) { return true; } -bool mg_flash_write(void *addr, const void *buf, size_t len) { +static bool mg_stm32h5_write(void *addr, const void *buf, size_t len) { if ((len % mg_flash_write_align()) != 0) { MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align())); return false; @@ -121,7 +124,7 @@ bool mg_flash_write(void *addr, const void *buf, size_t len) { // MG_DEBUG(("Starting flash write %lu bytes @ %p", len, addr)); MG_REG(FLASH_NSCR) = MG_BIT(1); // Set programming flag while (ok && src < end) { - if (flash_page_start(dst) && mg_flash_erase(dst) == false) break; + if (flash_page_start(dst) && mg_stm32h5_erase(dst) == false) break; *(volatile uint32_t *) dst++ = *src++; flash_wait(); if (flash_is_err()) ok = false; @@ -134,8 +137,23 @@ bool mg_flash_write(void *addr, const void *buf, size_t len) { return ok; } -void mg_device_reset(void) { - // SCB->AIRCR = ((0x5fa << SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk); - *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; +static struct mg_flash s_mg_flash_stm32h5 = { + (void *) 0x08000000, // Start + 2 * 1024 * 1024, // Size, 2Mb + 16, // Align, 128 bit + mg_stm32h5_write, + mg_stm32h5_swap, +}; + +bool mg_ota_begin(size_t new_firmware_size) { + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_stm32h5); +} + +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_stm32h5); +} + +bool mg_ota_end(void) { + return mg_ota_flash_end(&s_mg_flash_stm32h5); } #endif diff --git a/src/flash.c b/src/flash.c new file mode 100644 index 0000000000..084943948e --- /dev/null +++ b/src/flash.c @@ -0,0 +1,65 @@ +#include "arch.h" +#include "flash.h" +#include "log.h" +#include "ota.h" + +static char *s_addr; // Current address to write to +static size_t s_size; // Firmware size to flash. In-progress indicator +static uint32_t s_crc32; // Firmware checksum + +bool mg_ota_flash_begin(size_t new_firmware_size, struct mg_flash *flash) { + bool ok = false; + if (s_size) { + MG_ERROR(("OTA already in progress. Call mg_ota_end()")); + } else { + size_t half = flash->size / 2; + s_crc32 = 0; + s_addr = (char *) flash->start + half; + MG_DEBUG(("FW %lu bytes, max %lu", new_firmware_size, half)); + if (new_firmware_size < half) { + ok = true; + s_size = new_firmware_size; + MG_INFO(("Starting OTA, firmware size %lu", s_size)); + } else { + MG_ERROR(("Firmware %lu is too big to fit %lu", new_firmware_size, half)); + } + } + return ok; +} + +bool mg_ota_flash_write(const void *buf, size_t len, struct mg_flash *flash) { + bool ok = false; + if (s_size == 0) { + MG_ERROR(("OTA is not started, call mg_ota_begin()")); + } else { + size_t len_aligned_down = MG_ROUND_DOWN(len, flash->align); + if (len_aligned_down) ok = flash->write_fn(s_addr, buf, len_aligned_down); + if (len_aligned_down < len) { + size_t left = len - len_aligned_down; + char tmp[flash->align]; + memset(tmp, 0xff, sizeof(tmp)); + memcpy(tmp, (char *) buf + len_aligned_down, left); + ok = flash->write_fn(s_addr + len_aligned_down, tmp, sizeof(tmp)); + } + s_crc32 = mg_crc32(s_crc32, (char *) buf, len); // Update CRC + MG_DEBUG(("%#x %p %lu -> %d", s_addr - len, buf, len, ok)); + s_addr += len; + } + return ok; +} + +bool mg_ota_flash_end(struct mg_flash *flash) { + char *base = (char *) flash->start + flash->size / 2; + bool ok = false; + if (s_size) { + size_t size = (size_t) (s_addr - base); + uint32_t crc32 = mg_crc32(0, base, s_size); + if (size == s_size && crc32 == s_crc32) ok = true; + MG_DEBUG(("CRC: %x/%x, size: %lu/%lu, status: %s", s_crc32, crc32, s_size, + size, ok ? "ok" : "fail")); + s_size = 0; + if (ok) ok = flash->swap_fn(); + } + MG_INFO(("Finishing OTA: %s", ok ? "ok" : "fail")); + return ok; +} diff --git a/src/flash.h b/src/flash.h new file mode 100644 index 0000000000..6f6d8a6497 --- /dev/null +++ b/src/flash.h @@ -0,0 +1,13 @@ +#include "arch.h" + +struct mg_flash { + void *start; // Address at which flash starts + size_t size; // Flash size + size_t align; // Write alignment + bool (*write_fn)(void *, const void *, size_t); // Write function + bool (*swap_fn)(void); // Swap partitions +}; + +bool mg_ota_flash_begin(size_t new_firmware_size, struct mg_flash *flash); +bool mg_ota_flash_write(const void *buf, size_t len, struct mg_flash *flash); +bool mg_ota_flash_end(struct mg_flash *flash); diff --git a/src/ota.h b/src/ota.h index 5109fc60aa..d8b63273d9 100644 --- a/src/ota.h +++ b/src/ota.h @@ -8,6 +8,7 @@ #define MG_OTA_NONE 0 // No OTA support #define MG_OTA_FLASH 1 // OTA via an internal flash #define MG_OTA_ESP32 2 // ESP32 OTA implementation +#define MG_OTA_STM32H5 3 // STM32H5 OTA implementation #define MG_OTA_CUSTOM 100 // Custom implementation #ifndef MG_OTA diff --git a/test/Makefile b/test/Makefile index def1a24358..8d753908f0 100644 --- a/test/Makefile +++ b/test/Makefile @@ -203,7 +203,7 @@ mongoose.c: Makefile $(wildcard ../src/*.c) $(wildcard ../src/drivers/*.c) cd .. && (export LC_ALL=C ; cat src/license.h; echo; echo '#include "mongoose.h"' ; (for F in src/*.c src/drivers/*.c ; do echo; echo '#ifdef MG_ENABLE_LINES'; echo "#line 1 \"$$F\""; echo '#endif'; cat $$F | sed -e 's,#include ".*,,'; done))> $@ mongoose.h: $(HDRS) Makefile - cd .. && (cat src/license.h; echo; echo '#ifndef MONGOOSE_H'; echo '#define MONGOOSE_H'; echo; cat src/version.h ; echo; echo '#ifdef __cplusplus'; echo 'extern "C" {'; echo '#endif'; cat src/arch.h src/arch_*.h src/net_ft.h src/net_lwip.h src/net_rl.h src/config.h src/str.h src/queue.h src/fmt.h src/printf.h src/log.h src/timer.h src/fs.h src/util.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/sha256.h src/tls_x25519.h src/tls_aes128.h src/tls_uecc.h src/tls_chacha20.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/tls_mbed.h src/tls_openssl.h src/ws.h src/sntp.h src/mqtt.h src/dns.h src/json.h src/rpc.h src/ota.h src/device.h src/net_builtin.h src/profile.h src/drivers/*.h | sed -e '/keep/! s,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@ + cd .. && (cat src/license.h; echo; echo '#ifndef MONGOOSE_H'; echo '#define MONGOOSE_H'; echo; cat src/version.h ; echo; echo '#ifdef __cplusplus'; echo 'extern "C" {'; echo '#endif'; cat src/arch.h src/arch_*.h src/net_ft.h src/net_lwip.h src/net_rl.h src/config.h src/str.h src/queue.h src/fmt.h src/printf.h src/log.h src/timer.h src/fs.h src/util.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/sha256.h src/tls_x25519.h src/tls_aes128.h src/tls_uecc.h src/tls_chacha20.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/tls_mbed.h src/tls_openssl.h src/ws.h src/sntp.h src/mqtt.h src/dns.h src/json.h src/rpc.h src/ota.h src/flash.h src/device.h src/net_builtin.h src/profile.h src/drivers/*.h | sed -e '/keep/! s,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@ clean: clean_examples clean_refprojs clean_tutorials clean_examples_embedded From 9b99f62311fa6bd006614d1f18207d5eb99f3222 Mon Sep 17 00:00:00 2001 From: "Sergio R. Caprile" Date: Wed, 23 Oct 2024 10:02:03 -0300 Subject: [PATCH 2/2] Refactor OTA API --- examples/device-dashboard/net.c | 50 - examples/esp32/device-dashboard/main/main.c | 27 +- .../device-dashboard/main/mongoose_config.h | 1 - examples/modbus-dashboard/net.c | 1 - examples/mqtt-dashboard/device/net.c | 45 +- .../mongoose_config.h | 3 +- .../mongoose_config.h | 2 - .../ch32v307-make-baremetal-builtin/main.c | 10 +- .../mongoose_config.h | 3 +- mongoose.c | 10930 ++++++++-------- mongoose.h | 87 +- .../web-ui-dashboard/net.c | 50 - src/device.h | 39 - src/device_ch32v307.c | 78 - src/device_dummy.c | 32 - src/device_flash.c | 172 - src/flash.c | 4 + src/flash.h | 12 +- src/ota.h | 43 +- src/ota_ch32v307.c | 112 + src/ota_dummy.c | 24 - src/ota_flash.c | 199 - src/{device_imxrt.c => ota_imxrt.c} | 287 +- src/ota_mcxn.c | 209 + src/{device_stm32h5.c => ota_stm32h5.c} | 66 +- src/{device_stm32h7.c => ota_stm32h7.c} | 123 +- test/Makefile | 2 +- 27 files changed, 6092 insertions(+), 6519 deletions(-) delete mode 100644 src/device.h delete mode 100644 src/device_ch32v307.c delete mode 100644 src/device_dummy.c delete mode 100644 src/device_flash.c create mode 100644 src/ota_ch32v307.c delete mode 100644 src/ota_flash.c rename src/{device_imxrt.c => ota_imxrt.c} (50%) create mode 100644 src/ota_mcxn.c rename src/{device_stm32h5.c => ota_stm32h5.c} (82%) rename src/{device_stm32h7.c => ota_stm32h7.c} (53%) diff --git a/examples/device-dashboard/net.c b/examples/device-dashboard/net.c index fae16969bd..41d5c28ab1 100644 --- a/examples/device-dashboard/net.c +++ b/examples/device-dashboard/net.c @@ -196,49 +196,9 @@ static void handle_firmware_upload(struct mg_connection *c, mg_http_reply(c, 500, "", "mg_ota_end() failed\n", tot); } else { mg_http_reply(c, 200, s_json_header, "true\n"); - if (data.len == 0) { - // Successful mg_ota_end() called, schedule device reboot - mg_timer_add(c->mgr, 500, 0, (void (*)(void *)) mg_device_reset, NULL); - } } } -static void handle_firmware_commit(struct mg_connection *c) { - mg_http_reply(c, 200, s_json_header, "%s\n", - mg_ota_commit() ? "true" : "false"); -} - -static void handle_firmware_rollback(struct mg_connection *c) { - mg_http_reply(c, 200, s_json_header, "%s\n", - mg_ota_rollback() ? "true" : "false"); -} - -static size_t print_status(void (*out)(char, void *), void *ptr, va_list *ap) { - int fw = va_arg(*ap, int); - return mg_xprintf(out, ptr, "{%m:%d,%m:%c%lx%c,%m:%u,%m:%u}\n", - MG_ESC("status"), mg_ota_status(fw), MG_ESC("crc32"), '"', - mg_ota_crc32(fw), '"', MG_ESC("size"), mg_ota_size(fw), - MG_ESC("timestamp"), mg_ota_timestamp(fw)); -} - -static void handle_firmware_status(struct mg_connection *c) { - mg_http_reply(c, 200, s_json_header, "[%M,%M]\n", print_status, - MG_FIRMWARE_CURRENT, print_status, MG_FIRMWARE_PREVIOUS); -} - -static void handle_device_reset(struct mg_connection *c) { - mg_http_reply(c, 200, s_json_header, "true\n"); - mg_timer_add(c->mgr, 500, 0, (void (*)(void *)) mg_device_reset, NULL); -} - -static void handle_device_eraselast(struct mg_connection *c) { - size_t ss = mg_flash_sector_size(), size = mg_flash_size(); - char *base = (char *) mg_flash_start(), *last = base + size - ss; - if (mg_flash_bank() == 2) last -= size / 2; - mg_flash_erase(last); - mg_http_reply(c, 200, s_json_header, "true\n"); -} - // HTTP request handler function static void fn(struct mg_connection *c, int ev, void *ev_data) { if (ev == MG_EV_ACCEPT) { @@ -270,16 +230,6 @@ static void fn(struct mg_connection *c, int ev, void *ev_data) { handle_settings_set(c, hm->body); } else if (mg_match(hm->uri, mg_str("/api/firmware/upload"), NULL)) { handle_firmware_upload(c, hm); - } else if (mg_match(hm->uri, mg_str("/api/firmware/commit"), NULL)) { - handle_firmware_commit(c); - } else if (mg_match(hm->uri, mg_str("/api/firmware/rollback"), NULL)) { - handle_firmware_rollback(c); - } else if (mg_match(hm->uri, mg_str("/api/firmware/status"), NULL)) { - handle_firmware_status(c); - } else if (mg_match(hm->uri, mg_str("/api/device/reset"), NULL)) { - handle_device_reset(c); - } else if (mg_match(hm->uri, mg_str("/api/device/eraselast"), NULL)) { - handle_device_eraselast(c); } else { struct mg_http_serve_opts opts; memset(&opts, 0, sizeof(opts)); diff --git a/examples/esp32/device-dashboard/main/main.c b/examples/esp32/device-dashboard/main/main.c index a5176dab60..43f28462d8 100644 --- a/examples/esp32/device-dashboard/main/main.c +++ b/examples/esp32/device-dashboard/main/main.c @@ -27,7 +27,15 @@ void app_main(void) { for (;;) mg_mgr_poll(&mgr, 1000); // Infinite event loop } -#if MG_DEVICE == MG_DEVICE_CUSTOM +#if MG_OTA == MG_OTA_CUSTOM +enum { + MG_OTA_UNAVAILABLE = 0, // No OTA information is present + MG_OTA_FIRST_BOOT = 1, // Device booting the first time after the OTA + MG_OTA_UNCOMMITTED = 2, // Ditto, but marking us for the rollback + MG_OTA_COMMITTED = 3 // The firmware is good +}; +enum { MG_FIRMWARE_CURRENT = 0, MG_FIRMWARE_PREVIOUS = 1 }; + void *mg_flash_start(void) { return NULL; } @@ -57,9 +65,7 @@ bool mg_flash_write(void *addr, const void *buf, size_t len) { void mg_device_reset(void) { esp_restart(); } -#endif -#if MG_OTA == MG_OTA_CUSTOM #include "esp_app_format.h" #include "esp_ota_ops.h" @@ -121,6 +127,14 @@ bool check_fw_header(const void* buf, size_t len) { return true; } +size_t mg_ota_size(int fw) { + const esp_partition_t *p = get_partition_from_fw(fw); + if (NULL == p) { + return 0; + } + return p->size; +} + bool mg_ota_begin(size_t new_firmware_size) { rx_checked = false; if (s_size) { @@ -255,11 +269,4 @@ uint32_t mg_ota_timestamp(int fw) { return mktime(&datetime); } -size_t mg_ota_size(int fw) { - const esp_partition_t *p = get_partition_from_fw(fw); - if (NULL == p) { - return 0; - } - return p->size; -} #endif diff --git a/examples/esp32/device-dashboard/main/mongoose_config.h b/examples/esp32/device-dashboard/main/mongoose_config.h index 239bdeebeb..6395e9a692 100644 --- a/examples/esp32/device-dashboard/main/mongoose_config.h +++ b/examples/esp32/device-dashboard/main/mongoose_config.h @@ -3,4 +3,3 @@ #define MG_ENABLE_PACKED_FS 1 #define MG_TLS MG_TLS_NONE // change to 'MG_TLS_MBED' to enable TLS #define MG_OTA MG_OTA_CUSTOM -#define MG_DEVICE MG_DEVICE_CUSTOM diff --git a/examples/modbus-dashboard/net.c b/examples/modbus-dashboard/net.c index a734f12e59..82dc27aa95 100644 --- a/examples/modbus-dashboard/net.c +++ b/examples/modbus-dashboard/net.c @@ -241,7 +241,6 @@ static void fn(struct mg_connection *c, int ev, void *ev_data) { } else if (mg_match(hm->uri, mg_str("/api/modbus/exec"), NULL)) { handle_modbus_exec(c, hm->body); } else if (mg_match(hm->uri, mg_str("/api/device/reset"), NULL)) { - mg_timer_add(c->mgr, 500, 0, (void (*)(void *)) mg_device_reset, NULL); mg_http_reply(c, 200, s_json_header, "true\n"); } else { struct mg_http_serve_opts opts; diff --git a/examples/mqtt-dashboard/device/net.c b/examples/mqtt-dashboard/device/net.c index 5964dae8ed..6f0ccc221d 100644 --- a/examples/mqtt-dashboard/device/net.c +++ b/examples/mqtt-dashboard/device/net.c @@ -53,15 +53,6 @@ static void set_device_id(void) { g_device_id = strdup(buf); } -static size_t print_fw_status(void (*out)(char, void *), void *ptr, - va_list *ap) { - int fw = va_arg(*ap, int); - return mg_xprintf(out, ptr, "{%m:%d,%m:%c%lx%c,%m:%u,%m:%u}", - MG_ESC("status"), mg_ota_status(fw), MG_ESC("crc32"), '"', - mg_ota_crc32(fw), '"', MG_ESC("size"), mg_ota_size(fw), - MG_ESC("timestamp"), mg_ota_timestamp(fw)); -} - static size_t print_shorts(void (*out)(char, void *), void *ptr, va_list *ap) { uint16_t *array = va_arg(*ap, uint16_t *); int i, len = 0, num_elems = va_arg(*ap, int); @@ -87,7 +78,7 @@ static void publish_status(struct mg_connection *c) { // Print JSON notification into the io buffer mg_xprintf(mg_pfn_iobuf, &io, - "{%m:%m,%m:{%m:%m,%m:%d,%m:%d,%m:[%M],%m:[%M],%m:%M,%m:%M}}", // + "{%m:%m,%m:{%m:%m,%m:%d,%m:%d,%m:[%M],%m:[%M]}}", // MG_ESC("method"), MG_ESC("status.notify"), MG_ESC("params"), // MG_ESC("status"), MG_ESC("online"), // MG_ESC(("log_level")), s_device_config.log_level, // @@ -95,9 +86,7 @@ static void publish_status(struct mg_connection *c) { MG_ESC(("pin_map")), print_shorts, s_device_config.pin_map, s_device_config.pin_count, // MG_ESC(("pin_state")), print_bools, s_device_config.pin_state, - s_device_config.pin_count, // - MG_ESC(("crnt_fw")), print_fw_status, MG_FIRMWARE_CURRENT, // - MG_ESC(("prev_fw")), print_fw_status, MG_FIRMWARE_PREVIOUS); + s_device_config.pin_count); memset(&pub_opts, 0, sizeof(pub_opts)); mg_snprintf(topic, sizeof(topic), "%s/%s/status", g_root_topic, g_device_id); @@ -160,27 +149,6 @@ static void rpc_config_set(struct mg_rpc_req *r) { } } -static void rpc_ota_commit(struct mg_rpc_req *r) { - if (mg_ota_commit()) { - mg_rpc_ok(r, "%m", MG_ESC("ok")); - } else { - mg_rpc_err(r, 1, "Failed to commit the firmware"); - } -} - -static void rpc_device_reset(struct mg_rpc_req *r) { - mg_rpc_ok(r, "%m", MG_ESC("ok")); - mg_timer_add(s_conn->mgr, 500, 0, (void (*)(void *)) mg_device_reset, NULL); -} - -static void rpc_ota_rollback(struct mg_rpc_req *r) { - if (mg_ota_rollback()) { - mg_rpc_ok(r, "%m", MG_ESC("ok")); - } else { - mg_rpc_err(r, 1, "Failed to rollback to the previous firmware"); - } -} - static void rpc_ota_upload(struct mg_rpc_req *r) { long ofs = mg_json_get_long(r->frame, "$.params.offset", -1); long tot = mg_json_get_long(r->frame, "$.params.total", -1); @@ -200,10 +168,6 @@ static void rpc_ota_upload(struct mg_rpc_req *r) { mg_rpc_err(r, 1, "mg_ota_end() failed\n", tot); } else { mg_rpc_ok(r, "%m", MG_ESC("ok")); - if (len == 0) { // Successful mg_ota_end() called, schedule device reboot - mg_timer_add(s_conn->mgr, 500, 0, (void (*)(void *)) mg_device_reset, - NULL); - } } free(buf); } @@ -297,11 +261,8 @@ void web_init(struct mg_mgr *mgr) { // Configure JSON-RPC functions we're going to handle mg_rpc_add(&s_rpc, mg_str("config.set"), rpc_config_set, NULL); - mg_rpc_add(&s_rpc, mg_str("ota.commit"), rpc_ota_commit, NULL); - mg_rpc_add(&s_rpc, mg_str("ota.rollback"), rpc_ota_rollback, NULL); mg_rpc_add(&s_rpc, mg_str("ota.upload"), rpc_ota_upload, NULL); - mg_rpc_add(&s_rpc, mg_str("device.reset"), rpc_device_reset, NULL); - + mg_timer_add(mgr, 3000, MG_TIMER_REPEAT | MG_TIMER_RUN_NOW, timer_reconnect, mgr); mg_timer_add(mgr, ping_interval_ms, MG_TIMER_REPEAT, timer_ping, mgr); diff --git a/examples/nxp/rt1170-evk-make-baremetal-builtin/mongoose_config.h b/examples/nxp/rt1170-evk-make-baremetal-builtin/mongoose_config.h index 61211e1967..24e8df57b3 100644 --- a/examples/nxp/rt1170-evk-make-baremetal-builtin/mongoose_config.h +++ b/examples/nxp/rt1170-evk-make-baremetal-builtin/mongoose_config.h @@ -2,8 +2,7 @@ // See https://mongoose.ws/documentation/#build-options #define MG_ARCH MG_ARCH_NEWLIB -#define MG_OTA MG_OTA_FLASH -#define MG_DEVICE MG_DEVICE_RT1060 +#define MG_OTA MG_OTA_RT1060 #define MG_ENABLE_TCPIP 1 #define MG_ENABLE_DRIVER_IMXRT 1 diff --git a/examples/renesas/ek-ra6m4-make-baremetal-builtin/mongoose_config.h b/examples/renesas/ek-ra6m4-make-baremetal-builtin/mongoose_config.h index 91e1a0745c..2da086e922 100644 --- a/examples/renesas/ek-ra6m4-make-baremetal-builtin/mongoose_config.h +++ b/examples/renesas/ek-ra6m4-make-baremetal-builtin/mongoose_config.h @@ -2,8 +2,6 @@ // See https://mongoose.ws/documentation/#build-options #define MG_ARCH MG_ARCH_NEWLIB -#define MG_OTA MG_OTA_FLASH -#define MG_DEVICE MG_DEVICE_STM32H5 #define MG_ENABLE_TCPIP 1 #define MG_ENABLE_CUSTOM_MILLIS 1 diff --git a/examples/wch/ch32v307-make-baremetal-builtin/main.c b/examples/wch/ch32v307-make-baremetal-builtin/main.c index 118763f4b2..887983f44c 100644 --- a/examples/wch/ch32v307-make-baremetal-builtin/main.c +++ b/examples/wch/ch32v307-make-baremetal-builtin/main.c @@ -7,16 +7,16 @@ #define BLINK_PERIOD_MS 1000 // LED_PIN blinking period in millis // This flash space resides at after the 0-wait 320k area -static char *s_flash_space = (char *) (0x8000000 + 320 * 1024); +// static char *s_flash_space = (char *) (0x8000000 + 320 * 1024); bool web_load_settings(void *buf, size_t len) { - if (*(uint32_t *) s_flash_space != SETTINGS_MAGIC) return false; - memcpy(buf, s_flash_space, len); - return true; + (void) buf, (void) len; + return false; } bool web_save_settings(void *buf, size_t len) { - return mg_flash_write(s_flash_space, buf, len); + (void) buf, (void) len; + return true; } static void timer_fn(void *arg) { diff --git a/examples/wch/ch32v307-make-baremetal-builtin/mongoose_config.h b/examples/wch/ch32v307-make-baremetal-builtin/mongoose_config.h index d4d0185167..fa99916d53 100644 --- a/examples/wch/ch32v307-make-baremetal-builtin/mongoose_config.h +++ b/examples/wch/ch32v307-make-baremetal-builtin/mongoose_config.h @@ -2,8 +2,7 @@ // See https://mongoose.ws/documentation/#build-options #define MG_ARCH MG_ARCH_NEWLIB -#define MG_OTA MG_OTA_FLASH -#define MG_DEVICE MG_DEVICE_CH32V307 +#define MG_OTA MG_OTA_CH32V307 #define MG_ENABLE_TCPIP 1 #define MG_ENABLE_CUSTOM_MILLIS 1 diff --git a/mongoose.c b/mongoose.c index e895c9c900..0b72f6631a 100644 --- a/mongoose.c +++ b/mongoose.c @@ -117,6397 +117,6291 @@ size_t mg_base64_decode(const char *src, size_t n, char *dst, size_t dl) { } #ifdef MG_ENABLE_LINES -#line 1 "src/device_ch32v307.c" +#line 1 "src/dns.c" #endif -#if MG_DEVICE == MG_DEVICE_CH32V307 -// RM: https://www.wch-ic.com/downloads/CH32FV2x_V3xRM_PDF.html - -#define FLASH_BASE 0x40022000 -#define FLASH_ACTLR (FLASH_BASE + 0) -#define FLASH_KEYR (FLASH_BASE + 4) -#define FLASH_OBKEYR (FLASH_BASE + 8) -#define FLASH_STATR (FLASH_BASE + 12) -#define FLASH_CTLR (FLASH_BASE + 16) -#define FLASH_ADDR (FLASH_BASE + 20) -#define FLASH_OBR (FLASH_BASE + 28) -#define FLASH_WPR (FLASH_BASE + 32) -void *mg_flash_start(void) { - return (void *) 0x08000000; -} -size_t mg_flash_size(void) { - return 480 * 1024; // First 320k is 0-wait -} -size_t mg_flash_sector_size(void) { - return 4096; -} -size_t mg_flash_write_align(void) { - return 4; -} -int mg_flash_bank(void) { - return 0; -} -void mg_device_reset(void) { - *((volatile uint32_t *) 0xbeef0000) |= 1U << 7; // NVIC_SystemReset() -} -static void flash_unlock(void) { - static bool unlocked; - if (unlocked == false) { - MG_REG(FLASH_KEYR) = 0x45670123; - MG_REG(FLASH_KEYR) = 0xcdef89ab; - unlocked = true; - } -} -static void flash_wait(void) { - while (MG_REG(FLASH_STATR) & MG_BIT(0)) (void) 0; -} -bool mg_flash_erase(void *addr) { - //MG_INFO(("%p", addr)); - flash_unlock(); - flash_wait(); - MG_REG(FLASH_ADDR) = (uint32_t) addr; - MG_REG(FLASH_CTLR) |= MG_BIT(1) | MG_BIT(6); // PER | STRT; - flash_wait(); - return true; -} -static bool is_page_boundary(const void *addr) { - uint32_t val = (uint32_t) addr; - return (val & (mg_flash_sector_size() - 1)) == 0; -} -bool mg_flash_write(void *addr, const void *buf, size_t len) { - //MG_INFO(("%p %p %lu", addr, buf, len)); - //mg_hexdump(buf, len); - flash_unlock(); - const uint16_t *src = (uint16_t *) buf, *end = &src[len / 2]; - uint16_t *dst = (uint16_t *) addr; - MG_REG(FLASH_CTLR) |= MG_BIT(0); // Set PG - //MG_INFO(("CTLR: %#lx", MG_REG(FLASH_CTLR))); - while (src < end) { - if (is_page_boundary(dst)) mg_flash_erase(dst); - *dst++ = *src++; - flash_wait(); - } - MG_REG(FLASH_CTLR) &= ~MG_BIT(0); // Clear PG - return true; -} -#endif -#ifdef MG_ENABLE_LINES -#line 1 "src/device_dummy.c" -#endif +struct dns_data { + struct dns_data *next; + struct mg_connection *c; + uint64_t expire; + uint16_t txnid; +}; +static void mg_sendnsreq(struct mg_connection *, struct mg_str *, int, + struct mg_dns *, bool); -#if MG_DEVICE == MG_DEVICE_NONE -void *mg_flash_start(void) { - return NULL; -} -size_t mg_flash_size(void) { - return 0; -} -size_t mg_flash_sector_size(void) { - return 0; -} -size_t mg_flash_write_align(void) { - return 0; -} -int mg_flash_bank(void) { - return 0; -} -bool mg_flash_erase(void *location) { - (void) location; - return false; +static void mg_dns_free(struct dns_data **head, struct dns_data *d) { + LIST_DELETE(struct dns_data, head, d); + free(d); } -bool mg_flash_swap_bank(void) { - return true; + +void mg_resolve_cancel(struct mg_connection *c) { + struct dns_data *tmp, *d; + struct dns_data **head = (struct dns_data **) &c->mgr->active_dns_requests; + for (d = *head; d != NULL; d = tmp) { + tmp = d->next; + if (d->c == c) mg_dns_free(head, d); + } } -bool mg_flash_write(void *addr, const void *buf, size_t len) { - (void) addr, (void) buf, (void) len; - return false; + +static size_t mg_dns_parse_name_depth(const uint8_t *s, size_t len, size_t ofs, + char *to, size_t tolen, size_t j, + int depth) { + size_t i = 0; + if (tolen > 0 && depth == 0) to[0] = '\0'; + if (depth > 5) return 0; + // MG_INFO(("ofs %lx %x %x", (unsigned long) ofs, s[ofs], s[ofs + 1])); + while (ofs + i + 1 < len) { + size_t n = s[ofs + i]; + if (n == 0) { + i++; + break; + } + if (n & 0xc0) { + size_t ptr = (((n & 0x3f) << 8) | s[ofs + i + 1]); // 12 is hdr len + // MG_INFO(("PTR %lx", (unsigned long) ptr)); + if (ptr + 1 < len && (s[ptr] & 0xc0) == 0 && + mg_dns_parse_name_depth(s, len, ptr, to, tolen, j, depth + 1) == 0) + return 0; + i += 2; + break; + } + if (ofs + i + n + 1 >= len) return 0; + if (j > 0) { + if (j < tolen) to[j] = '.'; + j++; + } + if (j + n < tolen) memcpy(&to[j], &s[ofs + i + 1], n); + j += n; + i += n + 1; + if (j < tolen) to[j] = '\0'; // Zero-terminate this chunk + // MG_INFO(("--> [%s]", to)); + } + if (tolen > 0) to[tolen - 1] = '\0'; // Make sure make sure it is nul-term + return i; } -void mg_device_reset(void) { + +static size_t mg_dns_parse_name(const uint8_t *s, size_t n, size_t ofs, + char *dst, size_t dstlen) { + return mg_dns_parse_name_depth(s, n, ofs, dst, dstlen, 0, 0); } -#endif -#ifdef MG_ENABLE_LINES -#line 1 "src/device_flash.c" -#endif +size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs, + bool is_question, struct mg_dns_rr *rr) { + const uint8_t *s = buf + ofs, *e = &buf[len]; + memset(rr, 0, sizeof(*rr)); + if (len < sizeof(struct mg_dns_header)) return 0; // Too small + if (len > 512) return 0; // Too large, we don't expect that + if (s >= e) return 0; // Overflow -#if MG_DEVICE == MG_DEVICE_STM32H7 || MG_DEVICE == MG_DEVICE_STM32H5 || \ - MG_DEVICE == MG_DEVICE_RT1020 || MG_DEVICE == MG_DEVICE_RT1060 -// Flash can be written only if it is erased. Erased flash is 0xff (all bits 1) -// Writes must be mg_flash_write_align() - aligned. Thus if we want to save an -// object, we pad it at the end for alignment. -// -// Objects in the flash sector are stored sequentially: -// | 32-bit size | 32-bit KEY | ..data.. | ..pad.. | 32-bit size | ...... -// -// In order to get to the next object, read its size, then align up. + if ((rr->nlen = (uint16_t) mg_dns_parse_name(buf, len, ofs, NULL, 0)) == 0) + return 0; + s += rr->nlen + 4; + if (s > e) return 0; + rr->atype = (uint16_t) (((uint16_t) s[-4] << 8) | s[-3]); + rr->aclass = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]); + if (is_question) return (size_t) (rr->nlen + 4); -// Traverse the list of saved objects -size_t mg_flash_next(char *p, char *end, uint32_t *key, size_t *size) { - size_t aligned_size = 0, align = mg_flash_write_align(), left = end - p; - uint32_t *p32 = (uint32_t *) p, min_size = sizeof(uint32_t) * 2; - if (p32[0] != 0xffffffff && left > MG_ROUND_UP(min_size, align)) { - if (size) *size = (size_t) p32[0]; - if (key) *key = p32[1]; - aligned_size = MG_ROUND_UP(p32[0] + sizeof(uint32_t) * 2, align); - if (left < aligned_size) aligned_size = 0; // Out of bounds, fail - } - return aligned_size; + s += 6; + if (s > e) return 0; + rr->alen = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]); + if (s + rr->alen > e) return 0; + return (size_t) (rr->nlen + rr->alen + 10); } -// Return the last sector of Bank 2 -static char *flash_last_sector(void) { - size_t ss = mg_flash_sector_size(), size = mg_flash_size(); - char *base = (char *) mg_flash_start(), *last = base + size - ss; - if (mg_flash_bank() == 2) last -= size / 2; - return last; -} +bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *dm) { + const struct mg_dns_header *h = (struct mg_dns_header *) buf; + struct mg_dns_rr rr; + size_t i, n, num_answers, ofs = sizeof(*h); + memset(dm, 0, sizeof(*dm)); -// Find a saved object with a given key -bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t len) { - char *base = (char *) mg_flash_start(), *s = (char *) sector, *res = NULL; - size_t ss = mg_flash_sector_size(), ofs = 0, n, sz; - bool ok = false; - if (s == NULL) s = flash_last_sector(); - if (s < base || s >= base + mg_flash_size()) { - MG_ERROR(("%p is outsize of flash", sector)); - } else if (((s - base) % ss) != 0) { - MG_ERROR(("%p is not a sector boundary", sector)); - } else { - uint32_t k, scanned = 0; - while ((n = mg_flash_next(s + ofs, s + ss, &k, &sz)) > 0) { - // MG_DEBUG((" > obj %lu, ofs %lu, key %x/%x", scanned, ofs, k, key)); - // mg_hexdump(s + ofs, n); - if (k == key && sz == len) { - res = s + ofs + sizeof(uint32_t) * 2; - memcpy(buf, res, len); // Copy object - ok = true; // Keep scanning for the newer versions of it - } - ofs += n, scanned++; - } - MG_DEBUG(("Scanned %u objects, key %x is @ %p", scanned, key, res)); + if (len < sizeof(*h)) return 0; // Too small, headers dont fit + if (mg_ntohs(h->num_questions) > 1) return 0; // Sanity + num_answers = mg_ntohs(h->num_answers); + if (num_answers > 10) { + MG_DEBUG(("Got %u answers, ignoring beyond 10th one", num_answers)); + num_answers = 10; // Sanity cap } - return ok; -} + dm->txnid = mg_ntohs(h->txnid); -// For all saved objects in the sector, delete old versions of objects -static void mg_flash_sector_cleanup(char *sector) { - // Buffer all saved objects into an IO buffer (backed by RAM) - // erase sector, and re-save them. - struct mg_iobuf io = {0, 0, 0, 2048}; - size_t ss = mg_flash_sector_size(); - size_t n, size, size2, ofs = 0, hs = sizeof(uint32_t) * 2; - uint32_t key; - // Traverse all objects - MG_DEBUG(("Cleaning up sector %p", sector)); - while ((n = mg_flash_next(sector + ofs, sector + ss, &key, &size)) > 0) { - // Delete an old copy of this object in the cache - for (size_t o = 0; o < io.len; o += size2 + hs) { - uint32_t k = *(uint32_t *) (io.buf + o + sizeof(uint32_t)); - size2 = *(uint32_t *) (io.buf + o); - if (k == key) { - mg_iobuf_del(&io, o, size2 + hs); - break; - } - } - // And add the new copy - mg_iobuf_add(&io, io.len, sector + ofs, size + hs); + for (i = 0; i < mg_ntohs(h->num_questions); i++) { + if ((n = mg_dns_parse_rr(buf, len, ofs, true, &rr)) == 0) return false; + // MG_INFO(("Q %lu %lu %hu/%hu", ofs, n, rr.atype, rr.aclass)); ofs += n; } - // All objects are cached in RAM now - if (mg_flash_erase(sector)) { // Erase sector. If successful, - for (ofs = 0; ofs < io.len; ofs += size + hs) { // Traverse cached objects - size = *(uint32_t *) (io.buf + ofs); - key = *(uint32_t *) (io.buf + ofs + sizeof(uint32_t)); - mg_flash_save(sector, key, io.buf + ofs + hs, size); // Save to flash + for (i = 0; i < num_answers; i++) { + if ((n = mg_dns_parse_rr(buf, len, ofs, false, &rr)) == 0) return false; + // MG_INFO(("A -- %lu %lu %hu/%hu %s", ofs, n, rr.atype, rr.aclass, + // dm->name)); + mg_dns_parse_name(buf, len, ofs, dm->name, sizeof(dm->name)); + ofs += n; + + if (rr.alen == 4 && rr.atype == 1 && rr.aclass == 1) { + dm->addr.is_ip6 = false; + memcpy(&dm->addr.ip, &buf[ofs - 4], 4); + dm->resolved = true; + break; // Return success + } else if (rr.alen == 16 && rr.atype == 28 && rr.aclass == 1) { + dm->addr.is_ip6 = true; + memcpy(&dm->addr.ip, &buf[ofs - 16], 16); + dm->resolved = true; + break; // Return success } } - mg_iobuf_free(&io); + return true; } -// Save an object with a given key - append to the end of an object list -bool mg_flash_save(void *sector, uint32_t key, const void *buf, size_t len) { - char *base = (char *) mg_flash_start(), *s = (char *) sector; - size_t ss = mg_flash_sector_size(), ofs = 0, n; - bool ok = false; - if (s == NULL) s = flash_last_sector(); - if (s < base || s >= base + mg_flash_size()) { - MG_ERROR(("%p is outsize of flash", sector)); - } else if (((s - base) % ss) != 0) { - MG_ERROR(("%p is not a sector boundary", sector)); - } else { - char ab[mg_flash_write_align()]; // Aligned write block - uint32_t hdr[2] = {(uint32_t) len, key}; - size_t needed = sizeof(hdr) + len; - size_t needed_aligned = MG_ROUND_UP(needed, sizeof(ab)); - while ((n = mg_flash_next(s + ofs, s + ss, NULL, NULL)) > 0) ofs += n; - - // If there is not enough space left, cleanup sector and re-eval ofs - if (ofs + needed_aligned >= ss) { - mg_flash_sector_cleanup(s); - ofs = 0; - while ((n = mg_flash_next(s + ofs, s + ss, NULL, NULL)) > 0) ofs += n; +static void dns_cb(struct mg_connection *c, int ev, void *ev_data) { + struct dns_data *d, *tmp; + struct dns_data **head = (struct dns_data **) &c->mgr->active_dns_requests; + if (ev == MG_EV_POLL) { + uint64_t now = *(uint64_t *) ev_data; + for (d = *head; d != NULL; d = tmp) { + tmp = d->next; + // MG_DEBUG ("%lu %lu dns poll", d->expire, now)); + if (now > d->expire) mg_error(d->c, "DNS timeout"); } - - if (ofs + needed_aligned <= ss) { - // Enough space to save this object - if (sizeof(ab) < sizeof(hdr)) { - // Flash write granularity is 32 bit or less, write with no buffering - ok = mg_flash_write(s + ofs, hdr, sizeof(hdr)); - if (ok) mg_flash_write(s + ofs + sizeof(hdr), buf, len); - } else { - // Flash granularity is sizeof(hdr) or more. We need to save in - // 3 chunks: initial block, bulk, rest. This is because we have - // two memory chunks to write: hdr and buf, on aligned boundaries. - n = sizeof(ab) - sizeof(hdr); // Initial chunk that we write - if (n > len) n = len; // is - memset(ab, 0xff, sizeof(ab)); // initialized to all-one - memcpy(ab, hdr, sizeof(hdr)); // contains the header (key + size) - memcpy(ab + sizeof(hdr), buf, n); // and an initial part of buf - MG_INFO(("saving initial block of %lu", sizeof(ab))); - ok = mg_flash_write(s + ofs, ab, sizeof(ab)); - if (ok && len > n) { - size_t n2 = MG_ROUND_DOWN(len - n, sizeof(ab)); - if (n2 > 0) { - MG_INFO(("saving bulk, %lu", n2)); - ok = mg_flash_write(s + ofs + sizeof(ab), (char *) buf + n, n2); - } - if (ok && len > n) { - size_t n3 = len - n - n2; - if (n3 > sizeof(ab)) n3 = sizeof(ab); - memset(ab, 0xff, sizeof(ab)); - memcpy(ab, (char *) buf + n + n2, n3); - MG_INFO(("saving rest, %lu", n3)); - ok = mg_flash_write(s + ofs + sizeof(ab) + n2, ab, sizeof(ab)); + } else if (ev == MG_EV_READ) { + struct mg_dns_message dm; + int resolved = 0; + if (mg_dns_parse(c->recv.buf, c->recv.len, &dm) == false) { + MG_ERROR(("Unexpected DNS response:")); + mg_hexdump(c->recv.buf, c->recv.len); + } else { + // MG_VERBOSE(("%s %d", dm.name, dm.resolved)); + for (d = *head; d != NULL; d = tmp) { + tmp = d->next; + // MG_INFO(("d %p %hu %hu", d, d->txnid, dm.txnid)); + if (dm.txnid != d->txnid) continue; + if (d->c->is_resolving) { + if (dm.resolved) { + dm.addr.port = d->c->rem.port; // Save port + d->c->rem = dm.addr; // Copy resolved address + MG_DEBUG( + ("%lu %s is %M", d->c->id, dm.name, mg_print_ip, &d->c->rem)); + mg_connect_resolved(d->c); +#if MG_ENABLE_IPV6 + } else if (dm.addr.is_ip6 == false && dm.name[0] != '\0' && + c->mgr->use_dns6 == false) { + struct mg_str x = mg_str(dm.name); + mg_sendnsreq(d->c, &x, c->mgr->dnstimeout, &c->mgr->dns6, true); +#endif + } else { + mg_error(d->c, "%s DNS lookup failed", dm.name); } + } else { + MG_ERROR(("%lu already resolved", d->c->id)); } + mg_dns_free(head, d); + resolved = 1; } - MG_DEBUG(("Saved %lu/%lu bytes @ %p, key %x: %d", len, needed_aligned, - s + ofs, key, ok)); - MG_DEBUG(("Sector space left: %lu bytes", ss - ofs - needed_aligned)); - } else { - MG_ERROR(("Sector is full")); + } + if (!resolved) MG_ERROR(("stray DNS reply")); + c->recv.len = 0; + } else if (ev == MG_EV_CLOSE) { + for (d = *head; d != NULL; d = tmp) { + tmp = d->next; + mg_error(d->c, "DNS error"); + mg_dns_free(head, d); } } - return ok; -} -#else -bool mg_flash_save(void *sector, uint32_t key, const void *buf, size_t len) { - (void) sector, (void) key, (void) buf, (void) len; - return false; } -bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t len) { - (void) sector, (void) key, (void) buf, (void) len; - return false; -} -#endif - -#ifdef MG_ENABLE_LINES -#line 1 "src/device_imxrt.c" -#endif - +static bool mg_dns_send(struct mg_connection *c, const struct mg_str *name, + uint16_t txnid, bool ipv6) { + struct { + struct mg_dns_header header; + uint8_t data[256]; + } pkt; + size_t i, n; + memset(&pkt, 0, sizeof(pkt)); + pkt.header.txnid = mg_htons(txnid); + pkt.header.flags = mg_htons(0x100); + pkt.header.num_questions = mg_htons(1); + for (i = n = 0; i < sizeof(pkt.data) - 5; i++) { + if (name->buf[i] == '.' || i >= name->len) { + pkt.data[n] = (uint8_t) (i - n); + memcpy(&pkt.data[n + 1], name->buf + n, i - n); + n = i + 1; + } + if (i >= name->len) break; + } + memcpy(&pkt.data[n], "\x00\x00\x01\x00\x01", 5); // A query + n += 5; + if (ipv6) pkt.data[n - 3] = 0x1c; // AAAA query + // memcpy(&pkt.data[n], "\xc0\x0c\x00\x1c\x00\x01", 6); // AAAA query + // n += 6; + return mg_send(c, &pkt, sizeof(pkt.header) + n); +} -#if MG_DEVICE == MG_DEVICE_RT1020 || MG_DEVICE == MG_DEVICE_RT1060 +static void mg_sendnsreq(struct mg_connection *c, struct mg_str *name, int ms, + struct mg_dns *dnsc, bool ipv6) { + struct dns_data *d = NULL; + if (dnsc->url == NULL) { + mg_error(c, "DNS server URL is NULL. Call mg_mgr_init()"); + } else if (dnsc->c == NULL) { + dnsc->c = mg_connect(c->mgr, dnsc->url, NULL, NULL); + if (dnsc->c != NULL) { + dnsc->c->pfn = dns_cb; + // dnsc->c->is_hexdumping = 1; + } + } + if (dnsc->c == NULL) { + mg_error(c, "resolver"); + } else if ((d = (struct dns_data *) calloc(1, sizeof(*d))) == NULL) { + mg_error(c, "resolve OOM"); + } else { + struct dns_data *reqs = (struct dns_data *) c->mgr->active_dns_requests; + d->txnid = reqs ? (uint16_t) (reqs->txnid + 1) : 1; + d->next = (struct dns_data *) c->mgr->active_dns_requests; + c->mgr->active_dns_requests = d; + d->expire = mg_millis() + (uint64_t) ms; + d->c = c; + c->is_resolving = 1; + MG_VERBOSE(("%lu resolving %.*s @ %s, txnid %hu", c->id, (int) name->len, + name->buf, dnsc->url, d->txnid)); + if (!mg_dns_send(dnsc->c, name, d->txnid, ipv6)) { + mg_error(dnsc->c, "DNS send"); + } + } +} -struct mg_flexspi_lut_seq { - uint8_t seqNum; - uint8_t seqId; - uint16_t reserved; -}; +void mg_resolve(struct mg_connection *c, const char *url) { + struct mg_str host = mg_url_host(url); + c->rem.port = mg_htons(mg_url_port(url)); + if (mg_aton(host, &c->rem)) { + // host is an IP address, do not fire name resolution + mg_connect_resolved(c); + } else { + // host is not an IP, send DNS resolution request + struct mg_dns *dns = c->mgr->use_dns6 ? &c->mgr->dns6 : &c->mgr->dns4; + mg_sendnsreq(c, &host, c->mgr->dnstimeout, dns, c->mgr->use_dns6); + } +} -struct mg_flexspi_mem_config { - uint32_t tag; - uint32_t version; - uint32_t reserved0; - uint8_t readSampleClkSrc; - uint8_t csHoldTime; - uint8_t csSetupTime; - uint8_t columnAddressWidth; - uint8_t deviceModeCfgEnable; - uint8_t deviceModeType; - uint16_t waitTimeCfgCommands; - struct mg_flexspi_lut_seq deviceModeSeq; - uint32_t deviceModeArg; - uint8_t configCmdEnable; - uint8_t configModeType[3]; - struct mg_flexspi_lut_seq configCmdSeqs[3]; - uint32_t reserved1; - uint32_t configCmdArgs[3]; - uint32_t reserved2; - uint32_t controllerMiscOption; - uint8_t deviceType; - uint8_t sflashPadType; - uint8_t serialClkFreq; - uint8_t lutCustomSeqEnable; - uint32_t reserved3[2]; - uint32_t sflashA1Size; - uint32_t sflashA2Size; - uint32_t sflashB1Size; - uint32_t sflashB2Size; - uint32_t csPadSettingOverride; - uint32_t sclkPadSettingOverride; - uint32_t dataPadSettingOverride; - uint32_t dqsPadSettingOverride; - uint32_t timeoutInMs; - uint32_t commandInterval; - uint16_t dataValidTime[2]; - uint16_t busyOffset; - uint16_t busyBitPolarity; - uint32_t lookupTable[64]; - struct mg_flexspi_lut_seq lutCustomSeq[12]; - uint32_t reserved4[4]; -}; +#ifdef MG_ENABLE_LINES +#line 1 "src/event.c" +#endif -struct mg_flexspi_nor_config { - struct mg_flexspi_mem_config memConfig; - uint32_t pageSize; - uint32_t sectorSize; - uint8_t ipcmdSerialClkFreq; - uint8_t isUniformBlockSize; - uint8_t reserved0[2]; - uint8_t serialNorType; - uint8_t needExitNoCmdMode; - uint8_t halfClkForNonReadCmd; - uint8_t needRestoreNoCmdMode; - uint32_t blockSize; - uint32_t reserve2[11]; -}; -/* FLEXSPI memory config block related defintions */ -#define MG_FLEXSPI_CFG_BLK_TAG (0x42464346UL) // ascii "FCFB" Big Endian -#define MG_FLEXSPI_CFG_BLK_VERSION (0x56010400UL) // V1.4.0 -#define MG_FLEXSPI_LUT_SEQ(cmd0, pad0, op0, cmd1, pad1, op1) \ - (MG_FLEXSPI_LUT_OPERAND0(op0) | MG_FLEXSPI_LUT_NUM_PADS0(pad0) | MG_FLEXSPI_LUT_OPCODE0(cmd0) | \ - MG_FLEXSPI_LUT_OPERAND1(op1) | MG_FLEXSPI_LUT_NUM_PADS1(pad1) | MG_FLEXSPI_LUT_OPCODE1(cmd1)) -#define MG_CMD_SDR 0x01 -#define MG_CMD_DDR 0x21 -#define MG_DUMMY_SDR 0x0C -#define MG_DUMMY_DDR 0x2C -#define MG_RADDR_SDR 0x02 -#define MG_RADDR_DDR 0x22 -#define MG_READ_SDR 0x09 -#define MG_READ_DDR 0x29 -#define MG_WRITE_SDR 0x08 -#define MG_WRITE_DDR 0x28 -#define MG_STOP 0 -#define MG_FLEXSPI_1PAD 0 -#define MG_FLEXSPI_2PAD 1 -#define MG_FLEXSPI_4PAD 2 -#define MG_FLEXSPI_8PAD 3 -#define MG_FLEXSPI_QSPI_LUT \ - { \ - [0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0xEB, MG_RADDR_SDR, MG_FLEXSPI_4PAD, \ - 0x18), \ - [1] = MG_FLEXSPI_LUT_SEQ(MG_DUMMY_SDR, MG_FLEXSPI_4PAD, 0x06, MG_READ_SDR, MG_FLEXSPI_4PAD, \ - 0x04), \ - [4 * 1 + 0] = \ - MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x05, MG_READ_SDR, MG_FLEXSPI_1PAD, 0x04), \ - [4 * 3 + 0] = \ - MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x06, MG_STOP, MG_FLEXSPI_1PAD, 0x0), \ - [4 * 5 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x20, MG_RADDR_SDR, \ - MG_FLEXSPI_1PAD, 0x18), \ - [4 * 8 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0xD8, MG_RADDR_SDR, \ - MG_FLEXSPI_1PAD, 0x18), \ - [4 * 9 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x02, MG_RADDR_SDR, \ - MG_FLEXSPI_1PAD, 0x18), \ - [4 * 9 + 1] = \ - MG_FLEXSPI_LUT_SEQ(MG_WRITE_SDR, MG_FLEXSPI_1PAD, 0x04, MG_STOP, MG_FLEXSPI_1PAD, 0x0), \ - [4 * 11 + 0] = \ - MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x60, MG_STOP, MG_FLEXSPI_1PAD, 0x0), \ +void mg_call(struct mg_connection *c, int ev, void *ev_data) { +#if MG_ENABLE_PROFILE + const char *names[] = { + "EV_ERROR", "EV_OPEN", "EV_POLL", "EV_RESOLVE", + "EV_CONNECT", "EV_ACCEPT", "EV_TLS_HS", "EV_READ", + "EV_WRITE", "EV_CLOSE", "EV_HTTP_MSG", "EV_HTTP_CHUNK", + "EV_WS_OPEN", "EV_WS_MSG", "EV_WS_CTL", "EV_MQTT_CMD", + "EV_MQTT_MSG", "EV_MQTT_OPEN", "EV_SNTP_TIME", "EV_USER"}; + if (ev != MG_EV_POLL && ev < (int) (sizeof(names) / sizeof(names[0]))) { + MG_PROF_ADD(c, names[ev]); } +#endif + // Fire protocol handler first, user handler second. See #2559 + if (c->pfn != NULL) c->pfn(c, ev, ev_data); + if (c->fn != NULL) c->fn(c, ev, ev_data); +} -#define MG_FLEXSPI_LUT_OPERAND0(x) (((uint32_t) (((uint32_t) (x)))) & 0xFFU) -#define MG_FLEXSPI_LUT_NUM_PADS0(x) (((uint32_t) (((uint32_t) (x)) << 8U)) & 0x300U) -#define MG_FLEXSPI_LUT_OPCODE0(x) (((uint32_t) (((uint32_t) (x)) << 10U)) & 0xFC00U) -#define MG_FLEXSPI_LUT_OPERAND1(x) (((uint32_t) (((uint32_t) (x)) << 16U)) & 0xFF0000U) -#define MG_FLEXSPI_LUT_NUM_PADS1(x) (((uint32_t) (((uint32_t) (x)) << 24U)) & 0x3000000U) -#define MG_FLEXSPI_LUT_OPCODE1(x) (((uint32_t) (((uint32_t) (x)) << 26U)) & 0xFC000000U) - -#define FLEXSPI_NOR_INSTANCE 0 +void mg_error(struct mg_connection *c, const char *fmt, ...) { + char buf[64]; + va_list ap; + va_start(ap, fmt); + mg_vsnprintf(buf, sizeof(buf), fmt, &ap); + va_end(ap); + MG_ERROR(("%lu %ld %s", c->id, c->fd, buf)); + c->is_closing = 1; // Set is_closing before sending MG_EV_CALL + mg_call(c, MG_EV_ERROR, buf); // Let user handler override it +} -#if MG_DEVICE == MG_DEVICE_RT1020 -struct mg_flexspi_nor_driver_interface { - uint32_t version; - int (*init)(uint32_t instance, struct mg_flexspi_nor_config *config); - int (*program)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t dst_addr, - const uint32_t *src); - uint32_t reserved; - int (*erase)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t start, - uint32_t lengthInBytes); - uint32_t reserved2; - int (*update_lut)(uint32_t instance, uint32_t seqIndex, const uint32_t *lutBase, - uint32_t seqNumber); - int (*xfer)(uint32_t instance, char *xfer); - void (*clear_cache)(uint32_t instance); -}; -#elif MG_DEVICE == MG_DEVICE_RT1060 -struct mg_flexspi_nor_driver_interface { - uint32_t version; - int (*init)(uint32_t instance, struct mg_flexspi_nor_config *config); - int (*program)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t dst_addr, - const uint32_t *src); - int (*erase_all)(uint32_t instance, struct mg_flexspi_nor_config *config); - int (*erase)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t start, - uint32_t lengthInBytes); - int (*read)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t *dst, uint32_t addr, - uint32_t lengthInBytes); - void (*clear_cache)(uint32_t instance); - int (*xfer)(uint32_t instance, char *xfer); - int (*update_lut)(uint32_t instance, uint32_t seqIndex, const uint32_t *lutBase, - uint32_t seqNumber); - int (*get_config)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t *option); -}; +#ifdef MG_ENABLE_LINES +#line 1 "src/flash.c" #endif -#define flexspi_nor (*((struct mg_flexspi_nor_driver_interface**) \ - (*(uint32_t*)0x0020001c + 16))) -static bool s_flash_irq_disabled; -MG_IRAM void *mg_flash_start(void) { - return (void *) 0x60000000; -} -MG_IRAM size_t mg_flash_size(void) { - return 8 * 1024 * 1024; -} -MG_IRAM size_t mg_flash_sector_size(void) { - return 4 * 1024; // 4k -} -MG_IRAM size_t mg_flash_write_align(void) { - return 256; -} -MG_IRAM int mg_flash_bank(void) { - return 0; -} -MG_IRAM static bool flash_page_start(volatile uint32_t *dst) { - char *base = (char *) mg_flash_start(), *end = base + mg_flash_size(); - volatile char *p = (char *) dst; - return p >= base && p < end && ((p - base) % mg_flash_sector_size()) == 0; -} -// Note: the get_config function below works both for RT1020 and 1060 -#if MG_DEVICE == MG_DEVICE_RT1020 -MG_IRAM static int flexspi_nor_get_config(struct mg_flexspi_nor_config *config) { - struct mg_flexspi_nor_config default_config = { - .memConfig = {.tag = MG_FLEXSPI_CFG_BLK_TAG, - .version = MG_FLEXSPI_CFG_BLK_VERSION, - .readSampleClkSrc = 1, // ReadSampleClk_LoopbackFromDqsPad - .csHoldTime = 3, - .csSetupTime = 3, - .controllerMiscOption = MG_BIT(4), - .deviceType = 1, // serial NOR - .sflashPadType = 4, - .serialClkFreq = 7, // 133MHz - .sflashA1Size = 8 * 1024 * 1024, - .lookupTable = MG_FLEXSPI_QSPI_LUT}, - .pageSize = 256, - .sectorSize = 4 * 1024, - .ipcmdSerialClkFreq = 1, - .blockSize = 64 * 1024, - .isUniformBlockSize = false}; +#if MG_OTA != MG_OTA_NONE && MG_OTA != MG_OTA_CUSTOM - *config = default_config; - return 0; -} -#else -MG_IRAM static int flexspi_nor_get_config(struct mg_flexspi_nor_config *config) { - uint32_t options[] = {0xc0000000, 0x00}; +static char *s_addr; // Current address to write to +static size_t s_size; // Firmware size to flash. In-progress indicator +static uint32_t s_crc32; // Firmware checksum - MG_ARM_DISABLE_IRQ(); - uint32_t status = - flexspi_nor->get_config(FLEXSPI_NOR_INSTANCE, config, options); - if (!s_flash_irq_disabled) { - MG_ARM_ENABLE_IRQ(); +bool mg_ota_flash_begin(size_t new_firmware_size, struct mg_flash *flash) { + bool ok = false; + if (s_size) { + MG_ERROR(("OTA already in progress. Call mg_ota_end()")); + } else { + size_t half = flash->size / 2; + s_crc32 = 0; + s_addr = (char *) flash->start + half; + MG_DEBUG(("FW %lu bytes, max %lu", new_firmware_size, half)); + if (new_firmware_size < half) { + ok = true; + s_size = new_firmware_size; + MG_INFO(("Starting OTA, firmware size %lu", s_size)); + } else { + MG_ERROR(("Firmware %lu is too big to fit %lu", new_firmware_size, half)); + } } - if (status) { - MG_ERROR(("Failed to extract flash configuration: status %u", status)); - } - return status; -} -#endif - -MG_IRAM bool mg_flash_erase(void *addr) { - struct mg_flexspi_nor_config config; - if (flexspi_nor_get_config(&config) != 0) { - return false; - } - if (flash_page_start(addr) == false) { - MG_ERROR(("%p is not on a sector boundary", addr)); - return false; - } - - void *dst = (void *)((char *) addr - (char *) mg_flash_start()); - - // Note: Interrupts must be disabled before any call to the ROM API on RT1020 - // and 1060 - MG_ARM_DISABLE_IRQ(); - bool ok = (flexspi_nor->erase(FLEXSPI_NOR_INSTANCE, &config, (uint32_t) dst, - mg_flash_sector_size()) == 0); - if (!s_flash_irq_disabled) { - MG_ARM_ENABLE_IRQ(); // Reenable them after the call - } - MG_DEBUG(("Sector starting at %p erasure: %s", addr, ok ? "ok" : "fail")); return ok; } -MG_IRAM bool mg_flash_swap_bank(void) { - return true; -} - -static inline void spin(volatile uint32_t count) { - while (count--) (void) 0; -} - -static inline void flash_wait(void) { - while ((*((volatile uint32_t *)(0x402A8000 + 0xE0)) & MG_BIT(1)) == 0) - spin(1); -} - -MG_IRAM static void *flash_code_location(void) { - return (void *) ((char *) mg_flash_start() + 0x2000); -} - -MG_IRAM bool mg_flash_write(void *addr, const void *buf, size_t len) { - struct mg_flexspi_nor_config config; - if (flexspi_nor_get_config(&config) != 0) { - return false; - } - if ((len % mg_flash_write_align()) != 0) { - MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align())); - return false; - } - - if ((char *) addr < (char *) mg_flash_start()) { - MG_ERROR(("Invalid flash write address: %p", addr)); - return false; - } - - uint32_t *dst = (uint32_t *) addr; - uint32_t *src = (uint32_t *) buf; - uint32_t *end = (uint32_t *) ((char *) buf + len); - bool ok = true; - - // Note: If we overwrite the flash irq section of the image, we must also - // make sure interrupts are disabled and are not reenabled until we write - // this sector with another irq table. - if ((char *) addr == (char *) flash_code_location()) { - s_flash_irq_disabled = true; - MG_ARM_DISABLE_IRQ(); - } - - while (ok && src < end) { - if (flash_page_start(dst) && mg_flash_erase(dst) == false) { - break; - } - uint32_t status; - uint32_t dst_ofs = (uint32_t) dst - (uint32_t) mg_flash_start(); - if ((char *) buf >= (char *) mg_flash_start()) { - // If we copy from FLASH to FLASH, then we first need to copy the source - // to RAM - size_t tmp_buf_size = mg_flash_write_align() / sizeof(uint32_t); - uint32_t tmp[tmp_buf_size]; - - for (size_t i = 0; i < tmp_buf_size; i++) { - flash_wait(); - tmp[i] = src[i]; - } - MG_ARM_DISABLE_IRQ(); - status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, &config, - (uint32_t) dst_ofs, tmp); - } else { - MG_ARM_DISABLE_IRQ(); - status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, &config, - (uint32_t) dst_ofs, src); - } - if (!s_flash_irq_disabled) { - MG_ARM_ENABLE_IRQ(); - } - src = (uint32_t *) ((char *) src + mg_flash_write_align()); - dst = (uint32_t *) ((char *) dst + mg_flash_write_align()); - if (status != 0) { - ok = false; +bool mg_ota_flash_write(const void *buf, size_t len, struct mg_flash *flash) { + bool ok = false; + if (s_size == 0) { + MG_ERROR(("OTA is not started, call mg_ota_begin()")); + } else { + size_t len_aligned_down = MG_ROUND_DOWN(len, flash->align); + if (len_aligned_down) ok = flash->write_fn(s_addr, buf, len_aligned_down); + if (len_aligned_down < len) { + size_t left = len - len_aligned_down; + char tmp[flash->align]; + memset(tmp, 0xff, sizeof(tmp)); + memcpy(tmp, (char *) buf + len_aligned_down, left); + ok = flash->write_fn(s_addr + len_aligned_down, tmp, sizeof(tmp)); } + s_crc32 = mg_crc32(s_crc32, (char *) buf, len); // Update CRC + MG_DEBUG(("%#x %p %lu -> %d", s_addr - len, buf, len, ok)); + s_addr += len; } - MG_DEBUG(("Flash write %lu bytes @ %p: %s.", len, dst, ok ? "ok" : "fail")); return ok; } -MG_IRAM void mg_device_reset(void) { - MG_DEBUG(("Resetting device...")); - *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; +bool mg_ota_flash_end(struct mg_flash *flash) { + char *base = (char *) flash->start + flash->size / 2; + bool ok = false; + if (s_size) { + size_t size = (size_t) (s_addr - base); + uint32_t crc32 = mg_crc32(0, base, s_size); + if (size == s_size && crc32 == s_crc32) ok = true; + MG_DEBUG(("CRC: %x/%x, size: %lu/%lu, status: %s", s_crc32, crc32, s_size, + size, ok ? "ok" : "fail")); + s_size = 0; + if (ok) ok = flash->swap_fn(); + } + MG_INFO(("Finishing OTA: %s", ok ? "ok" : "fail")); + return ok; } #endif #ifdef MG_ENABLE_LINES -#line 1 "src/device_stm32h5.c" +#line 1 "src/fmt.c" #endif -// - -#if MG_OTA == MG_OTA_STM32H5 - -#define FLASH_BASE 0x40022000 // Base address of the flash controller -#define FLASH_KEYR (FLASH_BASE + 0x4) // See RM0481 7.11 -#define FLASH_OPTKEYR (FLASH_BASE + 0xc) -#define FLASH_OPTCR (FLASH_BASE + 0x1c) -#define FLASH_NSSR (FLASH_BASE + 0x20) -#define FLASH_NSCR (FLASH_BASE + 0x28) -#define FLASH_NSCCR (FLASH_BASE + 0x30) -#define FLASH_OPTSR_CUR (FLASH_BASE + 0x50) -#define FLASH_OPTSR_PRG (FLASH_BASE + 0x54) -#if 0 -void *mg_flash_start(void) { - return (void *) 0x08000000; -} -size_t mg_flash_size(void) { - return 2 * 1024 * 1024; // 2Mb -} -size_t mg_flash_sector_size(void) { - return 8 * 1024; // 8k -} -size_t mg_flash_write_align(void) { - return 16; // 128 bit -} -int mg_flash_bank(void) { - return MG_REG(FLASH_OPTCR) & MG_BIT(31) ? 2 : 1; -} -#endif -static void flash_unlock(void) { - static bool unlocked = false; - if (unlocked == false) { - MG_REG(FLASH_KEYR) = 0x45670123; - MG_REG(FLASH_KEYR) = 0Xcdef89ab; - MG_REG(FLASH_OPTKEYR) = 0x08192a3b; - MG_REG(FLASH_OPTKEYR) = 0x4c5d6e7f; - unlocked = true; - } +static bool is_digit(int c) { + return c >= '0' && c <= '9'; } -static int flash_page_start(volatile uint32_t *dst) { - char *base = (char *) mg_flash_start(), *end = base + mg_flash_size(); - volatile char *p = (char *) dst; - return p >= base && p < end && ((p - base) % mg_flash_sector_size()) == 0; +static int addexp(char *buf, int e, int sign) { + int n = 0; + buf[n++] = 'e'; + buf[n++] = (char) sign; + if (e > 400) return 0; + if (e < 10) buf[n++] = '0'; + if (e >= 100) buf[n++] = (char) (e / 100 + '0'), e -= 100 * (e / 100); + if (e >= 10) buf[n++] = (char) (e / 10 + '0'), e -= 10 * (e / 10); + buf[n++] = (char) (e + '0'); + return n; } -static bool flash_is_err(void) { - return MG_REG(FLASH_NSSR) & ((MG_BIT(8) - 1) << 17); // RM0481 7.11.9 +static int xisinf(double x) { + union { + double f; + uint64_t u; + } ieee754 = {x}; + return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) == 0x7ff00000 && + ((unsigned) ieee754.u == 0); } -static void flash_wait(void) { - while ((MG_REG(FLASH_NSSR) & MG_BIT(0)) && - (MG_REG(FLASH_NSSR) & MG_BIT(16)) == 0) { - (void) 0; - } +static int xisnan(double x) { + union { + double f; + uint64_t u; + } ieee754 = {x}; + return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) + + ((unsigned) ieee754.u != 0) > + 0x7ff00000; } -static void flash_clear_err(void) { - flash_wait(); // Wait until ready - MG_REG(FLASH_NSCCR) = ((MG_BIT(9) - 1) << 16U); // Clear all errors -} +static size_t mg_dtoa(char *dst, size_t dstlen, double d, int width, bool tz) { + char buf[40]; + int i, s = 0, n = 0, e = 0; + double t, mul, saved; + if (d == 0.0) return mg_snprintf(dst, dstlen, "%s", "0"); + if (xisinf(d)) return mg_snprintf(dst, dstlen, "%s", d > 0 ? "inf" : "-inf"); + if (xisnan(d)) return mg_snprintf(dst, dstlen, "%s", "nan"); + if (d < 0.0) d = -d, buf[s++] = '-'; -static bool flash_bank_is_swapped(void) { - return MG_REG(FLASH_OPTCR) & MG_BIT(31); // RM0481 7.11.8 -} + // Round + saved = d; + mul = 1.0; + while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0; + while (d <= 1.0 && d / mul <= 1.0) mul /= 10.0; + for (i = 0, t = mul * 5; i < width; i++) t /= 10.0; + d += t; + // Calculate exponent, and 'mul' for scientific representation + mul = 1.0; + while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0, e++; + while (d < 1.0 && d / mul < 1.0) mul /= 10.0, e--; + // printf(" --> %g %d %g %g\n", saved, e, t, mul); -static bool mg_stm32h5_erase(void *location) { - bool ok = false; - if (flash_page_start(location) == false) { - MG_ERROR(("%p is not on a sector boundary")); + if (e >= width && width > 1) { + n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width, tz); + // printf(" --> %.*g %d [%.*s]\n", 10, d / t, e, n, buf); + n += addexp(buf + s + n, e, '+'); + return mg_snprintf(dst, dstlen, "%.*s", n, buf); + } else if (e <= -width && width > 1) { + n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width, tz); + // printf(" --> %.*g %d [%.*s]\n", 10, d / mul, e, n, buf); + n += addexp(buf + s + n, -e, '-'); + return mg_snprintf(dst, dstlen, "%.*s", n, buf); } else { - uintptr_t diff = (char *) location - (char *) mg_flash_start(); - uint32_t sector = diff / mg_flash_sector_size(); - uint32_t saved_cr = MG_REG(FLASH_NSCR); // Save CR value - flash_unlock(); - flash_clear_err(); - MG_REG(FLASH_NSCR) = 0; - if ((sector < 128 && flash_bank_is_swapped()) || - (sector > 127 && !flash_bank_is_swapped())) { - MG_REG(FLASH_NSCR) |= MG_BIT(31); // Set FLASH_CR_BKSEL + for (i = 0, t = mul; t >= 1.0 && s + n < (int) sizeof(buf); i++) { + int ch = (int) (d / t); + if (n > 0 || ch > 0) buf[s + n++] = (char) (ch + '0'); + d -= ch * t; + t /= 10.0; + } + // printf(" --> [%g] -> %g %g (%d) [%.*s]\n", saved, d, t, n, s + n, buf); + if (n == 0) buf[s++] = '0'; + while (t >= 1.0 && n + s < (int) sizeof(buf)) buf[n++] = '0', t /= 10.0; + if (s + n < (int) sizeof(buf)) buf[n + s++] = '.'; + // printf(" 1--> [%g] -> [%.*s]\n", saved, s + n, buf); + for (i = 0, t = 0.1; s + n < (int) sizeof(buf) && n < width; i++) { + int ch = (int) (d / t); + buf[s + n++] = (char) (ch + '0'); + d -= ch * t; + t /= 10.0; } - if (sector > 127) sector -= 128; - MG_REG(FLASH_NSCR) |= MG_BIT(2) | (sector << 6); // Erase | sector_num - MG_REG(FLASH_NSCR) |= MG_BIT(5); // Start erasing - flash_wait(); - ok = !flash_is_err(); - MG_DEBUG(("Erase sector %lu @ %p: %s. CR %#lx SR %#lx", sector, location, - ok ? "ok" : "fail", MG_REG(FLASH_NSCR), MG_REG(FLASH_NSSR))); - // mg_hexdump(location, 32); - MG_REG(FLASH_NSCR) = saved_cr; // Restore saved CR } - return ok; + while (tz && n > 0 && buf[s + n - 1] == '0') n--; // Trim trailing zeroes + if (n > 0 && buf[s + n - 1] == '.') n--; // Trim trailing dot + n += s; + if (n >= (int) sizeof(buf)) n = (int) sizeof(buf) - 1; + buf[n] = '\0'; + return mg_snprintf(dst, dstlen, "%s", buf); } -static bool mg_stm32h5_swap(void) { - uint32_t desired = flash_bank_is_swapped() ? 0 : MG_BIT(31); - flash_unlock(); - flash_clear_err(); - // printf("OPTSR_PRG 1 %#lx\n", FLASH->OPTSR_PRG); - MG_SET_BITS(MG_REG(FLASH_OPTSR_PRG), MG_BIT(31), desired); - // printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG); - MG_REG(FLASH_OPTCR) |= MG_BIT(1); // OPTSTART - while ((MG_REG(FLASH_OPTSR_CUR) & MG_BIT(31)) != desired) (void) 0; - return true; -} - -static bool mg_stm32h5_write(void *addr, const void *buf, size_t len) { - if ((len % mg_flash_write_align()) != 0) { - MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align())); - return false; +static size_t mg_lld(char *buf, int64_t val, bool is_signed, bool is_hex) { + const char *letters = "0123456789abcdef"; + uint64_t v = (uint64_t) val; + size_t s = 0, n, i; + if (is_signed && val < 0) buf[s++] = '-', v = (uint64_t) (-val); + // This loop prints a number in reverse order. I guess this is because we + // write numbers from right to left: least significant digit comes last. + // Maybe because we use Arabic numbers, and Arabs write RTL? + if (is_hex) { + for (n = 0; v; v >>= 4) buf[s + n++] = letters[v & 15]; + } else { + for (n = 0; v; v /= 10) buf[s + n++] = letters[v % 10]; } - uint32_t *dst = (uint32_t *) addr; - uint32_t *src = (uint32_t *) buf; - uint32_t *end = (uint32_t *) ((char *) buf + len); - bool ok = true; - flash_unlock(); - flash_clear_err(); - MG_ARM_DISABLE_IRQ(); - // MG_DEBUG(("Starting flash write %lu bytes @ %p", len, addr)); - MG_REG(FLASH_NSCR) = MG_BIT(1); // Set programming flag - while (ok && src < end) { - if (flash_page_start(dst) && mg_stm32h5_erase(dst) == false) break; - *(volatile uint32_t *) dst++ = *src++; - flash_wait(); - if (flash_is_err()) ok = false; + // Reverse a string + for (i = 0; i < n / 2; i++) { + char t = buf[s + i]; + buf[s + i] = buf[s + n - i - 1], buf[s + n - i - 1] = t; } - MG_ARM_ENABLE_IRQ(); - MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst, - flash_is_err() ? "fail" : "ok", MG_REG(FLASH_NSCR), - MG_REG(FLASH_NSSR))); - MG_REG(FLASH_NSCR) = 0; // Clear flags - return ok; + if (val == 0) buf[n++] = '0'; // Handle special case + return n + s; } -static struct mg_flash s_mg_flash_stm32h5 = { - (void *) 0x08000000, // Start - 2 * 1024 * 1024, // Size, 2Mb - 16, // Align, 128 bit - mg_stm32h5_write, - mg_stm32h5_swap, -}; - -bool mg_ota_begin(size_t new_firmware_size) { - return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_stm32h5); +static size_t scpy(void (*out)(char, void *), void *ptr, char *buf, + size_t len) { + size_t i = 0; + while (i < len && buf[i] != '\0') out(buf[i++], ptr); + return i; } -bool mg_ota_write(const void *buf, size_t len) { - return mg_ota_flash_write(buf, len, &s_mg_flash_stm32h5); +size_t mg_xprintf(void (*out)(char, void *), void *ptr, const char *fmt, ...) { + size_t len = 0; + va_list ap; + va_start(ap, fmt); + len = mg_vxprintf(out, ptr, fmt, &ap); + va_end(ap); + return len; } -bool mg_ota_end(void) { - return mg_ota_flash_end(&s_mg_flash_stm32h5); +size_t mg_vxprintf(void (*out)(char, void *), void *param, const char *fmt, + va_list *ap) { + size_t i = 0, n = 0; + while (fmt[i] != '\0') { + if (fmt[i] == '%') { + size_t j, k, x = 0, is_long = 0, w = 0 /* width */, pr = ~0U /* prec */; + char pad = ' ', minus = 0, c = fmt[++i]; + if (c == '#') x++, c = fmt[++i]; + if (c == '-') minus++, c = fmt[++i]; + if (c == '0') pad = '0', c = fmt[++i]; + while (is_digit(c)) w *= 10, w += (size_t) (c - '0'), c = fmt[++i]; + if (c == '.') { + c = fmt[++i]; + if (c == '*') { + pr = (size_t) va_arg(*ap, int); + c = fmt[++i]; + } else { + pr = 0; + while (is_digit(c)) pr *= 10, pr += (size_t) (c - '0'), c = fmt[++i]; + } + } + while (c == 'h') c = fmt[++i]; // Treat h and hh as int + if (c == 'l') { + is_long++, c = fmt[++i]; + if (c == 'l') is_long++, c = fmt[++i]; + } + if (c == 'p') x = 1, is_long = 1; + if (c == 'd' || c == 'u' || c == 'x' || c == 'X' || c == 'p' || + c == 'g' || c == 'f') { + bool s = (c == 'd'), h = (c == 'x' || c == 'X' || c == 'p'); + char tmp[40]; + size_t xl = x ? 2 : 0; + if (c == 'g' || c == 'f') { + double v = va_arg(*ap, double); + if (pr == ~0U) pr = 6; + k = mg_dtoa(tmp, sizeof(tmp), v, (int) pr, c == 'g'); + } else if (is_long == 2) { + int64_t v = va_arg(*ap, int64_t); + k = mg_lld(tmp, v, s, h); + } else if (is_long == 1) { + long v = va_arg(*ap, long); + k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned long) v, s, h); + } else { + int v = va_arg(*ap, int); + k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned) v, s, h); + } + for (j = 0; j < xl && w > 0; j++) w--; + for (j = 0; pad == ' ' && !minus && k < w && j + k < w; j++) + n += scpy(out, param, &pad, 1); + n += scpy(out, param, (char *) "0x", xl); + for (j = 0; pad == '0' && k < w && j + k < w; j++) + n += scpy(out, param, &pad, 1); + n += scpy(out, param, tmp, k); + for (j = 0; pad == ' ' && minus && k < w && j + k < w; j++) + n += scpy(out, param, &pad, 1); + } else if (c == 'm' || c == 'M') { + mg_pm_t f = va_arg(*ap, mg_pm_t); + if (c == 'm') out('"', param); + n += f(out, param, ap); + if (c == 'm') n += 2, out('"', param); + } else if (c == 'c') { + int ch = va_arg(*ap, int); + out((char) ch, param); + n++; + } else if (c == 's') { + char *p = va_arg(*ap, char *); + if (pr == ~0U) pr = p == NULL ? 0 : strlen(p); + for (j = 0; !minus && pr < w && j + pr < w; j++) + n += scpy(out, param, &pad, 1); + n += scpy(out, param, p, pr); + for (j = 0; minus && pr < w && j + pr < w; j++) + n += scpy(out, param, &pad, 1); + } else if (c == '%') { + out('%', param); + n++; + } else { + out('%', param); + out(c, param); + n += 2; + } + i++; + } else { + out(fmt[i], param), n++, i++; + } + } + return n; } -#endif #ifdef MG_ENABLE_LINES -#line 1 "src/device_stm32h7.c" +#line 1 "src/fs.c" #endif -#if MG_DEVICE == MG_DEVICE_STM32H7 - -#define FLASH_BASE1 0x52002000 // Base address for bank1 -#define FLASH_BASE2 0x52002100 // Base address for bank2 -#define FLASH_KEYR 0x04 // See RM0433 4.9.2 -#define FLASH_OPTKEYR 0x08 -#define FLASH_OPTCR 0x18 -#define FLASH_SR 0x10 -#define FLASH_CR 0x0c -#define FLASH_CCR 0x14 -#define FLASH_OPTSR_CUR 0x1c -#define FLASH_OPTSR_PRG 0x20 -#define FLASH_SIZE_REG 0x1ff1e880 -MG_IRAM void *mg_flash_start(void) { - return (void *) 0x08000000; -} -MG_IRAM size_t mg_flash_size(void) { - return MG_REG(FLASH_SIZE_REG) * 1024; -} -MG_IRAM size_t mg_flash_sector_size(void) { - return 128 * 1024; // 128k -} -MG_IRAM size_t mg_flash_write_align(void) { - return 32; // 256 bit +struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int flags) { + struct mg_fd *fd = (struct mg_fd *) calloc(1, sizeof(*fd)); + if (fd != NULL) { + fd->fd = fs->op(path, flags); + fd->fs = fs; + if (fd->fd == NULL) { + free(fd); + fd = NULL; + } + } + return fd; } -MG_IRAM int mg_flash_bank(void) { - if (mg_flash_size() < 2 * 1024 * 1024) return 0; // No dual bank support - return MG_REG(FLASH_BASE1 + FLASH_OPTCR) & MG_BIT(31) ? 2 : 1; + +void mg_fs_close(struct mg_fd *fd) { + if (fd != NULL) { + fd->fs->cl(fd->fd); + free(fd); + } } -MG_IRAM static void flash_unlock(void) { - static bool unlocked = false; - if (unlocked == false) { - MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0x45670123; - MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0xcdef89ab; - if (mg_flash_bank() > 0) { - MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0x45670123; - MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0xcdef89ab; +struct mg_str mg_file_read(struct mg_fs *fs, const char *path) { + struct mg_str result = {NULL, 0}; + void *fp; + fs->st(path, &result.len, NULL); + if ((fp = fs->op(path, MG_FS_READ)) != NULL) { + result.buf = (char *) calloc(1, result.len + 1); + if (result.buf != NULL && + fs->rd(fp, (void *) result.buf, result.len) != result.len) { + free((void *) result.buf); + result.buf = NULL; } - MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x08192a3b; // opt reg is "shared" - MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x4c5d6e7f; // thus unlock once - unlocked = true; + fs->cl(fp); } + if (result.buf == NULL) result.len = 0; + return result; } -MG_IRAM static bool flash_page_start(volatile uint32_t *dst) { - char *base = (char *) mg_flash_start(), *end = base + mg_flash_size(); - volatile char *p = (char *) dst; - return p >= base && p < end && ((p - base) % mg_flash_sector_size()) == 0; +bool mg_file_write(struct mg_fs *fs, const char *path, const void *buf, + size_t len) { + bool result = false; + struct mg_fd *fd; + char tmp[MG_PATH_MAX]; + mg_snprintf(tmp, sizeof(tmp), "%s..%d", path, rand()); + if ((fd = mg_fs_open(fs, tmp, MG_FS_WRITE)) != NULL) { + result = fs->wr(fd->fd, buf, len) == len; + mg_fs_close(fd); + if (result) { + fs->rm(path); + fs->mv(tmp, path); + } else { + fs->rm(tmp); + } + } + return result; } -MG_IRAM static bool flash_is_err(uint32_t bank) { - return MG_REG(bank + FLASH_SR) & ((MG_BIT(11) - 1) << 17); // RM0433 4.9.5 +bool mg_file_printf(struct mg_fs *fs, const char *path, const char *fmt, ...) { + va_list ap; + char *data; + bool result = false; + va_start(ap, fmt); + data = mg_vmprintf(fmt, &ap); + va_end(ap); + result = mg_file_write(fs, path, data, strlen(data)); + free(data); + return result; } -MG_IRAM static void flash_wait(uint32_t bank) { - while (MG_REG(bank + FLASH_SR) & (MG_BIT(0) | MG_BIT(2))) (void) 0; +// This helper function allows to scan a filesystem in a sequential way, +// without using callback function: +// char buf[100] = ""; +// while (mg_fs_ls(&mg_fs_posix, "./", buf, sizeof(buf))) { +// ... +static void mg_fs_ls_fn(const char *filename, void *param) { + struct mg_str *s = (struct mg_str *) param; + if (s->buf[0] == '\0') { + mg_snprintf((char *) s->buf, s->len, "%s", filename); + } else if (strcmp(s->buf, filename) == 0) { + ((char *) s->buf)[0] = '\0'; // Fetch next file + } } -MG_IRAM static void flash_clear_err(uint32_t bank) { - flash_wait(bank); // Wait until ready - MG_REG(bank + FLASH_CCR) = ((MG_BIT(11) - 1) << 16U); // Clear all errors +bool mg_fs_ls(struct mg_fs *fs, const char *path, char *buf, size_t len) { + struct mg_str s = {buf, len}; + fs->ls(path, mg_fs_ls_fn, &s); + return buf[0] != '\0'; } -MG_IRAM static bool flash_bank_is_swapped(uint32_t bank) { - return MG_REG(bank + FLASH_OPTCR) & MG_BIT(31); // RM0433 4.9.7 +#ifdef MG_ENABLE_LINES +#line 1 "src/fs_fat.c" +#endif + + + +#if MG_ENABLE_FATFS +#include + +static int mg_days_from_epoch(int y, int m, int d) { + y -= m <= 2; + int era = y / 400; + int yoe = y - era * 400; + int doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; + int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; + return era * 146097 + doe - 719468; } -// Figure out flash bank based on the address -MG_IRAM static uint32_t flash_bank(void *addr) { - size_t ofs = (char *) addr - (char *) mg_flash_start(); - if (mg_flash_bank() == 0) return FLASH_BASE1; - return ofs < mg_flash_size() / 2 ? FLASH_BASE1 : FLASH_BASE2; +static time_t mg_timegm(const struct tm *t) { + int year = t->tm_year + 1900; + int month = t->tm_mon; // 0-11 + if (month > 11) { + year += month / 12; + month %= 12; + } else if (month < 0) { + int years_diff = (11 - month) / 12; + year -= years_diff; + month += 12 * years_diff; + } + int x = mg_days_from_epoch(year, month + 1, t->tm_mday); + return 60 * (60 * (24L * x + t->tm_hour) + t->tm_min) + t->tm_sec; } -MG_IRAM bool mg_flash_erase(void *addr) { - bool ok = false; - if (flash_page_start(addr) == false) { - MG_ERROR(("%p is not on a sector boundary", addr)); - } else { - uintptr_t diff = (char *) addr - (char *) mg_flash_start(); - uint32_t sector = diff / mg_flash_sector_size(); - uint32_t bank = flash_bank(addr); - uint32_t saved_cr = MG_REG(bank + FLASH_CR); // Save CR value +static time_t ff_time_to_epoch(uint16_t fdate, uint16_t ftime) { + struct tm tm; + memset(&tm, 0, sizeof(struct tm)); + tm.tm_sec = (ftime << 1) & 0x3e; + tm.tm_min = ((ftime >> 5) & 0x3f); + tm.tm_hour = ((ftime >> 11) & 0x1f); + tm.tm_mday = (fdate & 0x1f); + tm.tm_mon = ((fdate >> 5) & 0x0f) - 1; + tm.tm_year = ((fdate >> 9) & 0x7f) + 80; + return mg_timegm(&tm); +} - flash_unlock(); - if (sector > 7) sector -= 8; +static int ff_stat(const char *path, size_t *size, time_t *mtime) { + FILINFO fi; + if (path[0] == '\0') { + if (size) *size = 0; + if (mtime) *mtime = 0; + return MG_FS_DIR; + } else if (f_stat(path, &fi) == 0) { + if (size) *size = (size_t) fi.fsize; + if (mtime) *mtime = ff_time_to_epoch(fi.fdate, fi.ftime); + return MG_FS_READ | MG_FS_WRITE | ((fi.fattrib & AM_DIR) ? MG_FS_DIR : 0); + } else { + return 0; + } +} - flash_clear_err(bank); - MG_REG(bank + FLASH_CR) = MG_BIT(5); // 32-bit write parallelism - MG_REG(bank + FLASH_CR) |= (sector & 7U) << 8U; // Sector to erase - MG_REG(bank + FLASH_CR) |= MG_BIT(2); // Sector erase bit - MG_REG(bank + FLASH_CR) |= MG_BIT(7); // Start erasing - ok = !flash_is_err(bank); - MG_DEBUG(("Erase sector %lu @ %p %s. CR %#lx SR %#lx", sector, addr, - ok ? "ok" : "fail", MG_REG(bank + FLASH_CR), - MG_REG(bank + FLASH_SR))); - MG_REG(bank + FLASH_CR) = saved_cr; // Restore CR +static void ff_list(const char *dir, void (*fn)(const char *, void *), + void *userdata) { + DIR d; + FILINFO fi; + if (f_opendir(&d, dir) == FR_OK) { + while (f_readdir(&d, &fi) == FR_OK && fi.fname[0] != '\0') { + if (!strcmp(fi.fname, ".") || !strcmp(fi.fname, "..")) continue; + fn(fi.fname, userdata); + } + f_closedir(&d); } - return ok; } -MG_IRAM bool mg_flash_swap_bank(void) { - if (mg_flash_bank() == 0) return true; - uint32_t bank = FLASH_BASE1; - uint32_t desired = flash_bank_is_swapped(bank) ? 0 : MG_BIT(31); - flash_unlock(); - flash_clear_err(bank); - // printf("OPTSR_PRG 1 %#lx\n", FLASH->OPTSR_PRG); - MG_SET_BITS(MG_REG(bank + FLASH_OPTSR_PRG), MG_BIT(31), desired); - // printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG); - MG_REG(bank + FLASH_OPTCR) |= MG_BIT(1); // OPTSTART - while ((MG_REG(bank + FLASH_OPTSR_CUR) & MG_BIT(31)) != desired) (void) 0; - return true; +static void *ff_open(const char *path, int flags) { + FIL f; + unsigned char mode = FA_READ; + if (flags & MG_FS_WRITE) mode |= FA_WRITE | FA_OPEN_ALWAYS | FA_OPEN_APPEND; + if (f_open(&f, path, mode) == 0) { + FIL *fp; + if ((fp = calloc(1, sizeof(*fp))) != NULL) { + memcpy(fp, &f, sizeof(*fp)); + return fp; + } + } + return NULL; } -MG_IRAM bool mg_flash_write(void *addr, const void *buf, size_t len) { - if ((len % mg_flash_write_align()) != 0) { - MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align())); - return false; +static void ff_close(void *fp) { + if (fp != NULL) { + f_close((FIL *) fp); + free(fp); } - uint32_t bank = flash_bank(addr); - uint32_t *dst = (uint32_t *) addr; - uint32_t *src = (uint32_t *) buf; - uint32_t *end = (uint32_t *) ((char *) buf + len); - bool ok = true; - flash_unlock(); - flash_clear_err(bank); - MG_REG(bank + FLASH_CR) = MG_BIT(1); // Set programming flag - MG_REG(bank + FLASH_CR) |= MG_BIT(5); // 32-bit write parallelism - MG_DEBUG(("Writing flash @ %p, %lu bytes", addr, len)); - MG_ARM_DISABLE_IRQ(); - while (ok && src < end) { - if (flash_page_start(dst) && mg_flash_erase(dst) == false) break; - *(volatile uint32_t *) dst++ = *src++; - flash_wait(bank); - if (flash_is_err(bank)) ok = false; +} + +static size_t ff_read(void *fp, void *buf, size_t len) { + UINT n = 0, misalign = ((size_t) buf) & 3; + if (misalign) { + char aligned[4]; + f_read((FIL *) fp, aligned, len > misalign ? misalign : len, &n); + memcpy(buf, aligned, n); + } else { + f_read((FIL *) fp, buf, len, &n); } - MG_ARM_ENABLE_IRQ(); - MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst, - ok ? "ok" : "fail", MG_REG(bank + FLASH_CR), - MG_REG(bank + FLASH_SR))); - MG_REG(bank + FLASH_CR) &= ~MG_BIT(1); // Clear programming flag - return ok; + return n; } -MG_IRAM void mg_device_reset(void) { - // SCB->AIRCR = ((0x5fa << SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk); - *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; +static size_t ff_write(void *fp, const void *buf, size_t len) { + UINT n = 0; + return f_write((FIL *) fp, (char *) buf, len, &n) == FR_OK ? n : 0; } -#endif -#ifdef MG_ENABLE_LINES -#line 1 "src/dns.c" -#endif +static size_t ff_seek(void *fp, size_t offset) { + f_lseek((FIL *) fp, offset); + return offset; +} + +static bool ff_rename(const char *from, const char *to) { + return f_rename(from, to) == FR_OK; +} +static bool ff_remove(const char *path) { + return f_unlink(path) == FR_OK; +} +static bool ff_mkdir(const char *path) { + return f_mkdir(path) == FR_OK; +} +struct mg_fs mg_fs_fat = {ff_stat, ff_list, ff_open, ff_close, ff_read, + ff_write, ff_seek, ff_rename, ff_remove, ff_mkdir}; +#endif +#ifdef MG_ENABLE_LINES +#line 1 "src/fs_packed.c" +#endif -struct dns_data { - struct dns_data *next; - struct mg_connection *c; - uint64_t expire; - uint16_t txnid; +struct packed_file { + const char *data; + size_t size; + size_t pos; }; -static void mg_sendnsreq(struct mg_connection *, struct mg_str *, int, - struct mg_dns *, bool); - -static void mg_dns_free(struct dns_data **head, struct dns_data *d) { - LIST_DELETE(struct dns_data, head, d); - free(d); +#if MG_ENABLE_PACKED_FS +#else +const char *mg_unpack(const char *path, size_t *size, time_t *mtime) { + *size = 0, *mtime = 0; + (void) path; + return NULL; +} +const char *mg_unlist(size_t no) { + (void) no; + return NULL; } +#endif -void mg_resolve_cancel(struct mg_connection *c) { - struct dns_data *tmp, *d; - struct dns_data **head = (struct dns_data **) &c->mgr->active_dns_requests; - for (d = *head; d != NULL; d = tmp) { - tmp = d->next; - if (d->c == c) mg_dns_free(head, d); - } +struct mg_str mg_unpacked(const char *path) { + size_t len = 0; + const char *buf = mg_unpack(path, &len, NULL); + return mg_str_n(buf, len); } -static size_t mg_dns_parse_name_depth(const uint8_t *s, size_t len, size_t ofs, - char *to, size_t tolen, size_t j, - int depth) { - size_t i = 0; - if (tolen > 0 && depth == 0) to[0] = '\0'; - if (depth > 5) return 0; - // MG_INFO(("ofs %lx %x %x", (unsigned long) ofs, s[ofs], s[ofs + 1])); - while (ofs + i + 1 < len) { - size_t n = s[ofs + i]; - if (n == 0) { - i++; - break; - } - if (n & 0xc0) { - size_t ptr = (((n & 0x3f) << 8) | s[ofs + i + 1]); // 12 is hdr len - // MG_INFO(("PTR %lx", (unsigned long) ptr)); - if (ptr + 1 < len && (s[ptr] & 0xc0) == 0 && - mg_dns_parse_name_depth(s, len, ptr, to, tolen, j, depth + 1) == 0) - return 0; - i += 2; - break; - } - if (ofs + i + n + 1 >= len) return 0; - if (j > 0) { - if (j < tolen) to[j] = '.'; - j++; - } - if (j + n < tolen) memcpy(&to[j], &s[ofs + i + 1], n); - j += n; - i += n + 1; - if (j < tolen) to[j] = '\0'; // Zero-terminate this chunk - // MG_INFO(("--> [%s]", to)); - } - if (tolen > 0) to[tolen - 1] = '\0'; // Make sure make sure it is nul-term - return i; +static int is_dir_prefix(const char *prefix, size_t n, const char *path) { + // MG_INFO(("[%.*s] [%s] %c", (int) n, prefix, path, path[n])); + return n < strlen(path) && strncmp(prefix, path, n) == 0 && + (n == 0 || path[n] == '/' || path[n - 1] == '/'); } -static size_t mg_dns_parse_name(const uint8_t *s, size_t n, size_t ofs, - char *dst, size_t dstlen) { - return mg_dns_parse_name_depth(s, n, ofs, dst, dstlen, 0, 0); +static int packed_stat(const char *path, size_t *size, time_t *mtime) { + const char *p; + size_t i, n = strlen(path); + if (mg_unpack(path, size, mtime)) return MG_FS_READ; // Regular file + // Scan all files. If `path` is a dir prefix for any of them, it's a dir + for (i = 0; (p = mg_unlist(i)) != NULL; i++) { + if (is_dir_prefix(path, n, p)) return MG_FS_DIR; + } + return 0; } -size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs, - bool is_question, struct mg_dns_rr *rr) { - const uint8_t *s = buf + ofs, *e = &buf[len]; - - memset(rr, 0, sizeof(*rr)); - if (len < sizeof(struct mg_dns_header)) return 0; // Too small - if (len > 512) return 0; // Too large, we don't expect that - if (s >= e) return 0; // Overflow - - if ((rr->nlen = (uint16_t) mg_dns_parse_name(buf, len, ofs, NULL, 0)) == 0) - return 0; - s += rr->nlen + 4; - if (s > e) return 0; - rr->atype = (uint16_t) (((uint16_t) s[-4] << 8) | s[-3]); - rr->aclass = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]); - if (is_question) return (size_t) (rr->nlen + 4); - - s += 6; - if (s > e) return 0; - rr->alen = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]); - if (s + rr->alen > e) return 0; - return (size_t) (rr->nlen + rr->alen + 10); +static void packed_list(const char *dir, void (*fn)(const char *, void *), + void *userdata) { + char buf[MG_PATH_MAX], tmp[sizeof(buf)]; + const char *path, *begin, *end; + size_t i, n = strlen(dir); + tmp[0] = '\0'; // Previously listed entry + for (i = 0; (path = mg_unlist(i)) != NULL; i++) { + if (!is_dir_prefix(dir, n, path)) continue; + begin = &path[n + 1]; + end = strchr(begin, '/'); + if (end == NULL) end = begin + strlen(begin); + mg_snprintf(buf, sizeof(buf), "%.*s", (int) (end - begin), begin); + buf[sizeof(buf) - 1] = '\0'; + // If this entry has been already listed, skip + // NOTE: we're assuming that file list is sorted alphabetically + if (strcmp(buf, tmp) == 0) continue; + fn(buf, userdata); // Not yet listed, call user function + strcpy(tmp, buf); // And save this entry as listed + } } -bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *dm) { - const struct mg_dns_header *h = (struct mg_dns_header *) buf; - struct mg_dns_rr rr; - size_t i, n, num_answers, ofs = sizeof(*h); - memset(dm, 0, sizeof(*dm)); - - if (len < sizeof(*h)) return 0; // Too small, headers dont fit - if (mg_ntohs(h->num_questions) > 1) return 0; // Sanity - num_answers = mg_ntohs(h->num_answers); - if (num_answers > 10) { - MG_DEBUG(("Got %u answers, ignoring beyond 10th one", num_answers)); - num_answers = 10; // Sanity cap +static void *packed_open(const char *path, int flags) { + size_t size = 0; + const char *data = mg_unpack(path, &size, NULL); + struct packed_file *fp = NULL; + if (data == NULL) return NULL; + if (flags & MG_FS_WRITE) return NULL; + if ((fp = (struct packed_file *) calloc(1, sizeof(*fp))) != NULL) { + fp->size = size; + fp->data = data; } - dm->txnid = mg_ntohs(h->txnid); + return (void *) fp; +} - for (i = 0; i < mg_ntohs(h->num_questions); i++) { - if ((n = mg_dns_parse_rr(buf, len, ofs, true, &rr)) == 0) return false; - // MG_INFO(("Q %lu %lu %hu/%hu", ofs, n, rr.atype, rr.aclass)); - ofs += n; - } - for (i = 0; i < num_answers; i++) { - if ((n = mg_dns_parse_rr(buf, len, ofs, false, &rr)) == 0) return false; - // MG_INFO(("A -- %lu %lu %hu/%hu %s", ofs, n, rr.atype, rr.aclass, - // dm->name)); - mg_dns_parse_name(buf, len, ofs, dm->name, sizeof(dm->name)); - ofs += n; +static void packed_close(void *fp) { + if (fp != NULL) free(fp); +} - if (rr.alen == 4 && rr.atype == 1 && rr.aclass == 1) { - dm->addr.is_ip6 = false; - memcpy(&dm->addr.ip, &buf[ofs - 4], 4); - dm->resolved = true; - break; // Return success - } else if (rr.alen == 16 && rr.atype == 28 && rr.aclass == 1) { - dm->addr.is_ip6 = true; - memcpy(&dm->addr.ip, &buf[ofs - 16], 16); - dm->resolved = true; - break; // Return success - } - } - return true; +static size_t packed_read(void *fd, void *buf, size_t len) { + struct packed_file *fp = (struct packed_file *) fd; + if (fp->pos + len > fp->size) len = fp->size - fp->pos; + memcpy(buf, &fp->data[fp->pos], len); + fp->pos += len; + return len; } -static void dns_cb(struct mg_connection *c, int ev, void *ev_data) { - struct dns_data *d, *tmp; - struct dns_data **head = (struct dns_data **) &c->mgr->active_dns_requests; - if (ev == MG_EV_POLL) { - uint64_t now = *(uint64_t *) ev_data; - for (d = *head; d != NULL; d = tmp) { - tmp = d->next; - // MG_DEBUG ("%lu %lu dns poll", d->expire, now)); - if (now > d->expire) mg_error(d->c, "DNS timeout"); - } - } else if (ev == MG_EV_READ) { - struct mg_dns_message dm; - int resolved = 0; - if (mg_dns_parse(c->recv.buf, c->recv.len, &dm) == false) { - MG_ERROR(("Unexpected DNS response:")); - mg_hexdump(c->recv.buf, c->recv.len); - } else { - // MG_VERBOSE(("%s %d", dm.name, dm.resolved)); - for (d = *head; d != NULL; d = tmp) { - tmp = d->next; - // MG_INFO(("d %p %hu %hu", d, d->txnid, dm.txnid)); - if (dm.txnid != d->txnid) continue; - if (d->c->is_resolving) { - if (dm.resolved) { - dm.addr.port = d->c->rem.port; // Save port - d->c->rem = dm.addr; // Copy resolved address - MG_DEBUG( - ("%lu %s is %M", d->c->id, dm.name, mg_print_ip, &d->c->rem)); - mg_connect_resolved(d->c); -#if MG_ENABLE_IPV6 - } else if (dm.addr.is_ip6 == false && dm.name[0] != '\0' && - c->mgr->use_dns6 == false) { - struct mg_str x = mg_str(dm.name); - mg_sendnsreq(d->c, &x, c->mgr->dnstimeout, &c->mgr->dns6, true); -#endif - } else { - mg_error(d->c, "%s DNS lookup failed", dm.name); - } - } else { - MG_ERROR(("%lu already resolved", d->c->id)); - } - mg_dns_free(head, d); - resolved = 1; - } - } - if (!resolved) MG_ERROR(("stray DNS reply")); - c->recv.len = 0; - } else if (ev == MG_EV_CLOSE) { - for (d = *head; d != NULL; d = tmp) { - tmp = d->next; - mg_error(d->c, "DNS error"); - mg_dns_free(head, d); - } - } +static size_t packed_write(void *fd, const void *buf, size_t len) { + (void) fd, (void) buf, (void) len; + return 0; } -static bool mg_dns_send(struct mg_connection *c, const struct mg_str *name, - uint16_t txnid, bool ipv6) { - struct { - struct mg_dns_header header; - uint8_t data[256]; - } pkt; - size_t i, n; - memset(&pkt, 0, sizeof(pkt)); - pkt.header.txnid = mg_htons(txnid); - pkt.header.flags = mg_htons(0x100); - pkt.header.num_questions = mg_htons(1); - for (i = n = 0; i < sizeof(pkt.data) - 5; i++) { - if (name->buf[i] == '.' || i >= name->len) { - pkt.data[n] = (uint8_t) (i - n); - memcpy(&pkt.data[n + 1], name->buf + n, i - n); - n = i + 1; - } - if (i >= name->len) break; - } - memcpy(&pkt.data[n], "\x00\x00\x01\x00\x01", 5); // A query - n += 5; - if (ipv6) pkt.data[n - 3] = 0x1c; // AAAA query - // memcpy(&pkt.data[n], "\xc0\x0c\x00\x1c\x00\x01", 6); // AAAA query - // n += 6; - return mg_send(c, &pkt, sizeof(pkt.header) + n); +static size_t packed_seek(void *fd, size_t offset) { + struct packed_file *fp = (struct packed_file *) fd; + fp->pos = offset; + if (fp->pos > fp->size) fp->pos = fp->size; + return fp->pos; } -static void mg_sendnsreq(struct mg_connection *c, struct mg_str *name, int ms, - struct mg_dns *dnsc, bool ipv6) { - struct dns_data *d = NULL; - if (dnsc->url == NULL) { - mg_error(c, "DNS server URL is NULL. Call mg_mgr_init()"); - } else if (dnsc->c == NULL) { - dnsc->c = mg_connect(c->mgr, dnsc->url, NULL, NULL); - if (dnsc->c != NULL) { - dnsc->c->pfn = dns_cb; - // dnsc->c->is_hexdumping = 1; - } - } - if (dnsc->c == NULL) { - mg_error(c, "resolver"); - } else if ((d = (struct dns_data *) calloc(1, sizeof(*d))) == NULL) { - mg_error(c, "resolve OOM"); - } else { - struct dns_data *reqs = (struct dns_data *) c->mgr->active_dns_requests; - d->txnid = reqs ? (uint16_t) (reqs->txnid + 1) : 1; - d->next = (struct dns_data *) c->mgr->active_dns_requests; - c->mgr->active_dns_requests = d; - d->expire = mg_millis() + (uint64_t) ms; - d->c = c; - c->is_resolving = 1; - MG_VERBOSE(("%lu resolving %.*s @ %s, txnid %hu", c->id, (int) name->len, - name->buf, dnsc->url, d->txnid)); - if (!mg_dns_send(dnsc->c, name, d->txnid, ipv6)) { - mg_error(dnsc->c, "DNS send"); - } - } +static bool packed_rename(const char *from, const char *to) { + (void) from, (void) to; + return false; } -void mg_resolve(struct mg_connection *c, const char *url) { - struct mg_str host = mg_url_host(url); - c->rem.port = mg_htons(mg_url_port(url)); - if (mg_aton(host, &c->rem)) { - // host is an IP address, do not fire name resolution - mg_connect_resolved(c); - } else { - // host is not an IP, send DNS resolution request - struct mg_dns *dns = c->mgr->use_dns6 ? &c->mgr->dns6 : &c->mgr->dns4; - mg_sendnsreq(c, &host, c->mgr->dnstimeout, dns, c->mgr->use_dns6); - } +static bool packed_remove(const char *path) { + (void) path; + return false; +} + +static bool packed_mkdir(const char *path) { + (void) path; + return false; } +struct mg_fs mg_fs_packed = { + packed_stat, packed_list, packed_open, packed_close, packed_read, + packed_write, packed_seek, packed_rename, packed_remove, packed_mkdir}; + #ifdef MG_ENABLE_LINES -#line 1 "src/event.c" +#line 1 "src/fs_posix.c" #endif +#if MG_ENABLE_POSIX_FS +#ifndef MG_STAT_STRUCT +#define MG_STAT_STRUCT stat +#endif +#ifndef MG_STAT_FUNC +#define MG_STAT_FUNC stat +#endif - -void mg_call(struct mg_connection *c, int ev, void *ev_data) { -#if MG_ENABLE_PROFILE - const char *names[] = { - "EV_ERROR", "EV_OPEN", "EV_POLL", "EV_RESOLVE", - "EV_CONNECT", "EV_ACCEPT", "EV_TLS_HS", "EV_READ", - "EV_WRITE", "EV_CLOSE", "EV_HTTP_MSG", "EV_HTTP_CHUNK", - "EV_WS_OPEN", "EV_WS_MSG", "EV_WS_CTL", "EV_MQTT_CMD", - "EV_MQTT_MSG", "EV_MQTT_OPEN", "EV_SNTP_TIME", "EV_USER"}; - if (ev != MG_EV_POLL && ev < (int) (sizeof(names) / sizeof(names[0]))) { - MG_PROF_ADD(c, names[ev]); +static int p_stat(const char *path, size_t *size, time_t *mtime) { +#if !defined(S_ISDIR) + MG_ERROR(("stat() API is not supported. %p %p %p", path, size, mtime)); + return 0; +#else +#if MG_ARCH == MG_ARCH_WIN32 + struct _stati64 st; + wchar_t tmp[MG_PATH_MAX]; + MultiByteToWideChar(CP_UTF8, 0, path, -1, tmp, sizeof(tmp) / sizeof(tmp[0])); + if (_wstati64(tmp, &st) != 0) return 0; + // If path is a symlink, windows reports 0 in st.st_size. + // Get a real file size by opening it and jumping to the end + if (st.st_size == 0 && (st.st_mode & _S_IFREG)) { + FILE *fp = _wfopen(tmp, L"rb"); + if (fp != NULL) { + fseek(fp, 0, SEEK_END); + if (ftell(fp) > 0) st.st_size = ftell(fp); // Use _ftelli64 on win10+ + fclose(fp); + } } +#else + struct MG_STAT_STRUCT st; + if (MG_STAT_FUNC(path, &st) != 0) return 0; +#endif + if (size) *size = (size_t) st.st_size; + if (mtime) *mtime = st.st_mtime; + return MG_FS_READ | MG_FS_WRITE | (S_ISDIR(st.st_mode) ? MG_FS_DIR : 0); #endif - // Fire protocol handler first, user handler second. See #2559 - if (c->pfn != NULL) c->pfn(c, ev, ev_data); - if (c->fn != NULL) c->fn(c, ev, ev_data); -} - -void mg_error(struct mg_connection *c, const char *fmt, ...) { - char buf[64]; - va_list ap; - va_start(ap, fmt); - mg_vsnprintf(buf, sizeof(buf), fmt, &ap); - va_end(ap); - MG_ERROR(("%lu %ld %s", c->id, c->fd, buf)); - c->is_closing = 1; // Set is_closing before sending MG_EV_CALL - mg_call(c, MG_EV_ERROR, buf); // Let user handler override it } -#ifdef MG_ENABLE_LINES -#line 1 "src/flash.c" -#endif +#if MG_ARCH == MG_ARCH_WIN32 +struct dirent { + char d_name[MAX_PATH]; +}; +typedef struct win32_dir { + HANDLE handle; + WIN32_FIND_DATAW info; + struct dirent result; +} DIR; +#if 0 +int gettimeofday(struct timeval *tv, void *tz) { + FILETIME ft; + unsigned __int64 tmpres = 0; + if (tv != NULL) { + GetSystemTimeAsFileTime(&ft); + tmpres |= ft.dwHighDateTime; + tmpres <<= 32; + tmpres |= ft.dwLowDateTime; + tmpres /= 10; // convert into microseconds + tmpres -= (int64_t) 11644473600000000; + tv->tv_sec = (long) (tmpres / 1000000UL); + tv->tv_usec = (long) (tmpres % 1000000UL); + } + (void) tz; + return 0; +} +#endif +static int to_wchar(const char *path, wchar_t *wbuf, size_t wbuf_len) { + int ret; + char buf[MAX_PATH * 2], buf2[MAX_PATH * 2], *p; + strncpy(buf, path, sizeof(buf)); + buf[sizeof(buf) - 1] = '\0'; + // Trim trailing slashes. Leave backslash for paths like "X:\" + p = buf + strlen(buf) - 1; + while (p > buf && p[-1] != ':' && (p[0] == '\\' || p[0] == '/')) *p-- = '\0'; + memset(wbuf, 0, wbuf_len * sizeof(wchar_t)); + ret = MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len); + // Convert back to Unicode. If doubly-converted string does not match the + // original, something is fishy, reject. + WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2), + NULL, NULL); + if (strcmp(buf, buf2) != 0) { + wbuf[0] = L'\0'; + ret = 0; + } + return ret; +} -static char *s_addr; // Current address to write to -static size_t s_size; // Firmware size to flash. In-progress indicator -static uint32_t s_crc32; // Firmware checksum +DIR *opendir(const char *name) { + DIR *d = NULL; + wchar_t wpath[MAX_PATH]; + DWORD attrs; -bool mg_ota_flash_begin(size_t new_firmware_size, struct mg_flash *flash) { - bool ok = false; - if (s_size) { - MG_ERROR(("OTA already in progress. Call mg_ota_end()")); + if (name == NULL) { + SetLastError(ERROR_BAD_ARGUMENTS); + } else if ((d = (DIR *) calloc(1, sizeof(*d))) == NULL) { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); } else { - size_t half = flash->size / 2; - s_crc32 = 0; - s_addr = (char *) flash->start + half; - MG_DEBUG(("FW %lu bytes, max %lu", new_firmware_size, half)); - if (new_firmware_size < half) { - ok = true; - s_size = new_firmware_size; - MG_INFO(("Starting OTA, firmware size %lu", s_size)); + to_wchar(name, wpath, sizeof(wpath) / sizeof(wpath[0])); + attrs = GetFileAttributesW(wpath); + if (attrs != 0Xffffffff && (attrs & FILE_ATTRIBUTE_DIRECTORY)) { + (void) wcscat(wpath, L"\\*"); + d->handle = FindFirstFileW(wpath, &d->info); + d->result.d_name[0] = '\0'; } else { - MG_ERROR(("Firmware %lu is too big to fit %lu", new_firmware_size, half)); + free(d); + d = NULL; } } - return ok; + return d; } -bool mg_ota_flash_write(const void *buf, size_t len, struct mg_flash *flash) { - bool ok = false; - if (s_size == 0) { - MG_ERROR(("OTA is not started, call mg_ota_begin()")); +int closedir(DIR *d) { + int result = 0; + if (d != NULL) { + if (d->handle != INVALID_HANDLE_VALUE) + result = FindClose(d->handle) ? 0 : -1; + free(d); } else { - size_t len_aligned_down = MG_ROUND_DOWN(len, flash->align); - if (len_aligned_down) ok = flash->write_fn(s_addr, buf, len_aligned_down); - if (len_aligned_down < len) { - size_t left = len - len_aligned_down; - char tmp[flash->align]; - memset(tmp, 0xff, sizeof(tmp)); - memcpy(tmp, (char *) buf + len_aligned_down, left); - ok = flash->write_fn(s_addr + len_aligned_down, tmp, sizeof(tmp)); - } - s_crc32 = mg_crc32(s_crc32, (char *) buf, len); // Update CRC - MG_DEBUG(("%#x %p %lu -> %d", s_addr - len, buf, len, ok)); - s_addr += len; + result = -1; + SetLastError(ERROR_BAD_ARGUMENTS); } - return ok; + return result; } -bool mg_ota_flash_end(struct mg_flash *flash) { - char *base = (char *) flash->start + flash->size / 2; - bool ok = false; - if (s_size) { - size_t size = (size_t) (s_addr - base); - uint32_t crc32 = mg_crc32(0, base, s_size); - if (size == s_size && crc32 == s_crc32) ok = true; - MG_DEBUG(("CRC: %x/%x, size: %lu/%lu, status: %s", s_crc32, crc32, s_size, - size, ok ? "ok" : "fail")); - s_size = 0; - if (ok) ok = flash->swap_fn(); +struct dirent *readdir(DIR *d) { + struct dirent *result = NULL; + if (d != NULL) { + memset(&d->result, 0, sizeof(d->result)); + if (d->handle != INVALID_HANDLE_VALUE) { + result = &d->result; + WideCharToMultiByte(CP_UTF8, 0, d->info.cFileName, -1, result->d_name, + sizeof(result->d_name), NULL, NULL); + if (!FindNextFileW(d->handle, &d->info)) { + FindClose(d->handle); + d->handle = INVALID_HANDLE_VALUE; + } + } else { + SetLastError(ERROR_FILE_NOT_FOUND); + } + } else { + SetLastError(ERROR_BAD_ARGUMENTS); } - MG_INFO(("Finishing OTA: %s", ok ? "ok" : "fail")); - return ok; + return result; } - -#ifdef MG_ENABLE_LINES -#line 1 "src/fmt.c" #endif +static void p_list(const char *dir, void (*fn)(const char *, void *), + void *userdata) { +#if MG_ENABLE_DIRLIST + struct dirent *dp; + DIR *dirp; + if ((dirp = (opendir(dir))) == NULL) return; + while ((dp = readdir(dirp)) != NULL) { + if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue; + fn(dp->d_name, userdata); + } + closedir(dirp); +#else + (void) dir, (void) fn, (void) userdata; +#endif +} +static void *p_open(const char *path, int flags) { +#if MG_ARCH == MG_ARCH_WIN32 + const char *mode = flags == MG_FS_READ ? "rb" : "a+b"; + wchar_t b1[MG_PATH_MAX], b2[10]; + MultiByteToWideChar(CP_UTF8, 0, path, -1, b1, sizeof(b1) / sizeof(b1[0])); + MultiByteToWideChar(CP_UTF8, 0, mode, -1, b2, sizeof(b2) / sizeof(b2[0])); + return (void *) _wfopen(b1, b2); +#else + const char *mode = flags == MG_FS_READ ? "rbe" : "a+be"; // e for CLOEXEC + return (void *) fopen(path, mode); +#endif +} - -static bool is_digit(int c) { - return c >= '0' && c <= '9'; +static void p_close(void *fp) { + fclose((FILE *) fp); } -static int addexp(char *buf, int e, int sign) { - int n = 0; - buf[n++] = 'e'; - buf[n++] = (char) sign; - if (e > 400) return 0; - if (e < 10) buf[n++] = '0'; - if (e >= 100) buf[n++] = (char) (e / 100 + '0'), e -= 100 * (e / 100); - if (e >= 10) buf[n++] = (char) (e / 10 + '0'), e -= 10 * (e / 10); - buf[n++] = (char) (e + '0'); - return n; +static size_t p_read(void *fp, void *buf, size_t len) { + return fread(buf, 1, len, (FILE *) fp); } -static int xisinf(double x) { - union { - double f; - uint64_t u; - } ieee754 = {x}; - return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) == 0x7ff00000 && - ((unsigned) ieee754.u == 0); +static size_t p_write(void *fp, const void *buf, size_t len) { + return fwrite(buf, 1, len, (FILE *) fp); } -static int xisnan(double x) { - union { - double f; - uint64_t u; - } ieee754 = {x}; - return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) + - ((unsigned) ieee754.u != 0) > - 0x7ff00000; +static size_t p_seek(void *fp, size_t offset) { +#if (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64) || \ + (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L) || \ + (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 600) + if (fseeko((FILE *) fp, (off_t) offset, SEEK_SET) != 0) (void) 0; +#else + if (fseek((FILE *) fp, (long) offset, SEEK_SET) != 0) (void) 0; +#endif + return (size_t) ftell((FILE *) fp); } -static size_t mg_dtoa(char *dst, size_t dstlen, double d, int width, bool tz) { - char buf[40]; - int i, s = 0, n = 0, e = 0; - double t, mul, saved; - if (d == 0.0) return mg_snprintf(dst, dstlen, "%s", "0"); - if (xisinf(d)) return mg_snprintf(dst, dstlen, "%s", d > 0 ? "inf" : "-inf"); - if (xisnan(d)) return mg_snprintf(dst, dstlen, "%s", "nan"); - if (d < 0.0) d = -d, buf[s++] = '-'; - - // Round - saved = d; - mul = 1.0; - while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0; - while (d <= 1.0 && d / mul <= 1.0) mul /= 10.0; - for (i = 0, t = mul * 5; i < width; i++) t /= 10.0; - d += t; - // Calculate exponent, and 'mul' for scientific representation - mul = 1.0; - while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0, e++; - while (d < 1.0 && d / mul < 1.0) mul /= 10.0, e--; - // printf(" --> %g %d %g %g\n", saved, e, t, mul); - - if (e >= width && width > 1) { - n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width, tz); - // printf(" --> %.*g %d [%.*s]\n", 10, d / t, e, n, buf); - n += addexp(buf + s + n, e, '+'); - return mg_snprintf(dst, dstlen, "%.*s", n, buf); - } else if (e <= -width && width > 1) { - n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width, tz); - // printf(" --> %.*g %d [%.*s]\n", 10, d / mul, e, n, buf); - n += addexp(buf + s + n, -e, '-'); - return mg_snprintf(dst, dstlen, "%.*s", n, buf); - } else { - for (i = 0, t = mul; t >= 1.0 && s + n < (int) sizeof(buf); i++) { - int ch = (int) (d / t); - if (n > 0 || ch > 0) buf[s + n++] = (char) (ch + '0'); - d -= ch * t; - t /= 10.0; - } - // printf(" --> [%g] -> %g %g (%d) [%.*s]\n", saved, d, t, n, s + n, buf); - if (n == 0) buf[s++] = '0'; - while (t >= 1.0 && n + s < (int) sizeof(buf)) buf[n++] = '0', t /= 10.0; - if (s + n < (int) sizeof(buf)) buf[n + s++] = '.'; - // printf(" 1--> [%g] -> [%.*s]\n", saved, s + n, buf); - for (i = 0, t = 0.1; s + n < (int) sizeof(buf) && n < width; i++) { - int ch = (int) (d / t); - buf[s + n++] = (char) (ch + '0'); - d -= ch * t; - t /= 10.0; - } - } - while (tz && n > 0 && buf[s + n - 1] == '0') n--; // Trim trailing zeroes - if (n > 0 && buf[s + n - 1] == '.') n--; // Trim trailing dot - n += s; - if (n >= (int) sizeof(buf)) n = (int) sizeof(buf) - 1; - buf[n] = '\0'; - return mg_snprintf(dst, dstlen, "%s", buf); +static bool p_rename(const char *from, const char *to) { + return rename(from, to) == 0; } -static size_t mg_lld(char *buf, int64_t val, bool is_signed, bool is_hex) { - const char *letters = "0123456789abcdef"; - uint64_t v = (uint64_t) val; - size_t s = 0, n, i; - if (is_signed && val < 0) buf[s++] = '-', v = (uint64_t) (-val); - // This loop prints a number in reverse order. I guess this is because we - // write numbers from right to left: least significant digit comes last. - // Maybe because we use Arabic numbers, and Arabs write RTL? - if (is_hex) { - for (n = 0; v; v >>= 4) buf[s + n++] = letters[v & 15]; - } else { - for (n = 0; v; v /= 10) buf[s + n++] = letters[v % 10]; - } - // Reverse a string - for (i = 0; i < n / 2; i++) { - char t = buf[s + i]; - buf[s + i] = buf[s + n - i - 1], buf[s + n - i - 1] = t; - } - if (val == 0) buf[n++] = '0'; // Handle special case - return n + s; +static bool p_remove(const char *path) { + return remove(path) == 0; } -static size_t scpy(void (*out)(char, void *), void *ptr, char *buf, - size_t len) { - size_t i = 0; - while (i < len && buf[i] != '\0') out(buf[i++], ptr); - return i; +static bool p_mkdir(const char *path) { + return mkdir(path, 0775) == 0; } -size_t mg_xprintf(void (*out)(char, void *), void *ptr, const char *fmt, ...) { - size_t len = 0; - va_list ap; - va_start(ap, fmt); - len = mg_vxprintf(out, ptr, fmt, &ap); - va_end(ap); - return len; -} +#else -size_t mg_vxprintf(void (*out)(char, void *), void *param, const char *fmt, - va_list *ap) { - size_t i = 0, n = 0; - while (fmt[i] != '\0') { - if (fmt[i] == '%') { - size_t j, k, x = 0, is_long = 0, w = 0 /* width */, pr = ~0U /* prec */; - char pad = ' ', minus = 0, c = fmt[++i]; - if (c == '#') x++, c = fmt[++i]; - if (c == '-') minus++, c = fmt[++i]; - if (c == '0') pad = '0', c = fmt[++i]; - while (is_digit(c)) w *= 10, w += (size_t) (c - '0'), c = fmt[++i]; - if (c == '.') { - c = fmt[++i]; - if (c == '*') { - pr = (size_t) va_arg(*ap, int); - c = fmt[++i]; - } else { - pr = 0; - while (is_digit(c)) pr *= 10, pr += (size_t) (c - '0'), c = fmt[++i]; - } - } - while (c == 'h') c = fmt[++i]; // Treat h and hh as int - if (c == 'l') { - is_long++, c = fmt[++i]; - if (c == 'l') is_long++, c = fmt[++i]; - } - if (c == 'p') x = 1, is_long = 1; - if (c == 'd' || c == 'u' || c == 'x' || c == 'X' || c == 'p' || - c == 'g' || c == 'f') { - bool s = (c == 'd'), h = (c == 'x' || c == 'X' || c == 'p'); - char tmp[40]; - size_t xl = x ? 2 : 0; - if (c == 'g' || c == 'f') { - double v = va_arg(*ap, double); - if (pr == ~0U) pr = 6; - k = mg_dtoa(tmp, sizeof(tmp), v, (int) pr, c == 'g'); - } else if (is_long == 2) { - int64_t v = va_arg(*ap, int64_t); - k = mg_lld(tmp, v, s, h); - } else if (is_long == 1) { - long v = va_arg(*ap, long); - k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned long) v, s, h); - } else { - int v = va_arg(*ap, int); - k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned) v, s, h); - } - for (j = 0; j < xl && w > 0; j++) w--; - for (j = 0; pad == ' ' && !minus && k < w && j + k < w; j++) - n += scpy(out, param, &pad, 1); - n += scpy(out, param, (char *) "0x", xl); - for (j = 0; pad == '0' && k < w && j + k < w; j++) - n += scpy(out, param, &pad, 1); - n += scpy(out, param, tmp, k); - for (j = 0; pad == ' ' && minus && k < w && j + k < w; j++) - n += scpy(out, param, &pad, 1); - } else if (c == 'm' || c == 'M') { - mg_pm_t f = va_arg(*ap, mg_pm_t); - if (c == 'm') out('"', param); - n += f(out, param, ap); - if (c == 'm') n += 2, out('"', param); - } else if (c == 'c') { - int ch = va_arg(*ap, int); - out((char) ch, param); - n++; - } else if (c == 's') { - char *p = va_arg(*ap, char *); - if (pr == ~0U) pr = p == NULL ? 0 : strlen(p); - for (j = 0; !minus && pr < w && j + pr < w; j++) - n += scpy(out, param, &pad, 1); - n += scpy(out, param, p, pr); - for (j = 0; minus && pr < w && j + pr < w; j++) - n += scpy(out, param, &pad, 1); - } else if (c == '%') { - out('%', param); - n++; - } else { - out('%', param); - out(c, param); - n += 2; - } - i++; - } else { - out(fmt[i], param), n++, i++; - } - } - return n; +static int p_stat(const char *path, size_t *size, time_t *mtime) { + (void) path, (void) size, (void) mtime; + return 0; +} +static void p_list(const char *path, void (*fn)(const char *, void *), + void *userdata) { + (void) path, (void) fn, (void) userdata; +} +static void *p_open(const char *path, int flags) { + (void) path, (void) flags; + return NULL; +} +static void p_close(void *fp) { + (void) fp; +} +static size_t p_read(void *fd, void *buf, size_t len) { + (void) fd, (void) buf, (void) len; + return 0; +} +static size_t p_write(void *fd, const void *buf, size_t len) { + (void) fd, (void) buf, (void) len; + return 0; +} +static size_t p_seek(void *fd, size_t offset) { + (void) fd, (void) offset; + return (size_t) ~0; } +static bool p_rename(const char *from, const char *to) { + (void) from, (void) to; + return false; +} +static bool p_remove(const char *path) { + (void) path; + return false; +} +static bool p_mkdir(const char *path) { + (void) path; + return false; +} +#endif + +struct mg_fs mg_fs_posix = {p_stat, p_list, p_open, p_close, p_read, + p_write, p_seek, p_rename, p_remove, p_mkdir}; #ifdef MG_ENABLE_LINES -#line 1 "src/fs.c" +#line 1 "src/http.c" #endif -struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int flags) { - struct mg_fd *fd = (struct mg_fd *) calloc(1, sizeof(*fd)); - if (fd != NULL) { - fd->fd = fs->op(path, flags); - fd->fs = fs; - if (fd->fd == NULL) { - free(fd); - fd = NULL; - } - } - return fd; -} -void mg_fs_close(struct mg_fd *fd) { - if (fd != NULL) { - fd->fs->cl(fd->fd); - free(fd); - } -} - -struct mg_str mg_file_read(struct mg_fs *fs, const char *path) { - struct mg_str result = {NULL, 0}; - void *fp; - fs->st(path, &result.len, NULL); - if ((fp = fs->op(path, MG_FS_READ)) != NULL) { - result.buf = (char *) calloc(1, result.len + 1); - if (result.buf != NULL && - fs->rd(fp, (void *) result.buf, result.len) != result.len) { - free((void *) result.buf); - result.buf = NULL; - } - fs->cl(fp); - } - if (result.buf == NULL) result.len = 0; - return result; -} - -bool mg_file_write(struct mg_fs *fs, const char *path, const void *buf, - size_t len) { - bool result = false; - struct mg_fd *fd; - char tmp[MG_PATH_MAX]; - mg_snprintf(tmp, sizeof(tmp), "%s..%d", path, rand()); - if ((fd = mg_fs_open(fs, tmp, MG_FS_WRITE)) != NULL) { - result = fs->wr(fd->fd, buf, len) == len; - mg_fs_close(fd); - if (result) { - fs->rm(path); - fs->mv(tmp, path); - } else { - fs->rm(tmp); - } - } - return result; -} -bool mg_file_printf(struct mg_fs *fs, const char *path, const char *fmt, ...) { - va_list ap; - char *data; - bool result = false; - va_start(ap, fmt); - data = mg_vmprintf(fmt, &ap); - va_end(ap); - result = mg_file_write(fs, path, data, strlen(data)); - free(data); - return result; -} -// This helper function allows to scan a filesystem in a sequential way, -// without using callback function: -// char buf[100] = ""; -// while (mg_fs_ls(&mg_fs_posix, "./", buf, sizeof(buf))) { -// ... -static void mg_fs_ls_fn(const char *filename, void *param) { - struct mg_str *s = (struct mg_str *) param; - if (s->buf[0] == '\0') { - mg_snprintf((char *) s->buf, s->len, "%s", filename); - } else if (strcmp(s->buf, filename) == 0) { - ((char *) s->buf)[0] = '\0'; // Fetch next file - } -} -bool mg_fs_ls(struct mg_fs *fs, const char *path, char *buf, size_t len) { - struct mg_str s = {buf, len}; - fs->ls(path, mg_fs_ls_fn, &s); - return buf[0] != '\0'; -} -#ifdef MG_ENABLE_LINES -#line 1 "src/fs_fat.c" -#endif -#if MG_ENABLE_FATFS -#include -static int mg_days_from_epoch(int y, int m, int d) { - y -= m <= 2; - int era = y / 400; - int yoe = y - era * 400; - int doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; - int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; - return era * 146097 + doe - 719468; +static int mg_ncasecmp(const char *s1, const char *s2, size_t len) { + int diff = 0; + if (len > 0) do { + int c = *s1++, d = *s2++; + if (c >= 'A' && c <= 'Z') c += 'a' - 'A'; + if (d >= 'A' && d <= 'Z') d += 'a' - 'A'; + diff = c - d; + } while (diff == 0 && s1[-1] != '\0' && --len > 0); + return diff; } -static time_t mg_timegm(const struct tm *t) { - int year = t->tm_year + 1900; - int month = t->tm_mon; // 0-11 - if (month > 11) { - year += month / 12; - month %= 12; - } else if (month < 0) { - int years_diff = (11 - month) / 12; - year -= years_diff; - month += 12 * years_diff; +bool mg_to_size_t(struct mg_str str, size_t *val); +bool mg_to_size_t(struct mg_str str, size_t *val) { + size_t i = 0, max = (size_t) -1, max2 = max / 10, result = 0, ndigits = 0; + while (i < str.len && (str.buf[i] == ' ' || str.buf[i] == '\t')) i++; + if (i < str.len && str.buf[i] == '-') return false; + while (i < str.len && str.buf[i] >= '0' && str.buf[i] <= '9') { + size_t digit = (size_t) (str.buf[i] - '0'); + if (result > max2) return false; // Overflow + result *= 10; + if (result > max - digit) return false; // Overflow + result += digit; + i++, ndigits++; } - int x = mg_days_from_epoch(year, month + 1, t->tm_mday); - return 60 * (60 * (24L * x + t->tm_hour) + t->tm_min) + t->tm_sec; + while (i < str.len && (str.buf[i] == ' ' || str.buf[i] == '\t')) i++; + if (ndigits == 0) return false; // #2322: Content-Length = 1 * DIGIT + if (i != str.len) return false; // Ditto + *val = (size_t) result; + return true; } -static time_t ff_time_to_epoch(uint16_t fdate, uint16_t ftime) { - struct tm tm; - memset(&tm, 0, sizeof(struct tm)); - tm.tm_sec = (ftime << 1) & 0x3e; - tm.tm_min = ((ftime >> 5) & 0x3f); - tm.tm_hour = ((ftime >> 11) & 0x1f); - tm.tm_mday = (fdate & 0x1f); - tm.tm_mon = ((fdate >> 5) & 0x0f) - 1; - tm.tm_year = ((fdate >> 9) & 0x7f) + 80; - return mg_timegm(&tm); -} +// Chunk deletion marker is the MSB in the "processed" counter +#define MG_DMARK ((size_t) 1 << (sizeof(size_t) * 8 - 1)) -static int ff_stat(const char *path, size_t *size, time_t *mtime) { - FILINFO fi; - if (path[0] == '\0') { - if (size) *size = 0; - if (mtime) *mtime = 0; - return MG_FS_DIR; - } else if (f_stat(path, &fi) == 0) { - if (size) *size = (size_t) fi.fsize; - if (mtime) *mtime = ff_time_to_epoch(fi.fdate, fi.ftime); - return MG_FS_READ | MG_FS_WRITE | ((fi.fattrib & AM_DIR) ? MG_FS_DIR : 0); - } else { - return 0; - } -} +// Multipart POST example: +// --xyz +// Content-Disposition: form-data; name="val" +// +// abcdef +// --xyz +// Content-Disposition: form-data; name="foo"; filename="a.txt" +// Content-Type: text/plain +// +// hello world +// +// --xyz-- +size_t mg_http_next_multipart(struct mg_str body, size_t ofs, + struct mg_http_part *part) { + struct mg_str cd = mg_str_n("Content-Disposition", 19); + const char *s = body.buf; + size_t b = ofs, h1, h2, b1, b2, max = body.len; -static void ff_list(const char *dir, void (*fn)(const char *, void *), - void *userdata) { - DIR d; - FILINFO fi; - if (f_opendir(&d, dir) == FR_OK) { - while (f_readdir(&d, &fi) == FR_OK && fi.fname[0] != '\0') { - if (!strcmp(fi.fname, ".") || !strcmp(fi.fname, "..")) continue; - fn(fi.fname, userdata); + // Init part params + if (part != NULL) part->name = part->filename = part->body = mg_str_n(0, 0); + + // Skip boundary + while (b + 2 < max && s[b] != '\r' && s[b + 1] != '\n') b++; + if (b <= ofs || b + 2 >= max) return 0; + // MG_INFO(("B: %zu %zu [%.*s]", ofs, b - ofs, (int) (b - ofs), s)); + + // Skip headers + h1 = h2 = b + 2; + for (;;) { + while (h2 + 2 < max && s[h2] != '\r' && s[h2 + 1] != '\n') h2++; + if (h2 == h1) break; + if (h2 + 2 >= max) return 0; + // MG_INFO(("Header: [%.*s]", (int) (h2 - h1), &s[h1])); + if (part != NULL && h1 + cd.len + 2 < h2 && s[h1 + cd.len] == ':' && + mg_ncasecmp(&s[h1], cd.buf, cd.len) == 0) { + struct mg_str v = mg_str_n(&s[h1 + cd.len + 2], h2 - (h1 + cd.len + 2)); + part->name = mg_http_get_header_var(v, mg_str_n("name", 4)); + part->filename = mg_http_get_header_var(v, mg_str_n("filename", 8)); } - f_closedir(&d); + h1 = h2 = h2 + 2; } + b1 = b2 = h2 + 2; + while (b2 + 2 + (b - ofs) + 2 < max && !(s[b2] == '\r' && s[b2 + 1] == '\n' && + memcmp(&s[b2 + 2], s, b - ofs) == 0)) + b2++; + + if (b2 + 2 >= max) return 0; + if (part != NULL) part->body = mg_str_n(&s[b1], b2 - b1); + // MG_INFO(("Body: [%.*s]", (int) (b2 - b1), &s[b1])); + return b2 + 2; } -static void *ff_open(const char *path, int flags) { - FIL f; - unsigned char mode = FA_READ; - if (flags & MG_FS_WRITE) mode |= FA_WRITE | FA_OPEN_ALWAYS | FA_OPEN_APPEND; - if (f_open(&f, path, mode) == 0) { - FIL *fp; - if ((fp = calloc(1, sizeof(*fp))) != NULL) { - memcpy(fp, &f, sizeof(*fp)); - return fp; +void mg_http_bauth(struct mg_connection *c, const char *user, + const char *pass) { + struct mg_str u = mg_str(user), p = mg_str(pass); + size_t need = c->send.len + 36 + (u.len + p.len) * 2; + if (c->send.size < need) mg_iobuf_resize(&c->send, need); + if (c->send.size >= need) { + size_t i, n = 0; + char *buf = (char *) &c->send.buf[c->send.len]; + memcpy(buf, "Authorization: Basic ", 21); // DON'T use mg_send! + for (i = 0; i < u.len; i++) { + n = mg_base64_update(((unsigned char *) u.buf)[i], buf + 21, n); + } + if (p.len > 0) { + n = mg_base64_update(':', buf + 21, n); + for (i = 0; i < p.len; i++) { + n = mg_base64_update(((unsigned char *) p.buf)[i], buf + 21, n); + } } + n = mg_base64_final(buf + 21, n); + c->send.len += 21 + (size_t) n + 2; + memcpy(&c->send.buf[c->send.len - 2], "\r\n", 2); + } else { + MG_ERROR(("%lu oom %d->%d ", c->id, (int) c->send.size, (int) need)); } - return NULL; } -static void ff_close(void *fp) { - if (fp != NULL) { - f_close((FIL *) fp); - free(fp); +struct mg_str mg_http_var(struct mg_str buf, struct mg_str name) { + struct mg_str entry, k, v, result = mg_str_n(NULL, 0); + while (mg_span(buf, &entry, &buf, '&')) { + if (mg_span(entry, &k, &v, '=') && name.len == k.len && + mg_ncasecmp(name.buf, k.buf, k.len) == 0) { + result = v; + break; + } } + return result; } -static size_t ff_read(void *fp, void *buf, size_t len) { - UINT n = 0, misalign = ((size_t) buf) & 3; - if (misalign) { - char aligned[4]; - f_read((FIL *) fp, aligned, len > misalign ? misalign : len, &n); - memcpy(buf, aligned, n); - } else { - f_read((FIL *) fp, buf, len, &n); - } - return n; +int mg_http_get_var(const struct mg_str *buf, const char *name, char *dst, + size_t dst_len) { + int len; + if (dst != NULL && dst_len > 0) { + dst[0] = '\0'; // If destination buffer is valid, always nul-terminate it + } + if (dst == NULL || dst_len == 0) { + len = -2; // Bad destination + } else if (buf->buf == NULL || name == NULL || buf->len == 0) { + len = -1; // Bad source + } else { + struct mg_str v = mg_http_var(*buf, mg_str(name)); + if (v.buf == NULL) { + len = -4; // Name does not exist + } else { + len = mg_url_decode(v.buf, v.len, dst, dst_len, 1); + if (len < 0) len = -3; // Failed to decode + } + } + return len; } -static size_t ff_write(void *fp, const void *buf, size_t len) { - UINT n = 0; - return f_write((FIL *) fp, (char *) buf, len, &n) == FR_OK ? n : 0; +static bool isx(int c) { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F'); } -static size_t ff_seek(void *fp, size_t offset) { - f_lseek((FIL *) fp, offset); - return offset; +int mg_url_decode(const char *src, size_t src_len, char *dst, size_t dst_len, + int is_form_url_encoded) { + size_t i, j; + for (i = j = 0; i < src_len && j + 1 < dst_len; i++, j++) { + if (src[i] == '%') { + // Use `i + 2 < src_len`, not `i < src_len - 2`, note small src_len + if (i + 2 < src_len && isx(src[i + 1]) && isx(src[i + 2])) { + mg_str_to_num(mg_str_n(src + i + 1, 2), 16, &dst[j], sizeof(uint8_t)); + i += 2; + } else { + return -1; + } + } else if (is_form_url_encoded && src[i] == '+') { + dst[j] = ' '; + } else { + dst[j] = src[i]; + } + } + if (j < dst_len) dst[j] = '\0'; // Null-terminate the destination + return i >= src_len && j < dst_len ? (int) j : -1; } -static bool ff_rename(const char *from, const char *to) { - return f_rename(from, to) == FR_OK; +static bool isok(uint8_t c) { + return c == '\n' || c == '\r' || c == '\t' || c >= ' '; } -static bool ff_remove(const char *path) { - return f_unlink(path) == FR_OK; +int mg_http_get_request_len(const unsigned char *buf, size_t buf_len) { + size_t i; + for (i = 0; i < buf_len; i++) { + if (!isok(buf[i])) return -1; + if ((i > 0 && buf[i] == '\n' && buf[i - 1] == '\n') || + (i > 3 && buf[i] == '\n' && buf[i - 1] == '\r' && buf[i - 2] == '\n')) + return (int) i + 1; + } + return 0; +} +struct mg_str *mg_http_get_header(struct mg_http_message *h, const char *name) { + size_t i, n = strlen(name), max = sizeof(h->headers) / sizeof(h->headers[0]); + for (i = 0; i < max && h->headers[i].name.len > 0; i++) { + struct mg_str *k = &h->headers[i].name, *v = &h->headers[i].value; + if (n == k->len && mg_ncasecmp(k->buf, name, n) == 0) return v; + } + return NULL; } -static bool ff_mkdir(const char *path) { - return f_mkdir(path) == FR_OK; +// Is it a valid utf-8 continuation byte +static bool vcb(uint8_t c) { + return (c & 0xc0) == 0x80; } -struct mg_fs mg_fs_fat = {ff_stat, ff_list, ff_open, ff_close, ff_read, - ff_write, ff_seek, ff_rename, ff_remove, ff_mkdir}; -#endif +// Get character length (valid utf-8). Used to parse method, URI, headers +static size_t clen(const char *s, const char *end) { + const unsigned char *u = (unsigned char *) s, c = *u; + long n = (long) (end - s); + if (c > ' ' && c < '~') return 1; // Usual ascii printed char + if ((c & 0xe0) == 0xc0 && n > 1 && vcb(u[1])) return 2; // 2-byte UTF8 + if ((c & 0xf0) == 0xe0 && n > 2 && vcb(u[1]) && vcb(u[2])) return 3; + if ((c & 0xf8) == 0xf0 && n > 3 && vcb(u[1]) && vcb(u[2]) && vcb(u[3])) + return 4; + return 0; +} -#ifdef MG_ENABLE_LINES -#line 1 "src/fs_packed.c" -#endif +// Skip until the newline. Return advanced `s`, or NULL on error +static const char *skiptorn(const char *s, const char *end, struct mg_str *v) { + v->buf = (char *) s; + while (s < end && s[0] != '\n' && s[0] != '\r') s++, v->len++; // To newline + if (s >= end || (s[0] == '\r' && s[1] != '\n')) return NULL; // Stray \r + if (s < end && s[0] == '\r') s++; // Skip \r + if (s >= end || *s++ != '\n') return NULL; // Skip \n + return s; +} +static bool mg_http_parse_headers(const char *s, const char *end, + struct mg_http_header *h, size_t max_hdrs) { + size_t i, n; + for (i = 0; i < max_hdrs; i++) { + struct mg_str k = {NULL, 0}, v = {NULL, 0}; + if (s >= end) return false; + if (s[0] == '\n' || (s[0] == '\r' && s[1] == '\n')) break; + k.buf = (char *) s; + while (s < end && s[0] != ':' && (n = clen(s, end)) > 0) s += n, k.len += n; + if (k.len == 0) return false; // Empty name + if (s >= end || clen(s, end) == 0) return false; // Invalid UTF-8 + if (*s++ != ':') return false; // Invalid, not followed by : + // if (clen(s, end) == 0) return false; // Invalid UTF-8 + while (s < end && (s[0] == ' ' || s[0] == '\t')) s++; // Skip spaces + if ((s = skiptorn(s, end, &v)) == NULL) return false; + while (v.len > 0 && (v.buf[v.len - 1] == ' ' || v.buf[v.len - 1] == '\t')) { + v.len--; // Trim spaces + } + // MG_INFO(("--HH [%.*s] [%.*s]", (int) k.len, k.buf, (int) v.len, v.buf)); + h[i].name = k, h[i].value = v; // Success. Assign values + } + return true; +} +int mg_http_parse(const char *s, size_t len, struct mg_http_message *hm) { + int is_response, req_len = mg_http_get_request_len((unsigned char *) s, len); + const char *end = s == NULL ? NULL : s + req_len, *qs; // Cannot add to NULL + const struct mg_str *cl; + size_t n; + memset(hm, 0, sizeof(*hm)); + if (req_len <= 0) return req_len; -struct packed_file { - const char *data; - size_t size; - size_t pos; -}; + hm->message.buf = hm->head.buf = (char *) s; + hm->body.buf = (char *) end; + hm->head.len = (size_t) req_len; + hm->message.len = hm->body.len = (size_t) -1; // Set body length to infinite -#if MG_ENABLE_PACKED_FS -#else -const char *mg_unpack(const char *path, size_t *size, time_t *mtime) { - *size = 0, *mtime = 0; - (void) path; - return NULL; -} -const char *mg_unlist(size_t no) { - (void) no; - return NULL; -} -#endif + // Parse request line + hm->method.buf = (char *) s; + while (s < end && (n = clen(s, end)) > 0) s += n, hm->method.len += n; + while (s < end && s[0] == ' ') s++; // Skip spaces + hm->uri.buf = (char *) s; + while (s < end && (n = clen(s, end)) > 0) s += n, hm->uri.len += n; + while (s < end && s[0] == ' ') s++; // Skip spaces + if ((s = skiptorn(s, end, &hm->proto)) == NULL) return false; -struct mg_str mg_unpacked(const char *path) { - size_t len = 0; - const char *buf = mg_unpack(path, &len, NULL); - return mg_str_n(buf, len); -} + // If URI contains '?' character, setup query string + if ((qs = (const char *) memchr(hm->uri.buf, '?', hm->uri.len)) != NULL) { + hm->query.buf = (char *) qs + 1; + hm->query.len = (size_t) (&hm->uri.buf[hm->uri.len] - (qs + 1)); + hm->uri.len = (size_t) (qs - hm->uri.buf); + } -static int is_dir_prefix(const char *prefix, size_t n, const char *path) { - // MG_INFO(("[%.*s] [%s] %c", (int) n, prefix, path, path[n])); - return n < strlen(path) && strncmp(prefix, path, n) == 0 && - (n == 0 || path[n] == '/' || path[n - 1] == '/'); -} + // Sanity check. Allow protocol/reason to be empty + // Do this check after hm->method.len and hm->uri.len are finalised + if (hm->method.len == 0 || hm->uri.len == 0) return -1; -static int packed_stat(const char *path, size_t *size, time_t *mtime) { - const char *p; - size_t i, n = strlen(path); - if (mg_unpack(path, size, mtime)) return MG_FS_READ; // Regular file - // Scan all files. If `path` is a dir prefix for any of them, it's a dir - for (i = 0; (p = mg_unlist(i)) != NULL; i++) { - if (is_dir_prefix(path, n, p)) return MG_FS_DIR; + if (!mg_http_parse_headers(s, end, hm->headers, + sizeof(hm->headers) / sizeof(hm->headers[0]))) + return -1; // error when parsing + if ((cl = mg_http_get_header(hm, "Content-Length")) != NULL) { + if (mg_to_size_t(*cl, &hm->body.len) == false) return -1; + hm->message.len = (size_t) req_len + hm->body.len; } - return 0; -} -static void packed_list(const char *dir, void (*fn)(const char *, void *), - void *userdata) { - char buf[MG_PATH_MAX], tmp[sizeof(buf)]; - const char *path, *begin, *end; - size_t i, n = strlen(dir); - tmp[0] = '\0'; // Previously listed entry - for (i = 0; (path = mg_unlist(i)) != NULL; i++) { - if (!is_dir_prefix(dir, n, path)) continue; - begin = &path[n + 1]; - end = strchr(begin, '/'); - if (end == NULL) end = begin + strlen(begin); - mg_snprintf(buf, sizeof(buf), "%.*s", (int) (end - begin), begin); - buf[sizeof(buf) - 1] = '\0'; - // If this entry has been already listed, skip - // NOTE: we're assuming that file list is sorted alphabetically - if (strcmp(buf, tmp) == 0) continue; - fn(buf, userdata); // Not yet listed, call user function - strcpy(tmp, buf); // And save this entry as listed + // mg_http_parse() is used to parse both HTTP requests and HTTP + // responses. If HTTP response does not have Content-Length set, then + // body is read until socket is closed, i.e. body.len is infinite (~0). + // + // For HTTP requests though, according to + // http://tools.ietf.org/html/rfc7231#section-8.1.3, + // only POST and PUT methods have defined body semantics. + // Therefore, if Content-Length is not specified and methods are + // not one of PUT or POST, set body length to 0. + // + // So, if it is HTTP request, and Content-Length is not set, + // and method is not (PUT or POST) then reset body length to zero. + is_response = mg_ncasecmp(hm->method.buf, "HTTP/", 5) == 0; + if (hm->body.len == (size_t) ~0 && !is_response && + mg_strcasecmp(hm->method, mg_str("PUT")) != 0 && + mg_strcasecmp(hm->method, mg_str("POST")) != 0) { + hm->body.len = 0; + hm->message.len = (size_t) req_len; } -} -static void *packed_open(const char *path, int flags) { - size_t size = 0; - const char *data = mg_unpack(path, &size, NULL); - struct packed_file *fp = NULL; - if (data == NULL) return NULL; - if (flags & MG_FS_WRITE) return NULL; - if ((fp = (struct packed_file *) calloc(1, sizeof(*fp))) != NULL) { - fp->size = size; - fp->data = data; + // The 204 (No content) responses also have 0 body length + if (hm->body.len == (size_t) ~0 && is_response && + mg_strcasecmp(hm->uri, mg_str("204")) == 0) { + hm->body.len = 0; + hm->message.len = (size_t) req_len; } - return (void *) fp; -} + if (hm->message.len < (size_t) req_len) return -1; // Overflow protection -static void packed_close(void *fp) { - if (fp != NULL) free(fp); + return req_len; } -static size_t packed_read(void *fd, void *buf, size_t len) { - struct packed_file *fp = (struct packed_file *) fd; - if (fp->pos + len > fp->size) len = fp->size - fp->pos; - memcpy(buf, &fp->data[fp->pos], len); - fp->pos += len; - return len; +static void mg_http_vprintf_chunk(struct mg_connection *c, const char *fmt, + va_list *ap) { + size_t len = c->send.len; + mg_send(c, " \r\n", 10); + mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap); + if (c->send.len >= len + 10) { + mg_snprintf((char *) c->send.buf + len, 9, "%08lx", c->send.len - len - 10); + c->send.buf[len + 8] = '\r'; + if (c->send.len == len + 10) c->is_resp = 0; // Last chunk, reset marker + } + mg_send(c, "\r\n", 2); } -static size_t packed_write(void *fd, const void *buf, size_t len) { - (void) fd, (void) buf, (void) len; - return 0; +void mg_http_printf_chunk(struct mg_connection *c, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + mg_http_vprintf_chunk(c, fmt, &ap); + va_end(ap); } -static size_t packed_seek(void *fd, size_t offset) { - struct packed_file *fp = (struct packed_file *) fd; - fp->pos = offset; - if (fp->pos > fp->size) fp->pos = fp->size; - return fp->pos; +void mg_http_write_chunk(struct mg_connection *c, const char *buf, size_t len) { + mg_printf(c, "%lx\r\n", (unsigned long) len); + mg_send(c, buf, len); + mg_send(c, "\r\n", 2); + if (len == 0) c->is_resp = 0; } -static bool packed_rename(const char *from, const char *to) { - (void) from, (void) to; - return false; +// clang-format off +static const char *mg_http_status_code_str(int status_code) { + switch (status_code) { + case 100: return "Continue"; + case 101: return "Switching Protocols"; + case 102: return "Processing"; + case 200: return "OK"; + case 201: return "Created"; + case 202: return "Accepted"; + case 203: return "Non-authoritative Information"; + case 204: return "No Content"; + case 205: return "Reset Content"; + case 206: return "Partial Content"; + case 207: return "Multi-Status"; + case 208: return "Already Reported"; + case 226: return "IM Used"; + case 300: return "Multiple Choices"; + case 301: return "Moved Permanently"; + case 302: return "Found"; + case 303: return "See Other"; + case 304: return "Not Modified"; + case 305: return "Use Proxy"; + case 307: return "Temporary Redirect"; + case 308: return "Permanent Redirect"; + case 400: return "Bad Request"; + case 401: return "Unauthorized"; + case 402: return "Payment Required"; + case 403: return "Forbidden"; + case 404: return "Not Found"; + case 405: return "Method Not Allowed"; + case 406: return "Not Acceptable"; + case 407: return "Proxy Authentication Required"; + case 408: return "Request Timeout"; + case 409: return "Conflict"; + case 410: return "Gone"; + case 411: return "Length Required"; + case 412: return "Precondition Failed"; + case 413: return "Payload Too Large"; + case 414: return "Request-URI Too Long"; + case 415: return "Unsupported Media Type"; + case 416: return "Requested Range Not Satisfiable"; + case 417: return "Expectation Failed"; + case 418: return "I'm a teapot"; + case 421: return "Misdirected Request"; + case 422: return "Unprocessable Entity"; + case 423: return "Locked"; + case 424: return "Failed Dependency"; + case 426: return "Upgrade Required"; + case 428: return "Precondition Required"; + case 429: return "Too Many Requests"; + case 431: return "Request Header Fields Too Large"; + case 444: return "Connection Closed Without Response"; + case 451: return "Unavailable For Legal Reasons"; + case 499: return "Client Closed Request"; + case 500: return "Internal Server Error"; + case 501: return "Not Implemented"; + case 502: return "Bad Gateway"; + case 503: return "Service Unavailable"; + case 504: return "Gateway Timeout"; + case 505: return "HTTP Version Not Supported"; + case 506: return "Variant Also Negotiates"; + case 507: return "Insufficient Storage"; + case 508: return "Loop Detected"; + case 510: return "Not Extended"; + case 511: return "Network Authentication Required"; + case 599: return "Network Connect Timeout Error"; + default: return ""; + } } +// clang-format on -static bool packed_remove(const char *path) { - (void) path; - return false; +void mg_http_reply(struct mg_connection *c, int code, const char *headers, + const char *fmt, ...) { + va_list ap; + size_t len; + mg_printf(c, "HTTP/1.1 %d %s\r\n%sContent-Length: \r\n\r\n", code, + mg_http_status_code_str(code), headers == NULL ? "" : headers); + len = c->send.len; + va_start(ap, fmt); + mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, &ap); + va_end(ap); + if (c->send.len > 16) { + size_t n = mg_snprintf((char *) &c->send.buf[len - 15], 11, "%-10lu", + (unsigned long) (c->send.len - len)); + c->send.buf[len - 15 + n] = ' '; // Change ending 0 to space + } + c->is_resp = 0; } -static bool packed_mkdir(const char *path) { - (void) path; - return false; +static void http_cb(struct mg_connection *, int, void *); +static void restore_http_cb(struct mg_connection *c) { + mg_fs_close((struct mg_fd *) c->pfn_data); + c->pfn_data = NULL; + c->pfn = http_cb; + c->is_resp = 0; } -struct mg_fs mg_fs_packed = { - packed_stat, packed_list, packed_open, packed_close, packed_read, - packed_write, packed_seek, packed_rename, packed_remove, packed_mkdir}; +char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime); +char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime) { + mg_snprintf(buf, len, "\"%lld.%lld\"", (int64_t) mtime, (int64_t) size); + return buf; +} -#ifdef MG_ENABLE_LINES -#line 1 "src/fs_posix.c" -#endif +static void static_cb(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_WRITE || ev == MG_EV_POLL) { + struct mg_fd *fd = (struct mg_fd *) c->pfn_data; + // Read to send IO buffer directly, avoid extra on-stack buffer + size_t n, max = MG_IO_SIZE, space; + size_t *cl = (size_t *) &c->data[(sizeof(c->data) - sizeof(size_t)) / + sizeof(size_t) * sizeof(size_t)]; + if (c->send.size < max) mg_iobuf_resize(&c->send, max); + if (c->send.len >= c->send.size) return; // Rate limit + if ((space = c->send.size - c->send.len) > *cl) space = *cl; + n = fd->fs->rd(fd->fd, c->send.buf + c->send.len, space); + c->send.len += n; + *cl -= n; + if (n == 0) restore_http_cb(c); + } else if (ev == MG_EV_CLOSE) { + restore_http_cb(c); + } + (void) ev_data; +} +// Known mime types. Keep it outside guess_content_type() function, since +// some environments don't like it defined there. +// clang-format off +#define MG_C_STR(a) { (char *) (a), sizeof(a) - 1 } +static struct mg_str s_known_types[] = { + MG_C_STR("html"), MG_C_STR("text/html; charset=utf-8"), + MG_C_STR("htm"), MG_C_STR("text/html; charset=utf-8"), + MG_C_STR("css"), MG_C_STR("text/css; charset=utf-8"), + MG_C_STR("js"), MG_C_STR("text/javascript; charset=utf-8"), + MG_C_STR("gif"), MG_C_STR("image/gif"), + MG_C_STR("png"), MG_C_STR("image/png"), + MG_C_STR("jpg"), MG_C_STR("image/jpeg"), + MG_C_STR("jpeg"), MG_C_STR("image/jpeg"), + MG_C_STR("woff"), MG_C_STR("font/woff"), + MG_C_STR("ttf"), MG_C_STR("font/ttf"), + MG_C_STR("svg"), MG_C_STR("image/svg+xml"), + MG_C_STR("txt"), MG_C_STR("text/plain; charset=utf-8"), + MG_C_STR("avi"), MG_C_STR("video/x-msvideo"), + MG_C_STR("csv"), MG_C_STR("text/csv"), + MG_C_STR("doc"), MG_C_STR("application/msword"), + MG_C_STR("exe"), MG_C_STR("application/octet-stream"), + MG_C_STR("gz"), MG_C_STR("application/gzip"), + MG_C_STR("ico"), MG_C_STR("image/x-icon"), + MG_C_STR("json"), MG_C_STR("application/json"), + MG_C_STR("mov"), MG_C_STR("video/quicktime"), + MG_C_STR("mp3"), MG_C_STR("audio/mpeg"), + MG_C_STR("mp4"), MG_C_STR("video/mp4"), + MG_C_STR("mpeg"), MG_C_STR("video/mpeg"), + MG_C_STR("pdf"), MG_C_STR("application/pdf"), + MG_C_STR("shtml"), MG_C_STR("text/html; charset=utf-8"), + MG_C_STR("tgz"), MG_C_STR("application/tar-gz"), + MG_C_STR("wav"), MG_C_STR("audio/wav"), + MG_C_STR("webp"), MG_C_STR("image/webp"), + MG_C_STR("zip"), MG_C_STR("application/zip"), + MG_C_STR("3gp"), MG_C_STR("video/3gpp"), + {0, 0}, +}; +// clang-format on -#if MG_ENABLE_POSIX_FS +static struct mg_str guess_content_type(struct mg_str path, const char *extra) { + struct mg_str entry, k, v, s = mg_str(extra), asterisk = mg_str_n("*", 1); + size_t i = 0; -#ifndef MG_STAT_STRUCT -#define MG_STAT_STRUCT stat -#endif + // Shrink path to its extension only + while (i < path.len && path.buf[path.len - i - 1] != '.') i++; + path.buf += path.len - i; + path.len = i; -#ifndef MG_STAT_FUNC -#define MG_STAT_FUNC stat -#endif + // Process user-provided mime type overrides, if any + while (mg_span(s, &entry, &s, ',')) { + if (mg_span(entry, &k, &v, '=') && + (mg_strcmp(asterisk, k) == 0 || mg_strcmp(path, k) == 0)) + return v; + } -static int p_stat(const char *path, size_t *size, time_t *mtime) { -#if !defined(S_ISDIR) - MG_ERROR(("stat() API is not supported. %p %p %p", path, size, mtime)); - return 0; -#else -#if MG_ARCH == MG_ARCH_WIN32 - struct _stati64 st; - wchar_t tmp[MG_PATH_MAX]; - MultiByteToWideChar(CP_UTF8, 0, path, -1, tmp, sizeof(tmp) / sizeof(tmp[0])); - if (_wstati64(tmp, &st) != 0) return 0; - // If path is a symlink, windows reports 0 in st.st_size. - // Get a real file size by opening it and jumping to the end - if (st.st_size == 0 && (st.st_mode & _S_IFREG)) { - FILE *fp = _wfopen(tmp, L"rb"); - if (fp != NULL) { - fseek(fp, 0, SEEK_END); - if (ftell(fp) > 0) st.st_size = ftell(fp); // Use _ftelli64 on win10+ - fclose(fp); - } + // Process built-in mime types + for (i = 0; s_known_types[i].buf != NULL; i += 2) { + if (mg_strcmp(path, s_known_types[i]) == 0) return s_known_types[i + 1]; } -#else - struct MG_STAT_STRUCT st; - if (MG_STAT_FUNC(path, &st) != 0) return 0; -#endif - if (size) *size = (size_t) st.st_size; - if (mtime) *mtime = st.st_mtime; - return MG_FS_READ | MG_FS_WRITE | (S_ISDIR(st.st_mode) ? MG_FS_DIR : 0); -#endif + + return mg_str("text/plain; charset=utf-8"); } -#if MG_ARCH == MG_ARCH_WIN32 -struct dirent { - char d_name[MAX_PATH]; -}; - -typedef struct win32_dir { - HANDLE handle; - WIN32_FIND_DATAW info; - struct dirent result; -} DIR; - -#if 0 -int gettimeofday(struct timeval *tv, void *tz) { - FILETIME ft; - unsigned __int64 tmpres = 0; - - if (tv != NULL) { - GetSystemTimeAsFileTime(&ft); - tmpres |= ft.dwHighDateTime; - tmpres <<= 32; - tmpres |= ft.dwLowDateTime; - tmpres /= 10; // convert into microseconds - tmpres -= (int64_t) 11644473600000000; - tv->tv_sec = (long) (tmpres / 1000000UL); - tv->tv_usec = (long) (tmpres % 1000000UL); - } - (void) tz; - return 0; -} -#endif - -static int to_wchar(const char *path, wchar_t *wbuf, size_t wbuf_len) { - int ret; - char buf[MAX_PATH * 2], buf2[MAX_PATH * 2], *p; - strncpy(buf, path, sizeof(buf)); - buf[sizeof(buf) - 1] = '\0'; - // Trim trailing slashes. Leave backslash for paths like "X:\" - p = buf + strlen(buf) - 1; - while (p > buf && p[-1] != ':' && (p[0] == '\\' || p[0] == '/')) *p-- = '\0'; - memset(wbuf, 0, wbuf_len * sizeof(wchar_t)); - ret = MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len); - // Convert back to Unicode. If doubly-converted string does not match the - // original, something is fishy, reject. - WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2), - NULL, NULL); - if (strcmp(buf, buf2) != 0) { - wbuf[0] = L'\0'; - ret = 0; +static int getrange(struct mg_str *s, size_t *a, size_t *b) { + size_t i, numparsed = 0; + for (i = 0; i + 6 < s->len; i++) { + struct mg_str k, v = mg_str_n(s->buf + i + 6, s->len - i - 6); + if (memcmp(&s->buf[i], "bytes=", 6) != 0) continue; + if (mg_span(v, &k, &v, '-')) { + if (mg_to_size_t(k, a)) numparsed++; + if (v.len > 0 && mg_to_size_t(v, b)) numparsed++; + } else { + if (mg_to_size_t(v, a)) numparsed++; + } + break; } - return ret; + return (int) numparsed; } -DIR *opendir(const char *name) { - DIR *d = NULL; - wchar_t wpath[MAX_PATH]; - DWORD attrs; +void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm, + const char *path, + const struct mg_http_serve_opts *opts) { + char etag[64], tmp[MG_PATH_MAX]; + struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs; + struct mg_fd *fd = NULL; + size_t size = 0; + time_t mtime = 0; + struct mg_str *inm = NULL; + struct mg_str mime = guess_content_type(mg_str(path), opts->mime_types); + bool gzip = false; - if (name == NULL) { - SetLastError(ERROR_BAD_ARGUMENTS); - } else if ((d = (DIR *) calloc(1, sizeof(*d))) == NULL) { - SetLastError(ERROR_NOT_ENOUGH_MEMORY); - } else { - to_wchar(name, wpath, sizeof(wpath) / sizeof(wpath[0])); - attrs = GetFileAttributesW(wpath); - if (attrs != 0Xffffffff && (attrs & FILE_ATTRIBUTE_DIRECTORY)) { - (void) wcscat(wpath, L"\\*"); - d->handle = FindFirstFileW(wpath, &d->info); - d->result.d_name[0] = '\0'; - } else { - free(d); - d = NULL; + if (path != NULL) { + // If a browser sends us "Accept-Encoding: gzip", try to open .gz first + struct mg_str *ae = mg_http_get_header(hm, "Accept-Encoding"); + if (ae != NULL) { + char *ae_ = mg_mprintf("%.*s", ae->len, ae->buf); + if (ae_ != NULL && strstr(ae_, "gzip") != NULL) { + mg_snprintf(tmp, sizeof(tmp), "%s.gz", path); + fd = mg_fs_open(fs, tmp, MG_FS_READ); + if (fd != NULL) gzip = true, path = tmp; + } + free(ae_); } + // No luck opening .gz? Open what we've told to open + if (fd == NULL) fd = mg_fs_open(fs, path, MG_FS_READ); } - return d; -} -int closedir(DIR *d) { - int result = 0; - if (d != NULL) { - if (d->handle != INVALID_HANDLE_VALUE) - result = FindClose(d->handle) ? 0 : -1; - free(d); - } else { - result = -1; - SetLastError(ERROR_BAD_ARGUMENTS); + // Failed to open, and page404 is configured? Open it, then + if (fd == NULL && opts->page404 != NULL) { + fd = mg_fs_open(fs, opts->page404, MG_FS_READ); + path = opts->page404; + mime = guess_content_type(mg_str(path), opts->mime_types); } - return result; -} -struct dirent *readdir(DIR *d) { - struct dirent *result = NULL; - if (d != NULL) { - memset(&d->result, 0, sizeof(d->result)); - if (d->handle != INVALID_HANDLE_VALUE) { - result = &d->result; - WideCharToMultiByte(CP_UTF8, 0, d->info.cFileName, -1, result->d_name, - sizeof(result->d_name), NULL, NULL); - if (!FindNextFileW(d->handle, &d->info)) { - FindClose(d->handle); - d->handle = INVALID_HANDLE_VALUE; + if (fd == NULL || fs->st(path, &size, &mtime) == 0) { + mg_http_reply(c, 404, opts->extra_headers, "Not found\n"); + mg_fs_close(fd); + // NOTE: mg_http_etag() call should go first! + } else if (mg_http_etag(etag, sizeof(etag), size, mtime) != NULL && + (inm = mg_http_get_header(hm, "If-None-Match")) != NULL && + mg_strcasecmp(*inm, mg_str(etag)) == 0) { + mg_fs_close(fd); + mg_http_reply(c, 304, opts->extra_headers, ""); + } else { + int n, status = 200; + char range[100]; + size_t r1 = 0, r2 = 0, cl = size; + + // Handle Range header + struct mg_str *rh = mg_http_get_header(hm, "Range"); + range[0] = '\0'; + if (rh != NULL && (n = getrange(rh, &r1, &r2)) > 0) { + // If range is specified like "400-", set second limit to content len + if (n == 1) r2 = cl - 1; + if (r1 > r2 || r2 >= cl) { + status = 416; + cl = 0; + mg_snprintf(range, sizeof(range), "Content-Range: bytes */%lld\r\n", + (int64_t) size); + } else { + status = 206; + cl = r2 - r1 + 1; + mg_snprintf(range, sizeof(range), + "Content-Range: bytes %llu-%llu/%llu\r\n", (uint64_t) r1, + (uint64_t) (r1 + cl - 1), (uint64_t) size); + fs->sk(fd->fd, r1); } + } + mg_printf(c, + "HTTP/1.1 %d %s\r\n" + "Content-Type: %.*s\r\n" + "Etag: %s\r\n" + "Content-Length: %llu\r\n" + "%s%s%s\r\n", + status, mg_http_status_code_str(status), (int) mime.len, mime.buf, + etag, (uint64_t) cl, gzip ? "Content-Encoding: gzip\r\n" : "", + range, opts->extra_headers ? opts->extra_headers : ""); + if (mg_strcasecmp(hm->method, mg_str("HEAD")) == 0) { + c->is_resp = 0; + mg_fs_close(fd); } else { - SetLastError(ERROR_FILE_NOT_FOUND); + // Track to-be-sent content length at the end of c->data, aligned + size_t *clp = (size_t *) &c->data[(sizeof(c->data) - sizeof(size_t)) / + sizeof(size_t) * sizeof(size_t)]; + c->pfn = static_cb; + c->pfn_data = fd; + *clp = cl; } - } else { - SetLastError(ERROR_BAD_ARGUMENTS); } - return result; } -#endif -static void p_list(const char *dir, void (*fn)(const char *, void *), - void *userdata) { +struct printdirentrydata { + struct mg_connection *c; + struct mg_http_message *hm; + const struct mg_http_serve_opts *opts; + const char *dir; +}; + #if MG_ENABLE_DIRLIST - struct dirent *dp; - DIR *dirp; - if ((dirp = (opendir(dir))) == NULL) return; - while ((dp = readdir(dirp)) != NULL) { - if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue; - fn(dp->d_name, userdata); - } - closedir(dirp); -#else - (void) dir, (void) fn, (void) userdata; -#endif -} +static void printdirentry(const char *name, void *userdata) { + struct printdirentrydata *d = (struct printdirentrydata *) userdata; + struct mg_fs *fs = d->opts->fs == NULL ? &mg_fs_posix : d->opts->fs; + size_t size = 0; + time_t t = 0; + char path[MG_PATH_MAX], sz[40], mod[40]; + int flags, n = 0; -static void *p_open(const char *path, int flags) { -#if MG_ARCH == MG_ARCH_WIN32 - const char *mode = flags == MG_FS_READ ? "rb" : "a+b"; - wchar_t b1[MG_PATH_MAX], b2[10]; - MultiByteToWideChar(CP_UTF8, 0, path, -1, b1, sizeof(b1) / sizeof(b1[0])); - MultiByteToWideChar(CP_UTF8, 0, mode, -1, b2, sizeof(b2) / sizeof(b2[0])); - return (void *) _wfopen(b1, b2); + // MG_DEBUG(("[%s] [%s]", d->dir, name)); + if (mg_snprintf(path, sizeof(path), "%s%c%s", d->dir, '/', name) > + sizeof(path)) { + MG_ERROR(("%s truncated", name)); + } else if ((flags = fs->st(path, &size, &t)) == 0) { + MG_ERROR(("%lu stat(%s): %d", d->c->id, path, errno)); + } else { + const char *slash = flags & MG_FS_DIR ? "/" : ""; + if (flags & MG_FS_DIR) { + mg_snprintf(sz, sizeof(sz), "%s", "[DIR]"); + } else { + mg_snprintf(sz, sizeof(sz), "%lld", (uint64_t) size); + } +#if defined(MG_HTTP_DIRLIST_TIME_FMT) + { + char time_str[40]; + struct tm *time_info = localtime(&t); + strftime(time_str, sizeof time_str, "%Y/%m/%d %H:%M:%S", time_info); + mg_snprintf(mod, sizeof(mod), "%s", time_str); + } #else - const char *mode = flags == MG_FS_READ ? "rbe" : "a+be"; // e for CLOEXEC - return (void *) fopen(path, mode); + mg_snprintf(mod, sizeof(mod), "%lu", (unsigned long) t); #endif + n = (int) mg_url_encode(name, strlen(name), path, sizeof(path)); + mg_printf(d->c, + " %s%s" + "%s%s\n", + n, path, slash, name, slash, (unsigned long) t, mod, + flags & MG_FS_DIR ? (int64_t) -1 : (int64_t) size, sz); + } } -static void p_close(void *fp) { - fclose((FILE *) fp); -} - -static size_t p_read(void *fp, void *buf, size_t len) { - return fread(buf, 1, len, (FILE *) fp); +static void listdir(struct mg_connection *c, struct mg_http_message *hm, + const struct mg_http_serve_opts *opts, char *dir) { + const char *sort_js_code = + ""; + struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs; + struct printdirentrydata d = {c, hm, opts, dir}; + char tmp[10], buf[MG_PATH_MAX]; + size_t off, n; + int len = mg_url_decode(hm->uri.buf, hm->uri.len, buf, sizeof(buf), 0); + struct mg_str uri = len > 0 ? mg_str_n(buf, (size_t) len) : hm->uri; + + mg_printf(c, + "HTTP/1.1 200 OK\r\n" + "Content-Type: text/html; charset=utf-8\r\n" + "%s" + "Content-Length: \r\n\r\n", + opts->extra_headers == NULL ? "" : opts->extra_headers); + off = c->send.len; // Start of body + mg_printf(c, + "Index of %.*s%s%s" + "" + "

Index of %.*s

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

..[DIR]

Mongoose v.%s
\n", + MG_VERSION); + n = mg_snprintf(tmp, sizeof(tmp), "%lu", (unsigned long) (c->send.len - off)); + if (n > sizeof(tmp)) n = 0; + memcpy(c->send.buf + off - 12, tmp, n); // Set content length + c->is_resp = 0; // Mark response end } +#endif -static size_t p_write(void *fp, const void *buf, size_t len) { - return fwrite(buf, 1, len, (FILE *) fp); +// Resolve requested file into `path` and return its fs->st() result +static int uri_to_path2(struct mg_connection *c, struct mg_http_message *hm, + struct mg_fs *fs, struct mg_str url, struct mg_str dir, + char *path, size_t path_size) { + int flags, tmp; + // Append URI to the root_dir, and sanitize it + size_t n = mg_snprintf(path, path_size, "%.*s", (int) dir.len, dir.buf); + if (n + 2 >= path_size) { + mg_http_reply(c, 400, "", "Exceeded path size"); + return -1; + } + path[path_size - 1] = '\0'; + // Terminate root dir with slash + if (n > 0 && path[n - 1] != '/') path[n++] = '/', path[n] = '\0'; + if (url.len < hm->uri.len) { + mg_url_decode(hm->uri.buf + url.len, hm->uri.len - url.len, path + n, + path_size - n, 0); + } + path[path_size - 1] = '\0'; // Double-check + if (!mg_path_is_sane(mg_str_n(path, path_size))) { + mg_http_reply(c, 400, "", "Invalid path"); + return -1; + } + n = strlen(path); + while (n > 1 && path[n - 1] == '/') path[--n] = 0; // Trim trailing slashes + flags = mg_strcmp(hm->uri, mg_str("/")) == 0 ? MG_FS_DIR + : fs->st(path, NULL, NULL); + MG_VERBOSE(("%lu %.*s -> %s %d", c->id, (int) hm->uri.len, hm->uri.buf, path, + flags)); + if (flags == 0) { + // Do nothing - let's caller decide + } else if ((flags & MG_FS_DIR) && hm->uri.len > 0 && + hm->uri.buf[hm->uri.len - 1] != '/') { + mg_printf(c, + "HTTP/1.1 301 Moved\r\n" + "Location: %.*s/\r\n" + "Content-Length: 0\r\n" + "\r\n", + (int) hm->uri.len, hm->uri.buf); + c->is_resp = 0; + flags = -1; + } else if (flags & MG_FS_DIR) { + if (((mg_snprintf(path + n, path_size - n, "/" MG_HTTP_INDEX) > 0 && + (tmp = fs->st(path, NULL, NULL)) != 0) || + (mg_snprintf(path + n, path_size - n, "/index.shtml") > 0 && + (tmp = fs->st(path, NULL, NULL)) != 0))) { + flags = tmp; + } else if ((mg_snprintf(path + n, path_size - n, "/" MG_HTTP_INDEX ".gz") > + 0 && + (tmp = fs->st(path, NULL, NULL)) != + 0)) { // check for gzipped index + flags = tmp; + path[n + 1 + strlen(MG_HTTP_INDEX)] = + '\0'; // Remove appended .gz in index file name + } else { + path[n] = '\0'; // Remove appended index file name + } + } + return flags; } -static size_t p_seek(void *fp, size_t offset) { -#if (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64) || \ - (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L) || \ - (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 600) - if (fseeko((FILE *) fp, (off_t) offset, SEEK_SET) != 0) (void) 0; +static int uri_to_path(struct mg_connection *c, struct mg_http_message *hm, + const struct mg_http_serve_opts *opts, char *path, + size_t path_size) { + struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs; + struct mg_str k, v, part, s = mg_str(opts->root_dir), u = {NULL, 0}, p = u; + while (mg_span(s, &part, &s, ',')) { + if (!mg_span(part, &k, &v, '=')) k = part, v = mg_str_n(NULL, 0); + if (v.len == 0) v = k, k = mg_str("/"), u = k, p = v; + if (hm->uri.len < k.len) continue; + if (mg_strcmp(k, mg_str_n(hm->uri.buf, k.len)) != 0) continue; + u = k, p = v; + } + return uri_to_path2(c, hm, fs, u, p, path, path_size); +} + +void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm, + const struct mg_http_serve_opts *opts) { + char path[MG_PATH_MAX]; + const char *sp = opts->ssi_pattern; + int flags = uri_to_path(c, hm, opts, path, sizeof(path)); + if (flags < 0) { + // Do nothing: the response has already been sent by uri_to_path() + } else if (flags & MG_FS_DIR) { +#if MG_ENABLE_DIRLIST + listdir(c, hm, opts, path); #else - if (fseek((FILE *) fp, (long) offset, SEEK_SET) != 0) (void) 0; + mg_http_reply(c, 403, "", "Forbidden\n"); #endif - return (size_t) ftell((FILE *) fp); + } else if (flags && sp != NULL && mg_match(mg_str(path), mg_str(sp), NULL)) { + mg_http_serve_ssi(c, opts->root_dir, path); + } else { + mg_http_serve_file(c, hm, path, opts); + } } -static bool p_rename(const char *from, const char *to) { - return rename(from, to) == 0; +static bool mg_is_url_safe(int c) { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || c == '.' || c == '_' || c == '-' || c == '~'; } -static bool p_remove(const char *path) { - return remove(path) == 0; +size_t mg_url_encode(const char *s, size_t sl, char *buf, size_t len) { + size_t i, n = 0; + for (i = 0; i < sl; i++) { + int c = *(unsigned char *) &s[i]; + if (n + 4 >= len) return 0; + if (mg_is_url_safe(c)) { + buf[n++] = s[i]; + } else { + mg_snprintf(&buf[n], 4, "%%%M", mg_print_hex, 1, &s[i]); + n += 3; + } + } + if (len > 0 && n < len - 1) buf[n] = '\0'; // Null-terminate the destination + if (len > 0) buf[len - 1] = '\0'; // Always. + return n; } -static bool p_mkdir(const char *path) { - return mkdir(path, 0775) == 0; +void mg_http_creds(struct mg_http_message *hm, char *user, size_t userlen, + char *pass, size_t passlen) { + struct mg_str *v = mg_http_get_header(hm, "Authorization"); + user[0] = pass[0] = '\0'; + if (v != NULL && v->len > 6 && memcmp(v->buf, "Basic ", 6) == 0) { + char buf[256]; + size_t n = mg_base64_decode(v->buf + 6, v->len - 6, buf, sizeof(buf)); + const char *p = (const char *) memchr(buf, ':', n > 0 ? n : 0); + if (p != NULL) { + mg_snprintf(user, userlen, "%.*s", p - buf, buf); + mg_snprintf(pass, passlen, "%.*s", n - (size_t) (p - buf) - 1, p + 1); + } + } else if (v != NULL && v->len > 7 && memcmp(v->buf, "Bearer ", 7) == 0) { + mg_snprintf(pass, passlen, "%.*s", (int) v->len - 7, v->buf + 7); + } else if ((v = mg_http_get_header(hm, "Cookie")) != NULL) { + struct mg_str t = mg_http_get_header_var(*v, mg_str_n("access_token", 12)); + if (t.len > 0) mg_snprintf(pass, passlen, "%.*s", (int) t.len, t.buf); + } else { + mg_http_get_var(&hm->query, "access_token", pass, passlen); + } } -#else - -static int p_stat(const char *path, size_t *size, time_t *mtime) { - (void) path, (void) size, (void) mtime; - return 0; -} -static void p_list(const char *path, void (*fn)(const char *, void *), - void *userdata) { - (void) path, (void) fn, (void) userdata; -} -static void *p_open(const char *path, int flags) { - (void) path, (void) flags; - return NULL; -} -static void p_close(void *fp) { - (void) fp; -} -static size_t p_read(void *fd, void *buf, size_t len) { - (void) fd, (void) buf, (void) len; - return 0; -} -static size_t p_write(void *fd, const void *buf, size_t len) { - (void) fd, (void) buf, (void) len; - return 0; -} -static size_t p_seek(void *fd, size_t offset) { - (void) fd, (void) offset; - return (size_t) ~0; -} -static bool p_rename(const char *from, const char *to) { - (void) from, (void) to; - return false; -} -static bool p_remove(const char *path) { - (void) path; - return false; -} -static bool p_mkdir(const char *path) { - (void) path; - return false; +static struct mg_str stripquotes(struct mg_str s) { + return s.len > 1 && s.buf[0] == '"' && s.buf[s.len - 1] == '"' + ? mg_str_n(s.buf + 1, s.len - 2) + : s; } -#endif -struct mg_fs mg_fs_posix = {p_stat, p_list, p_open, p_close, p_read, - p_write, p_seek, p_rename, p_remove, p_mkdir}; - -#ifdef MG_ENABLE_LINES -#line 1 "src/http.c" -#endif - - - - - - - - - - - - - -static int mg_ncasecmp(const char *s1, const char *s2, size_t len) { - int diff = 0; - if (len > 0) do { - int c = *s1++, d = *s2++; - if (c >= 'A' && c <= 'Z') c += 'a' - 'A'; - if (d >= 'A' && d <= 'Z') d += 'a' - 'A'; - diff = c - d; - } while (diff == 0 && s1[-1] != '\0' && --len > 0); - return diff; -} - -bool mg_to_size_t(struct mg_str str, size_t *val); -bool mg_to_size_t(struct mg_str str, size_t *val) { - size_t i = 0, max = (size_t) -1, max2 = max / 10, result = 0, ndigits = 0; - while (i < str.len && (str.buf[i] == ' ' || str.buf[i] == '\t')) i++; - if (i < str.len && str.buf[i] == '-') return false; - while (i < str.len && str.buf[i] >= '0' && str.buf[i] <= '9') { - size_t digit = (size_t) (str.buf[i] - '0'); - if (result > max2) return false; // Overflow - result *= 10; - if (result > max - digit) return false; // Overflow - result += digit; - i++, ndigits++; - } - while (i < str.len && (str.buf[i] == ' ' || str.buf[i] == '\t')) i++; - if (ndigits == 0) return false; // #2322: Content-Length = 1 * DIGIT - if (i != str.len) return false; // Ditto - *val = (size_t) result; - return true; -} - -// Chunk deletion marker is the MSB in the "processed" counter -#define MG_DMARK ((size_t) 1 << (sizeof(size_t) * 8 - 1)) - -// Multipart POST example: -// --xyz -// Content-Disposition: form-data; name="val" -// -// abcdef -// --xyz -// Content-Disposition: form-data; name="foo"; filename="a.txt" -// Content-Type: text/plain -// -// hello world -// -// --xyz-- -size_t mg_http_next_multipart(struct mg_str body, size_t ofs, - struct mg_http_part *part) { - struct mg_str cd = mg_str_n("Content-Disposition", 19); - const char *s = body.buf; - size_t b = ofs, h1, h2, b1, b2, max = body.len; - - // Init part params - if (part != NULL) part->name = part->filename = part->body = mg_str_n(0, 0); - - // Skip boundary - while (b + 2 < max && s[b] != '\r' && s[b + 1] != '\n') b++; - if (b <= ofs || b + 2 >= max) return 0; - // MG_INFO(("B: %zu %zu [%.*s]", ofs, b - ofs, (int) (b - ofs), s)); - - // Skip headers - h1 = h2 = b + 2; - for (;;) { - while (h2 + 2 < max && s[h2] != '\r' && s[h2 + 1] != '\n') h2++; - if (h2 == h1) break; - if (h2 + 2 >= max) return 0; - // MG_INFO(("Header: [%.*s]", (int) (h2 - h1), &s[h1])); - if (part != NULL && h1 + cd.len + 2 < h2 && s[h1 + cd.len] == ':' && - mg_ncasecmp(&s[h1], cd.buf, cd.len) == 0) { - struct mg_str v = mg_str_n(&s[h1 + cd.len + 2], h2 - (h1 + cd.len + 2)); - part->name = mg_http_get_header_var(v, mg_str_n("name", 4)); - part->filename = mg_http_get_header_var(v, mg_str_n("filename", 8)); +struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v) { + size_t i; + for (i = 0; v.len > 0 && i + v.len + 2 < s.len; i++) { + if (s.buf[i + v.len] == '=' && memcmp(&s.buf[i], v.buf, v.len) == 0) { + const char *p = &s.buf[i + v.len + 1], *b = p, *x = &s.buf[s.len]; + int q = p < x && *p == '"' ? 1 : 0; + while (p < x && + (q ? p == b || *p != '"' : *p != ';' && *p != ' ' && *p != ',')) + p++; + // MG_INFO(("[%.*s] [%.*s] [%.*s]", (int) s.len, s.buf, (int) v.len, + // v.buf, (int) (p - b), b)); + return stripquotes(mg_str_n(b, (size_t) (p - b + q))); } - h1 = h2 = h2 + 2; } - b1 = b2 = h2 + 2; - while (b2 + 2 + (b - ofs) + 2 < max && !(s[b2] == '\r' && s[b2 + 1] == '\n' && - memcmp(&s[b2 + 2], s, b - ofs) == 0)) - b2++; - - if (b2 + 2 >= max) return 0; - if (part != NULL) part->body = mg_str_n(&s[b1], b2 - b1); - // MG_INFO(("Body: [%.*s]", (int) (b2 - b1), &s[b1])); - return b2 + 2; + return mg_str_n(NULL, 0); } -void mg_http_bauth(struct mg_connection *c, const char *user, - const char *pass) { - struct mg_str u = mg_str(user), p = mg_str(pass); - size_t need = c->send.len + 36 + (u.len + p.len) * 2; - if (c->send.size < need) mg_iobuf_resize(&c->send, need); - if (c->send.size >= need) { - size_t i, n = 0; - char *buf = (char *) &c->send.buf[c->send.len]; - memcpy(buf, "Authorization: Basic ", 21); // DON'T use mg_send! - for (i = 0; i < u.len; i++) { - n = mg_base64_update(((unsigned char *) u.buf)[i], buf + 21, n); - } - if (p.len > 0) { - n = mg_base64_update(':', buf + 21, n); - for (i = 0; i < p.len; i++) { - n = mg_base64_update(((unsigned char *) p.buf)[i], buf + 21, n); - } - } - n = mg_base64_final(buf + 21, n); - c->send.len += 21 + (size_t) n + 2; - memcpy(&c->send.buf[c->send.len - 2], "\r\n", 2); +long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm, + struct mg_fs *fs, const char *dir, size_t max_size) { + char buf[20] = "0", file[MG_PATH_MAX], path[MG_PATH_MAX]; + long res = 0, offset; + mg_http_get_var(&hm->query, "offset", buf, sizeof(buf)); + mg_http_get_var(&hm->query, "file", file, sizeof(file)); + offset = strtol(buf, NULL, 0); + mg_snprintf(path, sizeof(path), "%s%c%s", dir, MG_DIRSEP, file); + if (hm->body.len == 0) { + mg_http_reply(c, 200, "", "%ld", res); // Nothing to write + } else if (file[0] == '\0') { + mg_http_reply(c, 400, "", "file required"); + res = -1; + } else if (mg_path_is_sane(mg_str(file)) == false) { + mg_http_reply(c, 400, "", "%s: invalid file", file); + res = -2; + } else if (offset < 0) { + mg_http_reply(c, 400, "", "offset required"); + res = -3; + } else if ((size_t) offset + hm->body.len > max_size) { + mg_http_reply(c, 400, "", "%s: over max size of %lu", path, + (unsigned long) max_size); + res = -4; } else { - MG_ERROR(("%lu oom %d->%d ", c->id, (int) c->send.size, (int) need)); - } -} - -struct mg_str mg_http_var(struct mg_str buf, struct mg_str name) { - struct mg_str entry, k, v, result = mg_str_n(NULL, 0); - while (mg_span(buf, &entry, &buf, '&')) { - if (mg_span(entry, &k, &v, '=') && name.len == k.len && - mg_ncasecmp(name.buf, k.buf, k.len) == 0) { - result = v; - break; + struct mg_fd *fd; + size_t current_size = 0; + MG_DEBUG(("%s -> %lu bytes @ %ld", path, hm->body.len, offset)); + if (offset == 0) fs->rm(path); // If offset if 0, truncate file + fs->st(path, ¤t_size, NULL); + if (offset > 0 && current_size != (size_t) offset) { + mg_http_reply(c, 400, "", "%s: offset mismatch", path); + res = -5; + } else if ((fd = mg_fs_open(fs, path, MG_FS_WRITE)) == NULL) { + mg_http_reply(c, 400, "", "open(%s): %d", path, errno); + res = -6; + } else { + res = offset + (long) fs->wr(fd->fd, hm->body.buf, hm->body.len); + mg_fs_close(fd); + mg_http_reply(c, 200, "", "%ld", res); } } - return result; + return res; } -int mg_http_get_var(const struct mg_str *buf, const char *name, char *dst, - size_t dst_len) { - int len; - if (dst != NULL && dst_len > 0) { - dst[0] = '\0'; // If destination buffer is valid, always nul-terminate it - } - if (dst == NULL || dst_len == 0) { - len = -2; // Bad destination - } else if (buf->buf == NULL || name == NULL || buf->len == 0) { - len = -1; // Bad source - } else { - struct mg_str v = mg_http_var(*buf, mg_str(name)); - if (v.buf == NULL) { - len = -4; // Name does not exist - } else { - len = mg_url_decode(v.buf, v.len, dst, dst_len, 1); - if (len < 0) len = -3; // Failed to decode - } - } - return len; +int mg_http_status(const struct mg_http_message *hm) { + return atoi(hm->uri.buf); } -static bool isx(int c) { +static bool is_hex_digit(int c) { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); } -int mg_url_decode(const char *src, size_t src_len, char *dst, size_t dst_len, - int is_form_url_encoded) { - size_t i, j; - for (i = j = 0; i < src_len && j + 1 < dst_len; i++, j++) { - if (src[i] == '%') { - // Use `i + 2 < src_len`, not `i < src_len - 2`, note small src_len - if (i + 2 < src_len && isx(src[i + 1]) && isx(src[i + 2])) { - mg_str_to_num(mg_str_n(src + i + 1, 2), 16, &dst[j], sizeof(uint8_t)); - i += 2; - } else { - return -1; - } - } else if (is_form_url_encoded && src[i] == '+') { - dst[j] = ' '; - } else { - dst[j] = src[i]; - } - } - if (j < dst_len) dst[j] = '\0'; // Null-terminate the destination - return i >= src_len && j < dst_len ? (int) j : -1; -} - -static bool isok(uint8_t c) { - return c == '\n' || c == '\r' || c == '\t' || c >= ' '; +static int skip_chunk(const char *buf, int len, int *pl, int *dl) { + int i = 0, n = 0; + if (len < 3) return 0; + while (i < len && is_hex_digit(buf[i])) i++; + if (i == 0) return -1; // Error, no length specified + if (i > (int) sizeof(int) * 2) return -1; // Chunk length is too big + if (len < i + 1 || buf[i] != '\r' || buf[i + 1] != '\n') return -1; // Error + if (mg_str_to_num(mg_str_n(buf, (size_t) i), 16, &n, sizeof(int)) == false) + return -1; // Decode chunk length, overflow + if (n < 0) return -1; // Error. TODO(): some checks now redundant + if (n > len - i - 4) return 0; // Chunk not yet fully buffered + if (buf[i + n + 2] != '\r' || buf[i + n + 3] != '\n') return -1; // Error + *pl = i + 2, *dl = n; + return i + 2 + n + 2; } -int mg_http_get_request_len(const unsigned char *buf, size_t buf_len) { - size_t i; - for (i = 0; i < buf_len; i++) { - if (!isok(buf[i])) return -1; - if ((i > 0 && buf[i] == '\n' && buf[i - 1] == '\n') || - (i > 3 && buf[i] == '\n' && buf[i - 1] == '\r' && buf[i - 2] == '\n')) - return (int) i + 1; - } - return 0; -} -struct mg_str *mg_http_get_header(struct mg_http_message *h, const char *name) { - size_t i, n = strlen(name), max = sizeof(h->headers) / sizeof(h->headers[0]); - for (i = 0; i < max && h->headers[i].name.len > 0; i++) { - struct mg_str *k = &h->headers[i].name, *v = &h->headers[i].value; - if (n == k->len && mg_ncasecmp(k->buf, name, n) == 0) return v; +static void http_cb(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_READ || ev == MG_EV_CLOSE || + (ev == MG_EV_POLL && c->is_accepted && !c->is_draining && + c->recv.len > 0)) { // see #2796 + struct mg_http_message hm; + size_t ofs = 0; // Parsing offset + while (c->is_resp == 0 && ofs < c->recv.len) { + const char *buf = (char *) c->recv.buf + ofs; + int n = mg_http_parse(buf, c->recv.len - ofs, &hm); + struct mg_str *te; // Transfer - encoding header + bool is_chunked = false; + size_t old_len = c->recv.len; + if (n < 0) { + // We don't use mg_error() here, to avoid closing pipelined requests + // prematurely, see #2592 + MG_ERROR(("HTTP parse, %lu bytes", c->recv.len)); + c->is_draining = 1; + mg_hexdump(buf, c->recv.len - ofs > 16 ? 16 : c->recv.len - ofs); + c->recv.len = 0; + return; + } + if (n == 0) break; // Request is not buffered yet + mg_call(c, MG_EV_HTTP_HDRS, &hm); // Got all HTTP headers + if (c->recv.len != old_len) { + // User manipulated received data. Wash our hands + MG_DEBUG(("%lu detaching HTTP handler", c->id)); + c->pfn = NULL; + return; + } + if (ev == MG_EV_CLOSE) { // If client did not set Content-Length + hm.message.len = c->recv.len - ofs; // and closes now, deliver MSG + hm.body.len = hm.message.len - (size_t) (hm.body.buf - hm.message.buf); + } + if ((te = mg_http_get_header(&hm, "Transfer-Encoding")) != NULL) { + if (mg_strcasecmp(*te, mg_str("chunked")) == 0) { + is_chunked = true; + } else { + mg_error(c, "Invalid Transfer-Encoding"); // See #2460 + return; + } + } else if (mg_http_get_header(&hm, "Content-length") == NULL) { + // #2593: HTTP packets must contain either Transfer-Encoding or + // Content-length + bool is_response = mg_ncasecmp(hm.method.buf, "HTTP/", 5) == 0; + bool require_content_len = false; + if (!is_response && (mg_strcasecmp(hm.method, mg_str("POST")) == 0 || + mg_strcasecmp(hm.method, mg_str("PUT")) == 0)) { + // POST and PUT should include an entity body. Therefore, they should + // contain a Content-length header. Other requests can also contain a + // body, but their content has no defined semantics (RFC 7231) + require_content_len = true; + ofs += (size_t) n; // this request has been processed + } else if (is_response) { + // HTTP spec 7.2 Entity body: All other responses must include a body + // or Content-Length header field defined with a value of 0. + int status = mg_http_status(&hm); + require_content_len = status >= 200 && status != 204 && status != 304; + } + if (require_content_len) { + mg_http_reply(c, 411, "", ""); + MG_ERROR(("%s", "Content length missing from request")); + } + } + + if (is_chunked) { + // For chunked data, strip off prefixes and suffixes from chunks + // and relocate them right after the headers, then report a message + char *s = (char *) c->recv.buf + ofs + n; + int o = 0, pl, dl, cl, len = (int) (c->recv.len - ofs - (size_t) n); + + // Find zero-length chunk (the end of the body) + while ((cl = skip_chunk(s + o, len - o, &pl, &dl)) > 0 && dl) o += cl; + if (cl == 0) break; // No zero-len chunk, buffer more data + if (cl < 0) { + mg_error(c, "Invalid chunk"); + break; + } + + // Zero chunk found. Second pass: strip + relocate + o = 0, hm.body.len = 0, hm.message.len = (size_t) n; + while ((cl = skip_chunk(s + o, len - o, &pl, &dl)) > 0) { + memmove(s + hm.body.len, s + o + pl, (size_t) dl); + o += cl, hm.body.len += (size_t) dl, hm.message.len += (size_t) dl; + if (dl == 0) break; + } + ofs += (size_t) (n + o); + } else { // Normal, non-chunked data + size_t len = c->recv.len - ofs - (size_t) n; + if (hm.body.len > len) break; // Buffer more data + ofs += (size_t) n + hm.body.len; + } + + if (c->is_accepted) c->is_resp = 1; // Start generating response + mg_call(c, MG_EV_HTTP_MSG, &hm); // User handler can clear is_resp + if (c->is_accepted && !c->is_resp) { + struct mg_str *cc = mg_http_get_header(&hm, "Connection"); + if (cc != NULL && mg_strcasecmp(*cc, mg_str("close")) == 0) { + c->is_draining = 1; // honor "Connection: close" + break; + } + } + } + if (ofs > 0) mg_iobuf_del(&c->recv, 0, ofs); // Delete processed data } - return NULL; + (void) ev_data; } -// Is it a valid utf-8 continuation byte -static bool vcb(uint8_t c) { - return (c & 0xc0) == 0x80; +static void mg_hfn(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_HTTP_MSG) { + struct mg_http_message *hm = (struct mg_http_message *) ev_data; + if (mg_match(hm->uri, mg_str("/quit"), NULL)) { + mg_http_reply(c, 200, "", "ok\n"); + c->is_draining = 1; + c->data[0] = 'X'; + } else if (mg_match(hm->uri, mg_str("/debug"), NULL)) { + int level = (int) mg_json_get_long(hm->body, "$.level", MG_LL_DEBUG); + mg_log_set(level); + mg_http_reply(c, 200, "", "Debug level set to %d\n", level); + } else { + mg_http_reply(c, 200, "", "hi\n"); + } + } else if (ev == MG_EV_CLOSE) { + if (c->data[0] == 'X') *(bool *) c->fn_data = true; + } } -// Get character length (valid utf-8). Used to parse method, URI, headers -static size_t clen(const char *s, const char *end) { - const unsigned char *u = (unsigned char *) s, c = *u; - long n = (long) (end - s); - if (c > ' ' && c < '~') return 1; // Usual ascii printed char - if ((c & 0xe0) == 0xc0 && n > 1 && vcb(u[1])) return 2; // 2-byte UTF8 - if ((c & 0xf0) == 0xe0 && n > 2 && vcb(u[1]) && vcb(u[2])) return 3; - if ((c & 0xf8) == 0xf0 && n > 3 && vcb(u[1]) && vcb(u[2]) && vcb(u[3])) - return 4; - return 0; +void mg_hello(const char *url) { + struct mg_mgr mgr; + bool done = false; + mg_mgr_init(&mgr); + if (mg_http_listen(&mgr, url, mg_hfn, &done) == NULL) done = true; + while (done == false) mg_mgr_poll(&mgr, 100); + mg_mgr_free(&mgr); } -// Skip until the newline. Return advanced `s`, or NULL on error -static const char *skiptorn(const char *s, const char *end, struct mg_str *v) { - v->buf = (char *) s; - while (s < end && s[0] != '\n' && s[0] != '\r') s++, v->len++; // To newline - if (s >= end || (s[0] == '\r' && s[1] != '\n')) return NULL; // Stray \r - if (s < end && s[0] == '\r') s++; // Skip \r - if (s >= end || *s++ != '\n') return NULL; // Skip \n - return s; +struct mg_connection *mg_http_connect(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data) { + struct mg_connection *c = mg_connect(mgr, url, fn, fn_data); + if (c != NULL) c->pfn = http_cb; + return c; } -static bool mg_http_parse_headers(const char *s, const char *end, - struct mg_http_header *h, size_t max_hdrs) { - size_t i, n; - for (i = 0; i < max_hdrs; i++) { - struct mg_str k = {NULL, 0}, v = {NULL, 0}; - if (s >= end) return false; - if (s[0] == '\n' || (s[0] == '\r' && s[1] == '\n')) break; - k.buf = (char *) s; - while (s < end && s[0] != ':' && (n = clen(s, end)) > 0) s += n, k.len += n; - if (k.len == 0) return false; // Empty name - if (s >= end || clen(s, end) == 0) return false; // Invalid UTF-8 - if (*s++ != ':') return false; // Invalid, not followed by : - // if (clen(s, end) == 0) return false; // Invalid UTF-8 - while (s < end && (s[0] == ' ' || s[0] == '\t')) s++; // Skip spaces - if ((s = skiptorn(s, end, &v)) == NULL) return false; - while (v.len > 0 && (v.buf[v.len - 1] == ' ' || v.buf[v.len - 1] == '\t')) { - v.len--; // Trim spaces - } - // MG_INFO(("--HH [%.*s] [%.*s]", (int) k.len, k.buf, (int) v.len, v.buf)); - h[i].name = k, h[i].value = v; // Success. Assign values - } - return true; +struct mg_connection *mg_http_listen(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data) { + struct mg_connection *c = mg_listen(mgr, url, fn, fn_data); + if (c != NULL) c->pfn = http_cb; + return c; } -int mg_http_parse(const char *s, size_t len, struct mg_http_message *hm) { - int is_response, req_len = mg_http_get_request_len((unsigned char *) s, len); - const char *end = s == NULL ? NULL : s + req_len, *qs; // Cannot add to NULL - const struct mg_str *cl; - size_t n; +#ifdef MG_ENABLE_LINES +#line 1 "src/iobuf.c" +#endif - memset(hm, 0, sizeof(*hm)); - if (req_len <= 0) return req_len; - hm->message.buf = hm->head.buf = (char *) s; - hm->body.buf = (char *) end; - hm->head.len = (size_t) req_len; - hm->message.len = hm->body.len = (size_t) -1; // Set body length to infinite - // Parse request line - hm->method.buf = (char *) s; - while (s < end && (n = clen(s, end)) > 0) s += n, hm->method.len += n; - while (s < end && s[0] == ' ') s++; // Skip spaces - hm->uri.buf = (char *) s; - while (s < end && (n = clen(s, end)) > 0) s += n, hm->uri.len += n; - while (s < end && s[0] == ' ') s++; // Skip spaces - if ((s = skiptorn(s, end, &hm->proto)) == NULL) return false; - // If URI contains '?' character, setup query string - if ((qs = (const char *) memchr(hm->uri.buf, '?', hm->uri.len)) != NULL) { - hm->query.buf = (char *) qs + 1; - hm->query.len = (size_t) (&hm->uri.buf[hm->uri.len] - (qs + 1)); - hm->uri.len = (size_t) (qs - hm->uri.buf); - } - // Sanity check. Allow protocol/reason to be empty - // Do this check after hm->method.len and hm->uri.len are finalised - if (hm->method.len == 0 || hm->uri.len == 0) return -1; +static size_t roundup(size_t size, size_t align) { + return align == 0 ? size : (size + align - 1) / align * align; +} - if (!mg_http_parse_headers(s, end, hm->headers, - sizeof(hm->headers) / sizeof(hm->headers[0]))) - return -1; // error when parsing - if ((cl = mg_http_get_header(hm, "Content-Length")) != NULL) { - if (mg_to_size_t(*cl, &hm->body.len) == false) return -1; - hm->message.len = (size_t) req_len + hm->body.len; +int mg_iobuf_resize(struct mg_iobuf *io, size_t new_size) { + int ok = 1; + new_size = roundup(new_size, io->align); + if (new_size == 0) { + mg_bzero(io->buf, io->size); + free(io->buf); + io->buf = NULL; + io->len = io->size = 0; + } else if (new_size != io->size) { + // NOTE(lsm): do not use realloc here. Use calloc/free only, to ease the + // porting to some obscure platforms like FreeRTOS + void *p = calloc(1, new_size); + if (p != NULL) { + size_t len = new_size < io->len ? new_size : io->len; + if (len > 0 && io->buf != NULL) memmove(p, io->buf, len); + mg_bzero(io->buf, io->size); + free(io->buf); + io->buf = (unsigned char *) p; + io->size = new_size; + } else { + ok = 0; + MG_ERROR(("%lld->%lld", (uint64_t) io->size, (uint64_t) new_size)); + } } + return ok; +} - // mg_http_parse() is used to parse both HTTP requests and HTTP - // responses. If HTTP response does not have Content-Length set, then - // body is read until socket is closed, i.e. body.len is infinite (~0). - // - // For HTTP requests though, according to - // http://tools.ietf.org/html/rfc7231#section-8.1.3, - // only POST and PUT methods have defined body semantics. - // Therefore, if Content-Length is not specified and methods are - // not one of PUT or POST, set body length to 0. - // - // So, if it is HTTP request, and Content-Length is not set, - // and method is not (PUT or POST) then reset body length to zero. - is_response = mg_ncasecmp(hm->method.buf, "HTTP/", 5) == 0; - if (hm->body.len == (size_t) ~0 && !is_response && - mg_strcasecmp(hm->method, mg_str("PUT")) != 0 && - mg_strcasecmp(hm->method, mg_str("POST")) != 0) { - hm->body.len = 0; - hm->message.len = (size_t) req_len; - } - - // The 204 (No content) responses also have 0 body length - if (hm->body.len == (size_t) ~0 && is_response && - mg_strcasecmp(hm->uri, mg_str("204")) == 0) { - hm->body.len = 0; - hm->message.len = (size_t) req_len; - } - if (hm->message.len < (size_t) req_len) return -1; // Overflow protection +int mg_iobuf_init(struct mg_iobuf *io, size_t size, size_t align) { + io->buf = NULL; + io->align = align; + io->size = io->len = 0; + return mg_iobuf_resize(io, size); +} - return req_len; +size_t mg_iobuf_add(struct mg_iobuf *io, size_t ofs, const void *buf, + size_t len) { + size_t new_size = roundup(io->len + len, io->align); + mg_iobuf_resize(io, new_size); // Attempt to resize + if (new_size != io->size) len = 0; // Resize failure, append nothing + if (ofs < io->len) memmove(io->buf + ofs + len, io->buf + ofs, io->len - ofs); + if (buf != NULL) memmove(io->buf + ofs, buf, len); + if (ofs > io->len) io->len += ofs - io->len; + io->len += len; + return len; } -static void mg_http_vprintf_chunk(struct mg_connection *c, const char *fmt, - va_list *ap) { - size_t len = c->send.len; - mg_send(c, " \r\n", 10); - mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap); - if (c->send.len >= len + 10) { - mg_snprintf((char *) c->send.buf + len, 9, "%08lx", c->send.len - len - 10); - c->send.buf[len + 8] = '\r'; - if (c->send.len == len + 10) c->is_resp = 0; // Last chunk, reset marker - } - mg_send(c, "\r\n", 2); +size_t mg_iobuf_del(struct mg_iobuf *io, size_t ofs, size_t len) { + if (ofs > io->len) ofs = io->len; + if (ofs + len > io->len) len = io->len - ofs; + if (io->buf) memmove(io->buf + ofs, io->buf + ofs + len, io->len - ofs - len); + if (io->buf) mg_bzero(io->buf + io->len - len, len); + io->len -= len; + return len; } -void mg_http_printf_chunk(struct mg_connection *c, const char *fmt, ...) { - va_list ap; - va_start(ap, fmt); - mg_http_vprintf_chunk(c, fmt, &ap); - va_end(ap); +void mg_iobuf_free(struct mg_iobuf *io) { + mg_iobuf_resize(io, 0); } -void mg_http_write_chunk(struct mg_connection *c, const char *buf, size_t len) { - mg_printf(c, "%lx\r\n", (unsigned long) len); - mg_send(c, buf, len); - mg_send(c, "\r\n", 2); - if (len == 0) c->is_resp = 0; +#ifdef MG_ENABLE_LINES +#line 1 "src/json.c" +#endif + + + + +static const char *escapeseq(int esc) { + return esc ? "\b\f\n\r\t\\\"" : "bfnrt\\\""; } -// clang-format off -static const char *mg_http_status_code_str(int status_code) { - switch (status_code) { - case 100: return "Continue"; - case 101: return "Switching Protocols"; - case 102: return "Processing"; - case 200: return "OK"; - case 201: return "Created"; - case 202: return "Accepted"; - case 203: return "Non-authoritative Information"; - case 204: return "No Content"; - case 205: return "Reset Content"; - case 206: return "Partial Content"; - case 207: return "Multi-Status"; - case 208: return "Already Reported"; - case 226: return "IM Used"; - case 300: return "Multiple Choices"; - case 301: return "Moved Permanently"; - case 302: return "Found"; - case 303: return "See Other"; - case 304: return "Not Modified"; - case 305: return "Use Proxy"; - case 307: return "Temporary Redirect"; - case 308: return "Permanent Redirect"; - case 400: return "Bad Request"; - case 401: return "Unauthorized"; - case 402: return "Payment Required"; - case 403: return "Forbidden"; - case 404: return "Not Found"; - case 405: return "Method Not Allowed"; - case 406: return "Not Acceptable"; - case 407: return "Proxy Authentication Required"; - case 408: return "Request Timeout"; - case 409: return "Conflict"; - case 410: return "Gone"; - case 411: return "Length Required"; - case 412: return "Precondition Failed"; - case 413: return "Payload Too Large"; - case 414: return "Request-URI Too Long"; - case 415: return "Unsupported Media Type"; - case 416: return "Requested Range Not Satisfiable"; - case 417: return "Expectation Failed"; - case 418: return "I'm a teapot"; - case 421: return "Misdirected Request"; - case 422: return "Unprocessable Entity"; - case 423: return "Locked"; - case 424: return "Failed Dependency"; - case 426: return "Upgrade Required"; - case 428: return "Precondition Required"; - case 429: return "Too Many Requests"; - case 431: return "Request Header Fields Too Large"; - case 444: return "Connection Closed Without Response"; - case 451: return "Unavailable For Legal Reasons"; - case 499: return "Client Closed Request"; - case 500: return "Internal Server Error"; - case 501: return "Not Implemented"; - case 502: return "Bad Gateway"; - case 503: return "Service Unavailable"; - case 504: return "Gateway Timeout"; - case 505: return "HTTP Version Not Supported"; - case 506: return "Variant Also Negotiates"; - case 507: return "Insufficient Storage"; - case 508: return "Loop Detected"; - case 510: return "Not Extended"; - case 511: return "Network Authentication Required"; - case 599: return "Network Connect Timeout Error"; - default: return ""; +static char json_esc(int c, int esc) { + const char *p, *esc1 = escapeseq(esc), *esc2 = escapeseq(!esc); + for (p = esc1; *p != '\0'; p++) { + if (*p == c) return esc2[p - esc1]; } + return 0; } -// clang-format on -void mg_http_reply(struct mg_connection *c, int code, const char *headers, - const char *fmt, ...) { - va_list ap; - size_t len; - mg_printf(c, "HTTP/1.1 %d %s\r\n%sContent-Length: \r\n\r\n", code, - mg_http_status_code_str(code), headers == NULL ? "" : headers); - len = c->send.len; - va_start(ap, fmt); - mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, &ap); - va_end(ap); - if (c->send.len > 16) { - size_t n = mg_snprintf((char *) &c->send.buf[len - 15], 11, "%-10lu", - (unsigned long) (c->send.len - len)); - c->send.buf[len - 15 + n] = ' '; // Change ending 0 to space +static int mg_pass_string(const char *s, int len) { + int i; + for (i = 0; i < len; i++) { + if (s[i] == '\\' && i + 1 < len && json_esc(s[i + 1], 1)) { + i++; + } else if (s[i] == '\0') { + return MG_JSON_INVALID; + } else if (s[i] == '"') { + return i; + } } - c->is_resp = 0; + return MG_JSON_INVALID; } -static void http_cb(struct mg_connection *, int, void *); -static void restore_http_cb(struct mg_connection *c) { - mg_fs_close((struct mg_fd *) c->pfn_data); - c->pfn_data = NULL; - c->pfn = http_cb; - c->is_resp = 0; -} +static double mg_atod(const char *p, int len, int *numlen) { + double d = 0.0; + int i = 0, sign = 1; -char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime); -char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime) { - mg_snprintf(buf, len, "\"%lld.%lld\"", (int64_t) mtime, (int64_t) size); - return buf; -} + // Sign + if (i < len && *p == '-') { + sign = -1, i++; + } else if (i < len && *p == '+') { + i++; + } -static void static_cb(struct mg_connection *c, int ev, void *ev_data) { - if (ev == MG_EV_WRITE || ev == MG_EV_POLL) { - struct mg_fd *fd = (struct mg_fd *) c->pfn_data; - // Read to send IO buffer directly, avoid extra on-stack buffer - size_t n, max = MG_IO_SIZE, space; - size_t *cl = (size_t *) &c->data[(sizeof(c->data) - sizeof(size_t)) / - sizeof(size_t) * sizeof(size_t)]; - if (c->send.size < max) mg_iobuf_resize(&c->send, max); - if (c->send.len >= c->send.size) return; // Rate limit - if ((space = c->send.size - c->send.len) > *cl) space = *cl; - n = fd->fs->rd(fd->fd, c->send.buf + c->send.len, space); - c->send.len += n; - *cl -= n; - if (n == 0) restore_http_cb(c); - } else if (ev == MG_EV_CLOSE) { - restore_http_cb(c); + // Decimal + for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) { + d *= 10.0; + d += p[i] - '0'; } - (void) ev_data; + d *= sign; + + // Fractional + if (i < len && p[i] == '.') { + double frac = 0.0, base = 0.1; + i++; + for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) { + frac += base * (p[i] - '0'); + base /= 10.0; + } + d += frac * sign; + } + + // Exponential + if (i < len && (p[i] == 'e' || p[i] == 'E')) { + int j, exp = 0, minus = 0; + i++; + if (i < len && p[i] == '-') minus = 1, i++; + if (i < len && p[i] == '+') i++; + while (i < len && p[i] >= '0' && p[i] <= '9' && exp < 308) + exp = exp * 10 + (p[i++] - '0'); + if (minus) exp = -exp; + for (j = 0; j < exp; j++) d *= 10.0; + for (j = 0; j < -exp; j++) d /= 10.0; + } + + if (numlen != NULL) *numlen = i; + return d; } -// Known mime types. Keep it outside guess_content_type() function, since -// some environments don't like it defined there. -// clang-format off -#define MG_C_STR(a) { (char *) (a), sizeof(a) - 1 } -static struct mg_str s_known_types[] = { - MG_C_STR("html"), MG_C_STR("text/html; charset=utf-8"), - MG_C_STR("htm"), MG_C_STR("text/html; charset=utf-8"), - MG_C_STR("css"), MG_C_STR("text/css; charset=utf-8"), - MG_C_STR("js"), MG_C_STR("text/javascript; charset=utf-8"), - MG_C_STR("gif"), MG_C_STR("image/gif"), - MG_C_STR("png"), MG_C_STR("image/png"), - MG_C_STR("jpg"), MG_C_STR("image/jpeg"), - MG_C_STR("jpeg"), MG_C_STR("image/jpeg"), - MG_C_STR("woff"), MG_C_STR("font/woff"), - MG_C_STR("ttf"), MG_C_STR("font/ttf"), - MG_C_STR("svg"), MG_C_STR("image/svg+xml"), - MG_C_STR("txt"), MG_C_STR("text/plain; charset=utf-8"), - MG_C_STR("avi"), MG_C_STR("video/x-msvideo"), - MG_C_STR("csv"), MG_C_STR("text/csv"), - MG_C_STR("doc"), MG_C_STR("application/msword"), - MG_C_STR("exe"), MG_C_STR("application/octet-stream"), - MG_C_STR("gz"), MG_C_STR("application/gzip"), - MG_C_STR("ico"), MG_C_STR("image/x-icon"), - MG_C_STR("json"), MG_C_STR("application/json"), - MG_C_STR("mov"), MG_C_STR("video/quicktime"), - MG_C_STR("mp3"), MG_C_STR("audio/mpeg"), - MG_C_STR("mp4"), MG_C_STR("video/mp4"), - MG_C_STR("mpeg"), MG_C_STR("video/mpeg"), - MG_C_STR("pdf"), MG_C_STR("application/pdf"), - MG_C_STR("shtml"), MG_C_STR("text/html; charset=utf-8"), - MG_C_STR("tgz"), MG_C_STR("application/tar-gz"), - MG_C_STR("wav"), MG_C_STR("audio/wav"), - MG_C_STR("webp"), MG_C_STR("image/webp"), - MG_C_STR("zip"), MG_C_STR("application/zip"), - MG_C_STR("3gp"), MG_C_STR("video/3gpp"), - {0, 0}, -}; -// clang-format on - -static struct mg_str guess_content_type(struct mg_str path, const char *extra) { - struct mg_str entry, k, v, s = mg_str(extra), asterisk = mg_str_n("*", 1); - size_t i = 0; - - // Shrink path to its extension only - while (i < path.len && path.buf[path.len - i - 1] != '.') i++; - path.buf += path.len - i; - path.len = i; - - // Process user-provided mime type overrides, if any - while (mg_span(s, &entry, &s, ',')) { - if (mg_span(entry, &k, &v, '=') && - (mg_strcmp(asterisk, k) == 0 || mg_strcmp(path, k) == 0)) - return v; - } - - // Process built-in mime types - for (i = 0; s_known_types[i].buf != NULL; i += 2) { - if (mg_strcmp(path, s_known_types[i]) == 0) return s_known_types[i + 1]; - } - - return mg_str("text/plain; charset=utf-8"); -} - -static int getrange(struct mg_str *s, size_t *a, size_t *b) { - size_t i, numparsed = 0; - for (i = 0; i + 6 < s->len; i++) { - struct mg_str k, v = mg_str_n(s->buf + i + 6, s->len - i - 6); - if (memcmp(&s->buf[i], "bytes=", 6) != 0) continue; - if (mg_span(v, &k, &v, '-')) { - if (mg_to_size_t(k, a)) numparsed++; - if (v.len > 0 && mg_to_size_t(v, b)) numparsed++; - } else { - if (mg_to_size_t(v, a)) numparsed++; - } - break; - } - return (int) numparsed; -} - -void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm, - const char *path, - const struct mg_http_serve_opts *opts) { - char etag[64], tmp[MG_PATH_MAX]; - struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs; - struct mg_fd *fd = NULL; - size_t size = 0; - time_t mtime = 0; - struct mg_str *inm = NULL; - struct mg_str mime = guess_content_type(mg_str(path), opts->mime_types); - bool gzip = false; - - if (path != NULL) { - // If a browser sends us "Accept-Encoding: gzip", try to open .gz first - struct mg_str *ae = mg_http_get_header(hm, "Accept-Encoding"); - if (ae != NULL) { - char *ae_ = mg_mprintf("%.*s", ae->len, ae->buf); - if (ae_ != NULL && strstr(ae_, "gzip") != NULL) { - mg_snprintf(tmp, sizeof(tmp), "%s.gz", path); - fd = mg_fs_open(fs, tmp, MG_FS_READ); - if (fd != NULL) gzip = true, path = tmp; - } - free(ae_); - } - // No luck opening .gz? Open what we've told to open - if (fd == NULL) fd = mg_fs_open(fs, path, MG_FS_READ); - } - - // Failed to open, and page404 is configured? Open it, then - if (fd == NULL && opts->page404 != NULL) { - fd = mg_fs_open(fs, opts->page404, MG_FS_READ); - path = opts->page404; - mime = guess_content_type(mg_str(path), opts->mime_types); - } - - if (fd == NULL || fs->st(path, &size, &mtime) == 0) { - mg_http_reply(c, 404, opts->extra_headers, "Not found\n"); - mg_fs_close(fd); - // NOTE: mg_http_etag() call should go first! - } else if (mg_http_etag(etag, sizeof(etag), size, mtime) != NULL && - (inm = mg_http_get_header(hm, "If-None-Match")) != NULL && - mg_strcasecmp(*inm, mg_str(etag)) == 0) { - mg_fs_close(fd); - mg_http_reply(c, 304, opts->extra_headers, ""); +// Iterate over object or array elements +size_t mg_json_next(struct mg_str obj, size_t ofs, struct mg_str *key, + struct mg_str *val) { + if (ofs >= obj.len) { + ofs = 0; // Out of boundaries, stop scanning + } else if (obj.len < 2 || (*obj.buf != '{' && *obj.buf != '[')) { + ofs = 0; // Not an array or object, stop } else { - int n, status = 200; - char range[100]; - size_t r1 = 0, r2 = 0, cl = size; - - // Handle Range header - struct mg_str *rh = mg_http_get_header(hm, "Range"); - range[0] = '\0'; - if (rh != NULL && (n = getrange(rh, &r1, &r2)) > 0) { - // If range is specified like "400-", set second limit to content len - if (n == 1) r2 = cl - 1; - if (r1 > r2 || r2 >= cl) { - status = 416; - cl = 0; - mg_snprintf(range, sizeof(range), "Content-Range: bytes */%lld\r\n", - (int64_t) size); + struct mg_str sub = mg_str_n(obj.buf + ofs, obj.len - ofs); + if (ofs == 0) ofs++, sub.buf++, sub.len--; + if (*obj.buf == '[') { // Iterate over an array + int n = 0, o = mg_json_get(sub, "$", &n); + if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) { + ofs = 0; // Error parsing key, stop scanning } else { - status = 206; - cl = r2 - r1 + 1; - mg_snprintf(range, sizeof(range), - "Content-Range: bytes %llu-%llu/%llu\r\n", (uint64_t) r1, - (uint64_t) (r1 + cl - 1), (uint64_t) size); - fs->sk(fd->fd, r1); + if (key) *key = mg_str_n(NULL, 0); + if (val) *val = mg_str_n(sub.buf + o, (size_t) n); + ofs = (size_t) (&sub.buf[o + n] - obj.buf); + } + } else { // Iterate over an object + int n = 0, o = mg_json_get(sub, "$", &n); + if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) { + ofs = 0; // Error parsing key, stop scanning + } else { + if (key) *key = mg_str_n(sub.buf + o, (size_t) n); + sub.buf += o + n, sub.len -= (size_t) (o + n); + while (sub.len > 0 && *sub.buf != ':') sub.len--, sub.buf++; + if (sub.len > 0 && *sub.buf == ':') sub.len--, sub.buf++; + n = 0, o = mg_json_get(sub, "$", &n); + if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) { + ofs = 0; // Error parsing value, stop scanning + } else { + if (val) *val = mg_str_n(sub.buf + o, (size_t) n); + ofs = (size_t) (&sub.buf[o + n] - obj.buf); + } } } - mg_printf(c, - "HTTP/1.1 %d %s\r\n" - "Content-Type: %.*s\r\n" - "Etag: %s\r\n" - "Content-Length: %llu\r\n" - "%s%s%s\r\n", - status, mg_http_status_code_str(status), (int) mime.len, mime.buf, - etag, (uint64_t) cl, gzip ? "Content-Encoding: gzip\r\n" : "", - range, opts->extra_headers ? opts->extra_headers : ""); - if (mg_strcasecmp(hm->method, mg_str("HEAD")) == 0) { - c->is_resp = 0; - mg_fs_close(fd); - } else { - // Track to-be-sent content length at the end of c->data, aligned - size_t *clp = (size_t *) &c->data[(sizeof(c->data) - sizeof(size_t)) / - sizeof(size_t) * sizeof(size_t)]; - c->pfn = static_cb; - c->pfn_data = fd; - *clp = cl; + // MG_INFO(("SUB ofs %u %.*s", ofs, sub.len, sub.buf)); + while (ofs && ofs < obj.len && + (obj.buf[ofs] == ' ' || obj.buf[ofs] == '\t' || + obj.buf[ofs] == '\n' || obj.buf[ofs] == '\r')) { + ofs++; } + if (ofs && ofs < obj.len && obj.buf[ofs] == ',') ofs++; + if (ofs > obj.len) ofs = 0; } + return ofs; } -struct printdirentrydata { - struct mg_connection *c; - struct mg_http_message *hm; - const struct mg_http_serve_opts *opts; - const char *dir; -}; - -#if MG_ENABLE_DIRLIST -static void printdirentry(const char *name, void *userdata) { - struct printdirentrydata *d = (struct printdirentrydata *) userdata; - struct mg_fs *fs = d->opts->fs == NULL ? &mg_fs_posix : d->opts->fs; - size_t size = 0; - time_t t = 0; - char path[MG_PATH_MAX], sz[40], mod[40]; - int flags, n = 0; +int mg_json_get(struct mg_str json, const char *path, int *toklen) { + const char *s = json.buf; + int len = (int) json.len; + enum { S_VALUE, S_KEY, S_COLON, S_COMMA_OR_EOO } expecting = S_VALUE; + unsigned char nesting[MG_JSON_MAX_DEPTH]; + int i = 0; // Current offset in `s` + int j = 0; // Offset in `s` we're looking for (return value) + int depth = 0; // Current depth (nesting level) + int ed = 0; // Expected depth + int pos = 1; // Current position in `path` + int ci = -1, ei = -1; // Current and expected index in array - // MG_DEBUG(("[%s] [%s]", d->dir, name)); - if (mg_snprintf(path, sizeof(path), "%s%c%s", d->dir, '/', name) > - sizeof(path)) { - MG_ERROR(("%s truncated", name)); - } else if ((flags = fs->st(path, &size, &t)) == 0) { - MG_ERROR(("%lu stat(%s): %d", d->c->id, path, errno)); - } else { - const char *slash = flags & MG_FS_DIR ? "/" : ""; - if (flags & MG_FS_DIR) { - mg_snprintf(sz, sizeof(sz), "%s", "[DIR]"); - } else { - mg_snprintf(sz, sizeof(sz), "%lld", (uint64_t) size); - } -#if defined(MG_HTTP_DIRLIST_TIME_FMT) - { - char time_str[40]; - struct tm *time_info = localtime(&t); - strftime(time_str, sizeof time_str, "%Y/%m/%d %H:%M:%S", time_info); - mg_snprintf(mod, sizeof(mod), "%s", time_str); - } -#else - mg_snprintf(mod, sizeof(mod), "%lu", (unsigned long) t); -#endif - n = (int) mg_url_encode(name, strlen(name), path, sizeof(path)); - mg_printf(d->c, - " %s%s" - "%s%s\n", - n, path, slash, name, slash, (unsigned long) t, mod, - flags & MG_FS_DIR ? (int64_t) -1 : (int64_t) size, sz); - } -} + if (toklen) *toklen = 0; + if (path[0] != '$') return MG_JSON_INVALID; -static void listdir(struct mg_connection *c, struct mg_http_message *hm, - const struct mg_http_serve_opts *opts, char *dir) { - const char *sort_js_code = - ""; - struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs; - struct printdirentrydata d = {c, hm, opts, dir}; - char tmp[10], buf[MG_PATH_MAX]; - size_t off, n; - int len = mg_url_decode(hm->uri.buf, hm->uri.len, buf, sizeof(buf), 0); - struct mg_str uri = len > 0 ? mg_str_n(buf, (size_t) len) : hm->uri; +#define MG_CHECKRET(x) \ + do { \ + if (depth == ed && path[pos] == '\0' && ci == ei) { \ + if (toklen) *toklen = i - j + 1; \ + return j; \ + } \ + } while (0) - mg_printf(c, - "HTTP/1.1 200 OK\r\n" - "Content-Type: text/html; charset=utf-8\r\n" - "%s" - "Content-Length: \r\n\r\n", - opts->extra_headers == NULL ? "" : opts->extra_headers); - off = c->send.len; // Start of body - mg_printf(c, - "Index of %.*s%s%s" - "" - "

Index of %.*s

" - "" - "" - "" - "" - "\n", - (int) uri.len, uri.buf, sort_js_code, sort_js_code2, (int) uri.len, - uri.buf); - mg_printf(c, "%s", - " " - "\n"); +// In the ascii table, the distance between `[` and `]` is 2. +// Ditto for `{` and `}`. Hence +2 in the code below. +#define MG_EOO(x) \ + do { \ + if (depth == ed && ci != ei) return MG_JSON_NOT_FOUND; \ + if (c != nesting[depth - 1] + 2) return MG_JSON_INVALID; \ + depth--; \ + MG_CHECKRET(x); \ + } while (0) - fs->ls(dir, printdirentry, &d); - mg_printf(c, - "" - "
Name" - "ModifiedSize

..[DIR]

Mongoose v.%s
\n", - MG_VERSION); - n = mg_snprintf(tmp, sizeof(tmp), "%lu", (unsigned long) (c->send.len - off)); - if (n > sizeof(tmp)) n = 0; - memcpy(c->send.buf + off - 12, tmp, n); // Set content length - c->is_resp = 0; // Mark response end -} -#endif + for (i = 0; i < len; i++) { + unsigned char c = ((unsigned char *) s)[i]; + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue; + switch (expecting) { + case S_VALUE: + // p("V %s [%.*s] %d %d %d %d\n", path, pos, path, depth, ed, ci, ei); + if (depth == ed) j = i; + if (c == '{') { + if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP; + if (depth == ed && path[pos] == '.' && ci == ei) { + // If we start the object, reset array indices + ed++, pos++, ci = ei = -1; + } + nesting[depth++] = c; + expecting = S_KEY; + break; + } else if (c == '[') { + if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP; + if (depth == ed && path[pos] == '[' && ei == ci) { + ed++, pos++, ci = 0; + for (ei = 0; path[pos] != ']' && path[pos] != '\0'; pos++) { + ei *= 10; + ei += path[pos] - '0'; + } + if (path[pos] != 0) pos++; + } + nesting[depth++] = c; + break; + } else if (c == ']' && depth > 0) { // Empty array + MG_EOO(']'); + } else if (c == 't' && i + 3 < len && memcmp(&s[i], "true", 4) == 0) { + i += 3; + } else if (c == 'n' && i + 3 < len && memcmp(&s[i], "null", 4) == 0) { + i += 3; + } else if (c == 'f' && i + 4 < len && memcmp(&s[i], "false", 5) == 0) { + i += 4; + } else if (c == '-' || ((c >= '0' && c <= '9'))) { + int numlen = 0; + mg_atod(&s[i], len - i, &numlen); + i += numlen - 1; + } else if (c == '"') { + int n = mg_pass_string(&s[i + 1], len - i - 1); + if (n < 0) return n; + i += n + 1; + } else { + return MG_JSON_INVALID; + } + MG_CHECKRET('V'); + if (depth == ed && ei >= 0) ci++; + expecting = S_COMMA_OR_EOO; + break; -// Resolve requested file into `path` and return its fs->st() result -static int uri_to_path2(struct mg_connection *c, struct mg_http_message *hm, - struct mg_fs *fs, struct mg_str url, struct mg_str dir, - char *path, size_t path_size) { - int flags, tmp; - // Append URI to the root_dir, and sanitize it - size_t n = mg_snprintf(path, path_size, "%.*s", (int) dir.len, dir.buf); - if (n + 2 >= path_size) { - mg_http_reply(c, 400, "", "Exceeded path size"); - return -1; + case S_KEY: + if (c == '"') { + int n = mg_pass_string(&s[i + 1], len - i - 1); + if (n < 0) return n; + if (i + 1 + n >= len) return MG_JSON_NOT_FOUND; + if (depth < ed) return MG_JSON_NOT_FOUND; + if (depth == ed && path[pos - 1] != '.') return MG_JSON_NOT_FOUND; + // printf("K %s [%.*s] [%.*s] %d %d %d %d %d\n", path, pos, path, n, + // &s[i + 1], n, depth, ed, ci, ei); + // NOTE(cpq): in the check sequence below is important. + // strncmp() must go first: it fails fast if the remaining length + // of the path is smaller than `n`. + if (depth == ed && path[pos - 1] == '.' && + strncmp(&s[i + 1], &path[pos], (size_t) n) == 0 && + (path[pos + n] == '\0' || path[pos + n] == '.' || + path[pos + n] == '[')) { + pos += n; + } + i += n + 1; + expecting = S_COLON; + } else if (c == '}') { // Empty object + MG_EOO('}'); + expecting = S_COMMA_OR_EOO; + if (depth == ed && ei >= 0) ci++; + } else { + return MG_JSON_INVALID; + } + break; + + case S_COLON: + if (c == ':') { + expecting = S_VALUE; + } else { + return MG_JSON_INVALID; + } + break; + + case S_COMMA_OR_EOO: + if (depth <= 0) { + return MG_JSON_INVALID; + } else if (c == ',') { + expecting = (nesting[depth - 1] == '{') ? S_KEY : S_VALUE; + } else if (c == ']' || c == '}') { + if (depth == ed && c == '}' && path[pos - 1] == '.') + return MG_JSON_NOT_FOUND; + if (depth == ed && c == ']' && path[pos - 1] == ',') + return MG_JSON_NOT_FOUND; + MG_EOO('O'); + if (depth == ed && ei >= 0) ci++; + } else { + return MG_JSON_INVALID; + } + break; + } } - path[path_size - 1] = '\0'; - // Terminate root dir with slash - if (n > 0 && path[n - 1] != '/') path[n++] = '/', path[n] = '\0'; - if (url.len < hm->uri.len) { - mg_url_decode(hm->uri.buf + url.len, hm->uri.len - url.len, path + n, - path_size - n, 0); + return MG_JSON_NOT_FOUND; +} + +struct mg_str mg_json_get_tok(struct mg_str json, const char *path) { + int len = 0, ofs = mg_json_get(json, path, &len); + return mg_str_n(ofs < 0 ? NULL : json.buf + ofs, + (size_t) (len < 0 ? 0 : len)); +} + +bool mg_json_get_num(struct mg_str json, const char *path, double *v) { + int n, toklen, found = 0; + if ((n = mg_json_get(json, path, &toklen)) >= 0 && + (json.buf[n] == '-' || (json.buf[n] >= '0' && json.buf[n] <= '9'))) { + if (v != NULL) *v = mg_atod(json.buf + n, toklen, NULL); + found = 1; } - path[path_size - 1] = '\0'; // Double-check - if (!mg_path_is_sane(mg_str_n(path, path_size))) { - mg_http_reply(c, 400, "", "Invalid path"); - return -1; + return found; +} + +bool mg_json_get_bool(struct mg_str json, const char *path, bool *v) { + int found = 0, off = mg_json_get(json, path, NULL); + if (off >= 0 && (json.buf[off] == 't' || json.buf[off] == 'f')) { + if (v != NULL) *v = json.buf[off] == 't'; + found = 1; } - n = strlen(path); - while (n > 1 && path[n - 1] == '/') path[--n] = 0; // Trim trailing slashes - flags = mg_strcmp(hm->uri, mg_str("/")) == 0 ? MG_FS_DIR - : fs->st(path, NULL, NULL); - MG_VERBOSE(("%lu %.*s -> %s %d", c->id, (int) hm->uri.len, hm->uri.buf, path, - flags)); - if (flags == 0) { - // Do nothing - let's caller decide - } else if ((flags & MG_FS_DIR) && hm->uri.len > 0 && - hm->uri.buf[hm->uri.len - 1] != '/') { - mg_printf(c, - "HTTP/1.1 301 Moved\r\n" - "Location: %.*s/\r\n" - "Content-Length: 0\r\n" - "\r\n", - (int) hm->uri.len, hm->uri.buf); - c->is_resp = 0; - flags = -1; - } else if (flags & MG_FS_DIR) { - if (((mg_snprintf(path + n, path_size - n, "/" MG_HTTP_INDEX) > 0 && - (tmp = fs->st(path, NULL, NULL)) != 0) || - (mg_snprintf(path + n, path_size - n, "/index.shtml") > 0 && - (tmp = fs->st(path, NULL, NULL)) != 0))) { - flags = tmp; - } else if ((mg_snprintf(path + n, path_size - n, "/" MG_HTTP_INDEX ".gz") > - 0 && - (tmp = fs->st(path, NULL, NULL)) != - 0)) { // check for gzipped index - flags = tmp; - path[n + 1 + strlen(MG_HTTP_INDEX)] = - '\0'; // Remove appended .gz in index file name + return found; +} + +bool mg_json_unescape(struct mg_str s, char *to, size_t n) { + size_t i, j; + for (i = 0, j = 0; i < s.len && j < n; i++, j++) { + if (s.buf[i] == '\\' && i + 5 < s.len && s.buf[i + 1] == 'u') { + // \uXXXX escape. We process simple one-byte chars \u00xx within ASCII + // range. More complex chars would require dragging in a UTF8 library, + // which is too much for us + if (mg_str_to_num(mg_str_n(s.buf + i + 2, 4), 16, &to[j], + sizeof(uint8_t)) == false) + return false; + i += 5; + } else if (s.buf[i] == '\\' && i + 1 < s.len) { + char c = json_esc(s.buf[i + 1], 0); + if (c == 0) return false; + to[j] = c; + i++; } else { - path[n] = '\0'; // Remove appended index file name + to[j] = s.buf[i]; } } - return flags; + if (j >= n) return false; + if (n > 0) to[j] = '\0'; + return true; } -static int uri_to_path(struct mg_connection *c, struct mg_http_message *hm, - const struct mg_http_serve_opts *opts, char *path, - size_t path_size) { - struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs; - struct mg_str k, v, part, s = mg_str(opts->root_dir), u = {NULL, 0}, p = u; - while (mg_span(s, &part, &s, ',')) { - if (!mg_span(part, &k, &v, '=')) k = part, v = mg_str_n(NULL, 0); - if (v.len == 0) v = k, k = mg_str("/"), u = k, p = v; - if (hm->uri.len < k.len) continue; - if (mg_strcmp(k, mg_str_n(hm->uri.buf, k.len)) != 0) continue; - u = k, p = v; +char *mg_json_get_str(struct mg_str json, const char *path) { + char *result = NULL; + int len = 0, off = mg_json_get(json, path, &len); + if (off >= 0 && len > 1 && json.buf[off] == '"') { + if ((result = (char *) calloc(1, (size_t) len)) != NULL && + !mg_json_unescape(mg_str_n(json.buf + off + 1, (size_t) (len - 2)), + result, (size_t) len)) { + free(result); + result = NULL; + } } - return uri_to_path2(c, hm, fs, u, p, path, path_size); + return result; } -void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm, - const struct mg_http_serve_opts *opts) { - char path[MG_PATH_MAX]; - const char *sp = opts->ssi_pattern; - int flags = uri_to_path(c, hm, opts, path, sizeof(path)); - if (flags < 0) { - // Do nothing: the response has already been sent by uri_to_path() - } else if (flags & MG_FS_DIR) { -#if MG_ENABLE_DIRLIST - listdir(c, hm, opts, path); -#else - mg_http_reply(c, 403, "", "Forbidden\n"); -#endif - } else if (flags && sp != NULL && mg_match(mg_str(path), mg_str(sp), NULL)) { - mg_http_serve_ssi(c, opts->root_dir, path); - } else { - mg_http_serve_file(c, hm, path, opts); +char *mg_json_get_b64(struct mg_str json, const char *path, int *slen) { + char *result = NULL; + int len = 0, off = mg_json_get(json, path, &len); + if (off >= 0 && json.buf[off] == '"' && len > 1 && + (result = (char *) calloc(1, (size_t) len)) != NULL) { + size_t k = mg_base64_decode(json.buf + off + 1, (size_t) (len - 2), result, + (size_t) len); + if (slen != NULL) *slen = (int) k; } + return result; } -static bool mg_is_url_safe(int c) { - return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || - (c >= 'A' && c <= 'Z') || c == '.' || c == '_' || c == '-' || c == '~'; -} - -size_t mg_url_encode(const char *s, size_t sl, char *buf, size_t len) { - size_t i, n = 0; - for (i = 0; i < sl; i++) { - int c = *(unsigned char *) &s[i]; - if (n + 4 >= len) return 0; - if (mg_is_url_safe(c)) { - buf[n++] = s[i]; - } else { - mg_snprintf(&buf[n], 4, "%%%M", mg_print_hex, 1, &s[i]); - n += 3; +char *mg_json_get_hex(struct mg_str json, const char *path, int *slen) { + char *result = NULL; + int len = 0, off = mg_json_get(json, path, &len); + if (off >= 0 && json.buf[off] == '"' && len > 1 && + (result = (char *) calloc(1, (size_t) len / 2)) != NULL) { + int i; + for (i = 0; i < len - 2; i += 2) { + mg_str_to_num(mg_str_n(json.buf + off + 1 + i, 2), 16, &result[i >> 1], + sizeof(uint8_t)); } + result[len / 2 - 1] = '\0'; + if (slen != NULL) *slen = len / 2 - 1; } - if (len > 0 && n < len - 1) buf[n] = '\0'; // Null-terminate the destination - if (len > 0) buf[len - 1] = '\0'; // Always. - return n; + return result; +} + +long mg_json_get_long(struct mg_str json, const char *path, long dflt) { + double dv; + long result = dflt; + if (mg_json_get_num(json, path, &dv)) result = (long) dv; + return result; +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/log.c" +#endif + + + + + +int mg_log_level = MG_LL_INFO; +static mg_pfn_t s_log_func = mg_pfn_stdout; +static void *s_log_func_param = NULL; + +void mg_log_set_fn(mg_pfn_t fn, void *param) { + s_log_func = fn; + s_log_func_param = param; +} + +static void logc(unsigned char c) { + s_log_func((char) c, s_log_func_param); +} + +static void logs(const char *buf, size_t len) { + size_t i; + for (i = 0; i < len; i++) logc(((unsigned char *) buf)[i]); } -void mg_http_creds(struct mg_http_message *hm, char *user, size_t userlen, - char *pass, size_t passlen) { - struct mg_str *v = mg_http_get_header(hm, "Authorization"); - user[0] = pass[0] = '\0'; - if (v != NULL && v->len > 6 && memcmp(v->buf, "Basic ", 6) == 0) { - char buf[256]; - size_t n = mg_base64_decode(v->buf + 6, v->len - 6, buf, sizeof(buf)); - const char *p = (const char *) memchr(buf, ':', n > 0 ? n : 0); - if (p != NULL) { - mg_snprintf(user, userlen, "%.*s", p - buf, buf); - mg_snprintf(pass, passlen, "%.*s", n - (size_t) (p - buf) - 1, p + 1); - } - } else if (v != NULL && v->len > 7 && memcmp(v->buf, "Bearer ", 7) == 0) { - mg_snprintf(pass, passlen, "%.*s", (int) v->len - 7, v->buf + 7); - } else if ((v = mg_http_get_header(hm, "Cookie")) != NULL) { - struct mg_str t = mg_http_get_header_var(*v, mg_str_n("access_token", 12)); - if (t.len > 0) mg_snprintf(pass, passlen, "%.*s", (int) t.len, t.buf); - } else { - mg_http_get_var(&hm->query, "access_token", pass, passlen); - } +#if MG_ENABLE_CUSTOM_LOG +// Let user define their own mg_log_prefix() and mg_log() +#else +void mg_log_prefix(int level, const char *file, int line, const char *fname) { + const char *p = strrchr(file, '/'); + char buf[41]; + size_t n; + if (p == NULL) p = strrchr(file, '\\'); + n = mg_snprintf(buf, sizeof(buf), "%-6llx %d %s:%d:%s", mg_millis(), level, + p == NULL ? file : p + 1, line, fname); + if (n > sizeof(buf) - 2) n = sizeof(buf) - 2; + while (n < sizeof(buf)) buf[n++] = ' '; + logs(buf, n - 1); } -static struct mg_str stripquotes(struct mg_str s) { - return s.len > 1 && s.buf[0] == '"' && s.buf[s.len - 1] == '"' - ? mg_str_n(s.buf + 1, s.len - 2) - : s; +void mg_log(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + mg_vxprintf(s_log_func, s_log_func_param, fmt, &ap); + va_end(ap); + logs("\r\n", 2); } +#endif -struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v) { +static unsigned char nibble(unsigned c) { + return (unsigned char) (c < 10 ? c + '0' : c + 'W'); +} + +#define ISPRINT(x) ((x) >= ' ' && (x) <= '~') +void mg_hexdump(const void *buf, size_t len) { + const unsigned char *p = (const unsigned char *) buf; + unsigned char ascii[16], alen = 0; size_t i; - for (i = 0; v.len > 0 && i + v.len + 2 < s.len; i++) { - if (s.buf[i + v.len] == '=' && memcmp(&s.buf[i], v.buf, v.len) == 0) { - const char *p = &s.buf[i + v.len + 1], *b = p, *x = &s.buf[s.len]; - int q = p < x && *p == '"' ? 1 : 0; - while (p < x && - (q ? p == b || *p != '"' : *p != ';' && *p != ' ' && *p != ',')) - p++; - // MG_INFO(("[%.*s] [%.*s] [%.*s]", (int) s.len, s.buf, (int) v.len, - // v.buf, (int) (p - b), b)); - return stripquotes(mg_str_n(b, (size_t) (p - b + q))); + for (i = 0; i < len; i++) { + if ((i % 16) == 0) { + // Print buffered ascii chars + if (i > 0) logs(" ", 2), logs((char *) ascii, 16), logc('\n'), alen = 0; + // Print hex address, then \t + logc(nibble((i >> 12) & 15)), logc(nibble((i >> 8) & 15)), + logc(nibble((i >> 4) & 15)), logc('0'), logs(" ", 3); } + logc(nibble(p[i] >> 4)), logc(nibble(p[i] & 15)); // Two nibbles, e.g. c5 + logc(' '); // Space after hex number + ascii[alen++] = ISPRINT(p[i]) ? p[i] : '.'; // Add to the ascii buf } - return mg_str_n(NULL, 0); + while (alen < 16) logs(" ", 3), ascii[alen++] = ' '; + logs(" ", 2), logs((char *) ascii, 16), logc('\n'); } -long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm, - struct mg_fs *fs, const char *dir, size_t max_size) { - char buf[20] = "0", file[MG_PATH_MAX], path[MG_PATH_MAX]; - long res = 0, offset; - mg_http_get_var(&hm->query, "offset", buf, sizeof(buf)); - mg_http_get_var(&hm->query, "file", file, sizeof(file)); - offset = strtol(buf, NULL, 0); - mg_snprintf(path, sizeof(path), "%s%c%s", dir, MG_DIRSEP, file); - if (hm->body.len == 0) { - mg_http_reply(c, 200, "", "%ld", res); // Nothing to write - } else if (file[0] == '\0') { - mg_http_reply(c, 400, "", "file required"); - res = -1; - } else if (mg_path_is_sane(mg_str(file)) == false) { - mg_http_reply(c, 400, "", "%s: invalid file", file); - res = -2; - } else if (offset < 0) { - mg_http_reply(c, 400, "", "offset required"); - res = -3; - } else if ((size_t) offset + hm->body.len > max_size) { - mg_http_reply(c, 400, "", "%s: over max size of %lu", path, - (unsigned long) max_size); - res = -4; +#ifdef MG_ENABLE_LINES +#line 1 "src/md5.c" +#endif + + + +// This code implements the MD5 message-digest algorithm. +// The algorithm is due to Ron Rivest. This code was +// written by Colin Plumb in 1993, no copyright is claimed. +// This code is in the public domain; do with it what you wish. +// +// Equivalent code is available from RSA Data Security, Inc. +// This code has been tested against that, and is equivalent, +// except that you don't need to include two pages of legalese +// with every copy. +// +// To compute the message digest of a chunk of bytes, declare an +// MD5Context structure, pass it to MD5Init, call MD5Update as +// needed on buffers full of bytes, and then call MD5Final, which +// will fill a supplied 16-byte array with the digest. + +#if defined(MG_ENABLE_MD5) && MG_ENABLE_MD5 + +static void mg_byte_reverse(unsigned char *buf, unsigned longs) { + if (MG_BIG_ENDIAN) { + do { + uint32_t t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 | + ((unsigned) buf[1] << 8 | buf[0]); + *(uint32_t *) buf = t; + buf += 4; + } while (--longs); } else { - struct mg_fd *fd; - size_t current_size = 0; - MG_DEBUG(("%s -> %lu bytes @ %ld", path, hm->body.len, offset)); - if (offset == 0) fs->rm(path); // If offset if 0, truncate file - fs->st(path, ¤t_size, NULL); - if (offset > 0 && current_size != (size_t) offset) { - mg_http_reply(c, 400, "", "%s: offset mismatch", path); - res = -5; - } else if ((fd = mg_fs_open(fs, path, MG_FS_WRITE)) == NULL) { - mg_http_reply(c, 400, "", "open(%s): %d", path, errno); - res = -6; - } else { - res = offset + (long) fs->wr(fd->fd, hm->body.buf, hm->body.len); - mg_fs_close(fd); - mg_http_reply(c, 200, "", "%ld", res); - } + (void) buf, (void) longs; // Little endian. Do nothing } - return res; } -int mg_http_status(const struct mg_http_message *hm) { - return atoi(hm->uri.buf); -} +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) -static bool is_hex_digit(int c) { - return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || - (c >= 'A' && c <= 'F'); +#define MD5STEP(f, w, x, y, z, data, s) \ + (w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x) + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void mg_md5_init(mg_md5_ctx *ctx) { + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; } -static int skip_chunk(const char *buf, int len, int *pl, int *dl) { - int i = 0, n = 0; - if (len < 3) return 0; - while (i < len && is_hex_digit(buf[i])) i++; - if (i == 0) return -1; // Error, no length specified - if (i > (int) sizeof(int) * 2) return -1; // Chunk length is too big - if (len < i + 1 || buf[i] != '\r' || buf[i + 1] != '\n') return -1; // Error - if (mg_str_to_num(mg_str_n(buf, (size_t) i), 16, &n, sizeof(int)) == false) - return -1; // Decode chunk length, overflow - if (n < 0) return -1; // Error. TODO(): some checks now redundant - if (n > len - i - 4) return 0; // Chunk not yet fully buffered - if (buf[i + n + 2] != '\r' || buf[i + n + 3] != '\n') return -1; // Error - *pl = i + 2, *dl = n; - return i + 2 + n + 2; +static void mg_md5_transform(uint32_t buf[4], uint32_t const in[16]) { + uint32_t a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; } -static void http_cb(struct mg_connection *c, int ev, void *ev_data) { - if (ev == MG_EV_READ || ev == MG_EV_CLOSE || - (ev == MG_EV_POLL && c->is_accepted && !c->is_draining && - c->recv.len > 0)) { // see #2796 - struct mg_http_message hm; - size_t ofs = 0; // Parsing offset - while (c->is_resp == 0 && ofs < c->recv.len) { - const char *buf = (char *) c->recv.buf + ofs; - int n = mg_http_parse(buf, c->recv.len - ofs, &hm); - struct mg_str *te; // Transfer - encoding header - bool is_chunked = false; - size_t old_len = c->recv.len; - if (n < 0) { - // We don't use mg_error() here, to avoid closing pipelined requests - // prematurely, see #2592 - MG_ERROR(("HTTP parse, %lu bytes", c->recv.len)); - c->is_draining = 1; - mg_hexdump(buf, c->recv.len - ofs > 16 ? 16 : c->recv.len - ofs); - c->recv.len = 0; - return; - } - if (n == 0) break; // Request is not buffered yet - mg_call(c, MG_EV_HTTP_HDRS, &hm); // Got all HTTP headers - if (c->recv.len != old_len) { - // User manipulated received data. Wash our hands - MG_DEBUG(("%lu detaching HTTP handler", c->id)); - c->pfn = NULL; - return; - } - if (ev == MG_EV_CLOSE) { // If client did not set Content-Length - hm.message.len = c->recv.len - ofs; // and closes now, deliver MSG - hm.body.len = hm.message.len - (size_t) (hm.body.buf - hm.message.buf); - } - if ((te = mg_http_get_header(&hm, "Transfer-Encoding")) != NULL) { - if (mg_strcasecmp(*te, mg_str("chunked")) == 0) { - is_chunked = true; - } else { - mg_error(c, "Invalid Transfer-Encoding"); // See #2460 - return; - } - } else if (mg_http_get_header(&hm, "Content-length") == NULL) { - // #2593: HTTP packets must contain either Transfer-Encoding or - // Content-length - bool is_response = mg_ncasecmp(hm.method.buf, "HTTP/", 5) == 0; - bool require_content_len = false; - if (!is_response && (mg_strcasecmp(hm.method, mg_str("POST")) == 0 || - mg_strcasecmp(hm.method, mg_str("PUT")) == 0)) { - // POST and PUT should include an entity body. Therefore, they should - // contain a Content-length header. Other requests can also contain a - // body, but their content has no defined semantics (RFC 7231) - require_content_len = true; - ofs += (size_t) n; // this request has been processed - } else if (is_response) { - // HTTP spec 7.2 Entity body: All other responses must include a body - // or Content-Length header field defined with a value of 0. - int status = mg_http_status(&hm); - require_content_len = status >= 200 && status != 204 && status != 304; - } - if (require_content_len) { - mg_http_reply(c, 411, "", ""); - MG_ERROR(("%s", "Content length missing from request")); - } - } +void mg_md5_update(mg_md5_ctx *ctx, const unsigned char *buf, size_t len) { + uint32_t t; - if (is_chunked) { - // For chunked data, strip off prefixes and suffixes from chunks - // and relocate them right after the headers, then report a message - char *s = (char *) c->recv.buf + ofs + n; - int o = 0, pl, dl, cl, len = (int) (c->recv.len - ofs - (size_t) n); + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t) ctx->bits[1]++; + ctx->bits[1] += (uint32_t) len >> 29; - // Find zero-length chunk (the end of the body) - while ((cl = skip_chunk(s + o, len - o, &pl, &dl)) > 0 && dl) o += cl; - if (cl == 0) break; // No zero-len chunk, buffer more data - if (cl < 0) { - mg_error(c, "Invalid chunk"); - break; - } + t = (t >> 3) & 0x3f; - // Zero chunk found. Second pass: strip + relocate - o = 0, hm.body.len = 0, hm.message.len = (size_t) n; - while ((cl = skip_chunk(s + o, len - o, &pl, &dl)) > 0) { - memmove(s + hm.body.len, s + o + pl, (size_t) dl); - o += cl, hm.body.len += (size_t) dl, hm.message.len += (size_t) dl; - if (dl == 0) break; - } - ofs += (size_t) (n + o); - } else { // Normal, non-chunked data - size_t len = c->recv.len - ofs - (size_t) n; - if (hm.body.len > len) break; // Buffer more data - ofs += (size_t) n + hm.body.len; - } + if (t) { + unsigned char *p = (unsigned char *) ctx->in + t; - if (c->is_accepted) c->is_resp = 1; // Start generating response - mg_call(c, MG_EV_HTTP_MSG, &hm); // User handler can clear is_resp - if (c->is_accepted && !c->is_resp) { - struct mg_str *cc = mg_http_get_header(&hm, "Connection"); - if (cc != NULL && mg_strcasecmp(*cc, mg_str("close")) == 0) { - c->is_draining = 1; // honor "Connection: close" - break; - } - } + t = 64 - t; + if (len < t) { + memcpy(p, buf, len); + return; } - if (ofs > 0) mg_iobuf_del(&c->recv, 0, ofs); // Delete processed data + memcpy(p, buf, t); + mg_byte_reverse(ctx->in, 16); + mg_md5_transform(ctx->buf, (uint32_t *) ctx->in); + buf += t; + len -= t; } - (void) ev_data; -} -static void mg_hfn(struct mg_connection *c, int ev, void *ev_data) { - if (ev == MG_EV_HTTP_MSG) { - struct mg_http_message *hm = (struct mg_http_message *) ev_data; - if (mg_match(hm->uri, mg_str("/quit"), NULL)) { - mg_http_reply(c, 200, "", "ok\n"); - c->is_draining = 1; - c->data[0] = 'X'; - } else if (mg_match(hm->uri, mg_str("/debug"), NULL)) { - int level = (int) mg_json_get_long(hm->body, "$.level", MG_LL_DEBUG); - mg_log_set(level); - mg_http_reply(c, 200, "", "Debug level set to %d\n", level); - } else { - mg_http_reply(c, 200, "", "hi\n"); - } - } else if (ev == MG_EV_CLOSE) { - if (c->data[0] == 'X') *(bool *) c->fn_data = true; + while (len >= 64) { + memcpy(ctx->in, buf, 64); + mg_byte_reverse(ctx->in, 16); + mg_md5_transform(ctx->buf, (uint32_t *) ctx->in); + buf += 64; + len -= 64; } -} -void mg_hello(const char *url) { - struct mg_mgr mgr; - bool done = false; - mg_mgr_init(&mgr); - if (mg_http_listen(&mgr, url, mg_hfn, &done) == NULL) done = true; - while (done == false) mg_mgr_poll(&mgr, 100); - mg_mgr_free(&mgr); + memcpy(ctx->in, buf, len); } -struct mg_connection *mg_http_connect(struct mg_mgr *mgr, const char *url, - mg_event_handler_t fn, void *fn_data) { - struct mg_connection *c = mg_connect(mgr, url, fn, fn_data); - if (c != NULL) c->pfn = http_cb; - return c; -} +void mg_md5_final(mg_md5_ctx *ctx, unsigned char digest[16]) { + unsigned count; + unsigned char *p; + uint32_t *a; -struct mg_connection *mg_http_listen(struct mg_mgr *mgr, const char *url, - mg_event_handler_t fn, void *fn_data) { - struct mg_connection *c = mg_listen(mgr, url, fn, fn_data); - if (c != NULL) c->pfn = http_cb; - return c; + count = (ctx->bits[0] >> 3) & 0x3F; + + p = ctx->in + count; + *p++ = 0x80; + count = 64 - 1 - count; + if (count < 8) { + memset(p, 0, count); + mg_byte_reverse(ctx->in, 16); + mg_md5_transform(ctx->buf, (uint32_t *) ctx->in); + memset(ctx->in, 0, 56); + } else { + memset(p, 0, count - 8); + } + mg_byte_reverse(ctx->in, 14); + + a = (uint32_t *) ctx->in; + a[14] = ctx->bits[0]; + a[15] = ctx->bits[1]; + + mg_md5_transform(ctx->buf, (uint32_t *) ctx->in); + mg_byte_reverse((unsigned char *) ctx->buf, 4); + memcpy(digest, ctx->buf, 16); + memset((char *) ctx, 0, sizeof(*ctx)); } +#endif #ifdef MG_ENABLE_LINES -#line 1 "src/iobuf.c" +#line 1 "src/mqtt.c" #endif -static size_t roundup(size_t size, size_t align) { - return align == 0 ? size : (size + align - 1) / align * align; -} -int mg_iobuf_resize(struct mg_iobuf *io, size_t new_size) { - int ok = 1; - new_size = roundup(new_size, io->align); - if (new_size == 0) { - mg_bzero(io->buf, io->size); - free(io->buf); - io->buf = NULL; - io->len = io->size = 0; - } else if (new_size != io->size) { - // NOTE(lsm): do not use realloc here. Use calloc/free only, to ease the - // porting to some obscure platforms like FreeRTOS - void *p = calloc(1, new_size); - if (p != NULL) { - size_t len = new_size < io->len ? new_size : io->len; - if (len > 0 && io->buf != NULL) memmove(p, io->buf, len); - mg_bzero(io->buf, io->size); - free(io->buf); - io->buf = (unsigned char *) p; - io->size = new_size; - } else { - ok = 0; - MG_ERROR(("%lld->%lld", (uint64_t) io->size, (uint64_t) new_size)); - } - } - return ok; -} -int mg_iobuf_init(struct mg_iobuf *io, size_t size, size_t align) { - io->buf = NULL; - io->align = align; - io->size = io->len = 0; - return mg_iobuf_resize(io, size); + +#define MQTT_CLEAN_SESSION 0x02 +#define MQTT_HAS_WILL 0x04 +#define MQTT_WILL_RETAIN 0x20 +#define MQTT_HAS_PASSWORD 0x40 +#define MQTT_HAS_USER_NAME 0x80 + +struct mg_mqtt_pmap { + uint8_t id; + uint8_t type; +}; + +static const struct mg_mqtt_pmap s_prop_map[] = { + {MQTT_PROP_PAYLOAD_FORMAT_INDICATOR, MQTT_PROP_TYPE_BYTE}, + {MQTT_PROP_MESSAGE_EXPIRY_INTERVAL, MQTT_PROP_TYPE_INT}, + {MQTT_PROP_CONTENT_TYPE, MQTT_PROP_TYPE_STRING}, + {MQTT_PROP_RESPONSE_TOPIC, MQTT_PROP_TYPE_STRING}, + {MQTT_PROP_CORRELATION_DATA, MQTT_PROP_TYPE_BINARY_DATA}, + {MQTT_PROP_SUBSCRIPTION_IDENTIFIER, MQTT_PROP_TYPE_VARIABLE_INT}, + {MQTT_PROP_SESSION_EXPIRY_INTERVAL, MQTT_PROP_TYPE_INT}, + {MQTT_PROP_ASSIGNED_CLIENT_IDENTIFIER, MQTT_PROP_TYPE_STRING}, + {MQTT_PROP_SERVER_KEEP_ALIVE, MQTT_PROP_TYPE_SHORT}, + {MQTT_PROP_AUTHENTICATION_METHOD, MQTT_PROP_TYPE_STRING}, + {MQTT_PROP_AUTHENTICATION_DATA, MQTT_PROP_TYPE_BINARY_DATA}, + {MQTT_PROP_REQUEST_PROBLEM_INFORMATION, MQTT_PROP_TYPE_BYTE}, + {MQTT_PROP_WILL_DELAY_INTERVAL, MQTT_PROP_TYPE_INT}, + {MQTT_PROP_REQUEST_RESPONSE_INFORMATION, MQTT_PROP_TYPE_BYTE}, + {MQTT_PROP_RESPONSE_INFORMATION, MQTT_PROP_TYPE_STRING}, + {MQTT_PROP_SERVER_REFERENCE, MQTT_PROP_TYPE_STRING}, + {MQTT_PROP_REASON_STRING, MQTT_PROP_TYPE_STRING}, + {MQTT_PROP_RECEIVE_MAXIMUM, MQTT_PROP_TYPE_SHORT}, + {MQTT_PROP_TOPIC_ALIAS_MAXIMUM, MQTT_PROP_TYPE_SHORT}, + {MQTT_PROP_TOPIC_ALIAS, MQTT_PROP_TYPE_SHORT}, + {MQTT_PROP_MAXIMUM_QOS, MQTT_PROP_TYPE_BYTE}, + {MQTT_PROP_RETAIN_AVAILABLE, MQTT_PROP_TYPE_BYTE}, + {MQTT_PROP_USER_PROPERTY, MQTT_PROP_TYPE_STRING_PAIR}, + {MQTT_PROP_MAXIMUM_PACKET_SIZE, MQTT_PROP_TYPE_INT}, + {MQTT_PROP_WILDCARD_SUBSCRIPTION_AVAILABLE, MQTT_PROP_TYPE_BYTE}, + {MQTT_PROP_SUBSCRIPTION_IDENTIFIER_AVAILABLE, MQTT_PROP_TYPE_BYTE}, + {MQTT_PROP_SHARED_SUBSCRIPTION_AVAILABLE, MQTT_PROP_TYPE_BYTE}}; + +void mg_mqtt_send_header(struct mg_connection *c, uint8_t cmd, uint8_t flags, + uint32_t len) { + uint8_t buf[1 + sizeof(len)], *vlen = &buf[1]; + buf[0] = (uint8_t) ((cmd << 4) | flags); + do { + *vlen = len % 0x80; + len /= 0x80; + if (len > 0) *vlen |= 0x80; + vlen++; + } while (len > 0 && vlen < &buf[sizeof(buf)]); + mg_send(c, buf, (size_t) (vlen - buf)); } -size_t mg_iobuf_add(struct mg_iobuf *io, size_t ofs, const void *buf, - size_t len) { - size_t new_size = roundup(io->len + len, io->align); - mg_iobuf_resize(io, new_size); // Attempt to resize - if (new_size != io->size) len = 0; // Resize failure, append nothing - if (ofs < io->len) memmove(io->buf + ofs + len, io->buf + ofs, io->len - ofs); - if (buf != NULL) memmove(io->buf + ofs, buf, len); - if (ofs > io->len) io->len += ofs - io->len; - io->len += len; - return len; +static void mg_send_u16(struct mg_connection *c, uint16_t value) { + mg_send(c, &value, sizeof(value)); } -size_t mg_iobuf_del(struct mg_iobuf *io, size_t ofs, size_t len) { - if (ofs > io->len) ofs = io->len; - if (ofs + len > io->len) len = io->len - ofs; - if (io->buf) memmove(io->buf + ofs, io->buf + ofs + len, io->len - ofs - len); - if (io->buf) mg_bzero(io->buf + io->len - len, len); - io->len -= len; - return len; +static void mg_send_u32(struct mg_connection *c, uint32_t value) { + mg_send(c, &value, sizeof(value)); } -void mg_iobuf_free(struct mg_iobuf *io) { - mg_iobuf_resize(io, 0); +static uint8_t varint_size(size_t length) { + uint8_t bytes_needed = 0; + do { + bytes_needed++; + length /= 0x80; + } while (length > 0); + return bytes_needed; } -#ifdef MG_ENABLE_LINES -#line 1 "src/json.c" -#endif +static size_t encode_varint(uint8_t *buf, size_t value) { + size_t len = 0; + do { + uint8_t b = (uint8_t) (value % 128); + value /= 128; + if (value > 0) b |= 0x80; + buf[len++] = b; + } while (value > 0); + return len; +} +static size_t decode_varint(const uint8_t *buf, size_t len, size_t *value) { + size_t multiplier = 1, offset; + *value = 0; -static const char *escapeseq(int esc) { - return esc ? "\b\f\n\r\t\\\"" : "bfnrt\\\""; -} + for (offset = 0; offset < 4 && offset < len; offset++) { + uint8_t encoded_byte = buf[offset]; + *value += (encoded_byte & 0x7f) * multiplier; + multiplier *= 128; -static char json_esc(int c, int esc) { - const char *p, *esc1 = escapeseq(esc), *esc2 = escapeseq(!esc); - for (p = esc1; *p != '\0'; p++) { - if (*p == c) return esc2[p - esc1]; + if ((encoded_byte & 0x80) == 0) return offset + 1; } + return 0; } -static int mg_pass_string(const char *s, int len) { - int i; - for (i = 0; i < len; i++) { - if (s[i] == '\\' && i + 1 < len && json_esc(s[i + 1], 1)) { - i++; - } else if (s[i] == '\0') { - return MG_JSON_INVALID; - } else if (s[i] == '"') { - return i; - } +static int mqtt_prop_type_by_id(uint8_t prop_id) { + size_t i, num_properties = sizeof(s_prop_map) / sizeof(s_prop_map[0]); + for (i = 0; i < num_properties; ++i) { + if (s_prop_map[i].id == prop_id) return s_prop_map[i].type; } - return MG_JSON_INVALID; + return -1; // Property ID not found } -static double mg_atod(const char *p, int len, int *numlen) { - double d = 0.0; - int i = 0, sign = 1; - - // Sign - if (i < len && *p == '-') { - sign = -1, i++; - } else if (i < len && *p == '+') { - i++; - } - - // Decimal - for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) { - d *= 10.0; - d += p[i] - '0'; - } - d *= sign; - - // Fractional - if (i < len && p[i] == '.') { - double frac = 0.0, base = 0.1; - i++; - for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) { - frac += base * (p[i] - '0'); - base /= 10.0; +// Returns the size of the properties section, without the +// size of the content's length +static size_t get_properties_length(struct mg_mqtt_prop *props, size_t count) { + size_t i, size = 0; + for (i = 0; i < count; i++) { + size++; // identifier + switch (mqtt_prop_type_by_id(props[i].id)) { + case MQTT_PROP_TYPE_STRING_PAIR: + size += (uint32_t) (props[i].val.len + props[i].key.len + + 2 * sizeof(uint16_t)); + break; + case MQTT_PROP_TYPE_STRING: + size += (uint32_t) (props[i].val.len + sizeof(uint16_t)); + break; + case MQTT_PROP_TYPE_BINARY_DATA: + size += (uint32_t) (props[i].val.len + sizeof(uint16_t)); + break; + case MQTT_PROP_TYPE_VARIABLE_INT: + size += varint_size((uint32_t) props[i].iv); + break; + case MQTT_PROP_TYPE_INT: + size += (uint32_t) sizeof(uint32_t); + break; + case MQTT_PROP_TYPE_SHORT: + size += (uint32_t) sizeof(uint16_t); + break; + case MQTT_PROP_TYPE_BYTE: + size += (uint32_t) sizeof(uint8_t); + break; + default: + return size; // cannot parse further down } - d += frac * sign; - } - - // Exponential - if (i < len && (p[i] == 'e' || p[i] == 'E')) { - int j, exp = 0, minus = 0; - i++; - if (i < len && p[i] == '-') minus = 1, i++; - if (i < len && p[i] == '+') i++; - while (i < len && p[i] >= '0' && p[i] <= '9' && exp < 308) - exp = exp * 10 + (p[i++] - '0'); - if (minus) exp = -exp; - for (j = 0; j < exp; j++) d *= 10.0; - for (j = 0; j < -exp; j++) d /= 10.0; } - if (numlen != NULL) *numlen = i; - return d; + return size; } -// Iterate over object or array elements -size_t mg_json_next(struct mg_str obj, size_t ofs, struct mg_str *key, - struct mg_str *val) { - if (ofs >= obj.len) { - ofs = 0; // Out of boundaries, stop scanning - } else if (obj.len < 2 || (*obj.buf != '{' && *obj.buf != '[')) { - ofs = 0; // Not an array or object, stop - } else { - struct mg_str sub = mg_str_n(obj.buf + ofs, obj.len - ofs); - if (ofs == 0) ofs++, sub.buf++, sub.len--; - if (*obj.buf == '[') { // Iterate over an array - int n = 0, o = mg_json_get(sub, "$", &n); - if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) { - ofs = 0; // Error parsing key, stop scanning - } else { - if (key) *key = mg_str_n(NULL, 0); - if (val) *val = mg_str_n(sub.buf + o, (size_t) n); - ofs = (size_t) (&sub.buf[o + n] - obj.buf); - } - } else { // Iterate over an object - int n = 0, o = mg_json_get(sub, "$", &n); - if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) { - ofs = 0; // Error parsing key, stop scanning - } else { - if (key) *key = mg_str_n(sub.buf + o, (size_t) n); - sub.buf += o + n, sub.len -= (size_t) (o + n); - while (sub.len > 0 && *sub.buf != ':') sub.len--, sub.buf++; - if (sub.len > 0 && *sub.buf == ':') sub.len--, sub.buf++; - n = 0, o = mg_json_get(sub, "$", &n); - if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) { - ofs = 0; // Error parsing value, stop scanning - } else { - if (val) *val = mg_str_n(sub.buf + o, (size_t) n); - ofs = (size_t) (&sub.buf[o + n] - obj.buf); - } - } - } - // MG_INFO(("SUB ofs %u %.*s", ofs, sub.len, sub.buf)); - while (ofs && ofs < obj.len && - (obj.buf[ofs] == ' ' || obj.buf[ofs] == '\t' || - obj.buf[ofs] == '\n' || obj.buf[ofs] == '\r')) { - ofs++; - } - if (ofs && ofs < obj.len && obj.buf[ofs] == ',') ofs++; - if (ofs > obj.len) ofs = 0; - } - return ofs; +// returns the entire size of the properties section, including the +// size of the variable length of the content +static size_t get_props_size(struct mg_mqtt_prop *props, size_t count) { + size_t size = get_properties_length(props, count); + size += varint_size(size); + return size; } -int mg_json_get(struct mg_str json, const char *path, int *toklen) { - const char *s = json.buf; - int len = (int) json.len; - enum { S_VALUE, S_KEY, S_COLON, S_COMMA_OR_EOO } expecting = S_VALUE; - unsigned char nesting[MG_JSON_MAX_DEPTH]; - int i = 0; // Current offset in `s` - int j = 0; // Offset in `s` we're looking for (return value) - int depth = 0; // Current depth (nesting level) - int ed = 0; // Expected depth - int pos = 1; // Current position in `path` - int ci = -1, ei = -1; // Current and expected index in array - - if (toklen) *toklen = 0; - if (path[0] != '$') return MG_JSON_INVALID; - -#define MG_CHECKRET(x) \ - do { \ - if (depth == ed && path[pos] == '\0' && ci == ei) { \ - if (toklen) *toklen = i - j + 1; \ - return j; \ - } \ - } while (0) - -// In the ascii table, the distance between `[` and `]` is 2. -// Ditto for `{` and `}`. Hence +2 in the code below. -#define MG_EOO(x) \ - do { \ - if (depth == ed && ci != ei) return MG_JSON_NOT_FOUND; \ - if (c != nesting[depth - 1] + 2) return MG_JSON_INVALID; \ - depth--; \ - MG_CHECKRET(x); \ - } while (0) +static void mg_send_mqtt_properties(struct mg_connection *c, + struct mg_mqtt_prop *props, size_t nprops) { + size_t total_size = get_properties_length(props, nprops); + uint8_t buf_v[4] = {0, 0, 0, 0}; + uint8_t buf[4] = {0, 0, 0, 0}; + size_t i, len = encode_varint(buf, total_size); - for (i = 0; i < len; i++) { - unsigned char c = ((unsigned char *) s)[i]; - if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue; - switch (expecting) { - case S_VALUE: - // p("V %s [%.*s] %d %d %d %d\n", path, pos, path, depth, ed, ci, ei); - if (depth == ed) j = i; - if (c == '{') { - if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP; - if (depth == ed && path[pos] == '.' && ci == ei) { - // If we start the object, reset array indices - ed++, pos++, ci = ei = -1; - } - nesting[depth++] = c; - expecting = S_KEY; - break; - } else if (c == '[') { - if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP; - if (depth == ed && path[pos] == '[' && ei == ci) { - ed++, pos++, ci = 0; - for (ei = 0; path[pos] != ']' && path[pos] != '\0'; pos++) { - ei *= 10; - ei += path[pos] - '0'; - } - if (path[pos] != 0) pos++; - } - nesting[depth++] = c; - break; - } else if (c == ']' && depth > 0) { // Empty array - MG_EOO(']'); - } else if (c == 't' && i + 3 < len && memcmp(&s[i], "true", 4) == 0) { - i += 3; - } else if (c == 'n' && i + 3 < len && memcmp(&s[i], "null", 4) == 0) { - i += 3; - } else if (c == 'f' && i + 4 < len && memcmp(&s[i], "false", 5) == 0) { - i += 4; - } else if (c == '-' || ((c >= '0' && c <= '9'))) { - int numlen = 0; - mg_atod(&s[i], len - i, &numlen); - i += numlen - 1; - } else if (c == '"') { - int n = mg_pass_string(&s[i + 1], len - i - 1); - if (n < 0) return n; - i += n + 1; - } else { - return MG_JSON_INVALID; - } - MG_CHECKRET('V'); - if (depth == ed && ei >= 0) ci++; - expecting = S_COMMA_OR_EOO; + mg_send(c, buf, (size_t) len); + for (i = 0; i < nprops; i++) { + mg_send(c, &props[i].id, sizeof(props[i].id)); + switch (mqtt_prop_type_by_id(props[i].id)) { + case MQTT_PROP_TYPE_STRING_PAIR: + mg_send_u16(c, mg_htons((uint16_t) props[i].key.len)); + mg_send(c, props[i].key.buf, props[i].key.len); + mg_send_u16(c, mg_htons((uint16_t) props[i].val.len)); + mg_send(c, props[i].val.buf, props[i].val.len); break; - - case S_KEY: - if (c == '"') { - int n = mg_pass_string(&s[i + 1], len - i - 1); - if (n < 0) return n; - if (i + 1 + n >= len) return MG_JSON_NOT_FOUND; - if (depth < ed) return MG_JSON_NOT_FOUND; - if (depth == ed && path[pos - 1] != '.') return MG_JSON_NOT_FOUND; - // printf("K %s [%.*s] [%.*s] %d %d %d %d %d\n", path, pos, path, n, - // &s[i + 1], n, depth, ed, ci, ei); - // NOTE(cpq): in the check sequence below is important. - // strncmp() must go first: it fails fast if the remaining length - // of the path is smaller than `n`. - if (depth == ed && path[pos - 1] == '.' && - strncmp(&s[i + 1], &path[pos], (size_t) n) == 0 && - (path[pos + n] == '\0' || path[pos + n] == '.' || - path[pos + n] == '[')) { - pos += n; - } - i += n + 1; - expecting = S_COLON; - } else if (c == '}') { // Empty object - MG_EOO('}'); - expecting = S_COMMA_OR_EOO; - if (depth == ed && ei >= 0) ci++; - } else { - return MG_JSON_INVALID; - } + case MQTT_PROP_TYPE_BYTE: + mg_send(c, &props[i].iv, sizeof(uint8_t)); break; - - case S_COLON: - if (c == ':') { - expecting = S_VALUE; - } else { - return MG_JSON_INVALID; - } + case MQTT_PROP_TYPE_SHORT: + mg_send_u16(c, mg_htons((uint16_t) props[i].iv)); break; - - case S_COMMA_OR_EOO: - if (depth <= 0) { - return MG_JSON_INVALID; - } else if (c == ',') { - expecting = (nesting[depth - 1] == '{') ? S_KEY : S_VALUE; - } else if (c == ']' || c == '}') { - if (depth == ed && c == '}' && path[pos - 1] == '.') - return MG_JSON_NOT_FOUND; - if (depth == ed && c == ']' && path[pos - 1] == ',') - return MG_JSON_NOT_FOUND; - MG_EOO('O'); - if (depth == ed && ei >= 0) ci++; - } else { - return MG_JSON_INVALID; - } + case MQTT_PROP_TYPE_INT: + mg_send_u32(c, mg_htonl((uint32_t) props[i].iv)); + break; + case MQTT_PROP_TYPE_STRING: + mg_send_u16(c, mg_htons((uint16_t) props[i].val.len)); + mg_send(c, props[i].val.buf, props[i].val.len); + break; + case MQTT_PROP_TYPE_BINARY_DATA: + mg_send_u16(c, mg_htons((uint16_t) props[i].val.len)); + mg_send(c, props[i].val.buf, props[i].val.len); + break; + case MQTT_PROP_TYPE_VARIABLE_INT: + len = encode_varint(buf_v, props[i].iv); + mg_send(c, buf_v, (size_t) len); break; } } - return MG_JSON_NOT_FOUND; } -struct mg_str mg_json_get_tok(struct mg_str json, const char *path) { - int len = 0, ofs = mg_json_get(json, path, &len); - return mg_str_n(ofs < 0 ? NULL : json.buf + ofs, - (size_t) (len < 0 ? 0 : len)); -} +size_t mg_mqtt_next_prop(struct mg_mqtt_message *msg, struct mg_mqtt_prop *prop, + size_t ofs) { + uint8_t *i = (uint8_t *) msg->dgram.buf + msg->props_start + ofs; + uint8_t *end = (uint8_t *) msg->dgram.buf + msg->dgram.len; + size_t new_pos = ofs, len; + prop->id = i[0]; -bool mg_json_get_num(struct mg_str json, const char *path, double *v) { - int n, toklen, found = 0; - if ((n = mg_json_get(json, path, &toklen)) >= 0 && - (json.buf[n] == '-' || (json.buf[n] >= '0' && json.buf[n] <= '9'))) { - if (v != NULL) *v = mg_atod(json.buf + n, toklen, NULL); - found = 1; + if (ofs >= msg->dgram.len || ofs >= msg->props_start + msg->props_size) + return 0; + i++, new_pos++; + + switch (mqtt_prop_type_by_id(prop->id)) { + case MQTT_PROP_TYPE_STRING_PAIR: + prop->key.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]); + prop->key.buf = (char *) i + 2; + i += 2 + prop->key.len; + prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]); + prop->val.buf = (char *) i + 2; + new_pos += 2 * sizeof(uint16_t) + prop->val.len + prop->key.len; + break; + case MQTT_PROP_TYPE_BYTE: + prop->iv = (uint8_t) i[0]; + new_pos++; + break; + case MQTT_PROP_TYPE_SHORT: + prop->iv = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]); + new_pos += sizeof(uint16_t); + break; + case MQTT_PROP_TYPE_INT: + prop->iv = ((uint32_t) i[0] << 24) | ((uint32_t) i[1] << 16) | + ((uint32_t) i[2] << 8) | i[3]; + new_pos += sizeof(uint32_t); + break; + case MQTT_PROP_TYPE_STRING: + prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]); + prop->val.buf = (char *) i + 2; + new_pos += 2 + prop->val.len; + break; + case MQTT_PROP_TYPE_BINARY_DATA: + prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]); + prop->val.buf = (char *) i + 2; + new_pos += 2 + prop->val.len; + break; + case MQTT_PROP_TYPE_VARIABLE_INT: + len = decode_varint(i, (size_t) (end - i), (size_t *) &prop->iv); + new_pos = (!len) ? 0 : new_pos + len; + break; + default: + new_pos = 0; } - return found; + + return new_pos; } -bool mg_json_get_bool(struct mg_str json, const char *path, bool *v) { - int found = 0, off = mg_json_get(json, path, NULL); - if (off >= 0 && (json.buf[off] == 't' || json.buf[off] == 'f')) { - if (v != NULL) *v = json.buf[off] == 't'; - found = 1; +void mg_mqtt_login(struct mg_connection *c, const struct mg_mqtt_opts *opts) { + char client_id[21]; + struct mg_str cid = opts->client_id; + size_t total_len = 7 + 1 + 2 + 2; + uint8_t hdr[8] = {0, 4, 'M', 'Q', 'T', 'T', opts->version, 0}; + + if (cid.len == 0) { + mg_random_str(client_id, sizeof(client_id) - 1); + client_id[sizeof(client_id) - 1] = '\0'; + cid = mg_str(client_id); + } + + if (hdr[6] == 0) hdr[6] = 4; // If version is not set, use 4 (3.1.1) + c->is_mqtt5 = hdr[6] == 5; // Set version 5 flag + hdr[7] = (uint8_t) ((opts->qos & 3) << 3); // Connection flags + if (opts->user.len > 0) { + total_len += 2 + (uint32_t) opts->user.len; + hdr[7] |= MQTT_HAS_USER_NAME; + } + if (opts->pass.len > 0) { + total_len += 2 + (uint32_t) opts->pass.len; + hdr[7] |= MQTT_HAS_PASSWORD; + } + if (opts->topic.len > 0) { // allow zero-length msgs, message.len is size_t + total_len += 4 + (uint32_t) opts->topic.len + (uint32_t) opts->message.len; + hdr[7] |= MQTT_HAS_WILL; + } + if (opts->clean || cid.len == 0) hdr[7] |= MQTT_CLEAN_SESSION; + if (opts->retain) hdr[7] |= MQTT_WILL_RETAIN; + total_len += (uint32_t) cid.len; + if (c->is_mqtt5) { + total_len += get_props_size(opts->props, opts->num_props); + if (hdr[7] & MQTT_HAS_WILL) + total_len += get_props_size(opts->will_props, opts->num_will_props); + } + + mg_mqtt_send_header(c, MQTT_CMD_CONNECT, 0, (uint32_t) total_len); + mg_send(c, hdr, sizeof(hdr)); + // keepalive == 0 means "do not disconnect us!" + mg_send_u16(c, mg_htons((uint16_t) opts->keepalive)); + + if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props, opts->num_props); + + mg_send_u16(c, mg_htons((uint16_t) cid.len)); + mg_send(c, cid.buf, cid.len); + + if (hdr[7] & MQTT_HAS_WILL) { + if (c->is_mqtt5) + mg_send_mqtt_properties(c, opts->will_props, opts->num_will_props); + + mg_send_u16(c, mg_htons((uint16_t) opts->topic.len)); + mg_send(c, opts->topic.buf, opts->topic.len); + mg_send_u16(c, mg_htons((uint16_t) opts->message.len)); + mg_send(c, opts->message.buf, opts->message.len); } - return found; -} - -bool mg_json_unescape(struct mg_str s, char *to, size_t n) { - size_t i, j; - for (i = 0, j = 0; i < s.len && j < n; i++, j++) { - if (s.buf[i] == '\\' && i + 5 < s.len && s.buf[i + 1] == 'u') { - // \uXXXX escape. We process simple one-byte chars \u00xx within ASCII - // range. More complex chars would require dragging in a UTF8 library, - // which is too much for us - if (mg_str_to_num(mg_str_n(s.buf + i + 2, 4), 16, &to[j], - sizeof(uint8_t)) == false) - return false; - i += 5; - } else if (s.buf[i] == '\\' && i + 1 < s.len) { - char c = json_esc(s.buf[i + 1], 0); - if (c == 0) return false; - to[j] = c; - i++; - } else { - to[j] = s.buf[i]; - } + if (opts->user.len > 0) { + mg_send_u16(c, mg_htons((uint16_t) opts->user.len)); + mg_send(c, opts->user.buf, opts->user.len); } - if (j >= n) return false; - if (n > 0) to[j] = '\0'; - return true; -} - -char *mg_json_get_str(struct mg_str json, const char *path) { - char *result = NULL; - int len = 0, off = mg_json_get(json, path, &len); - if (off >= 0 && len > 1 && json.buf[off] == '"') { - if ((result = (char *) calloc(1, (size_t) len)) != NULL && - !mg_json_unescape(mg_str_n(json.buf + off + 1, (size_t) (len - 2)), - result, (size_t) len)) { - free(result); - result = NULL; - } + if (opts->pass.len > 0) { + mg_send_u16(c, mg_htons((uint16_t) opts->pass.len)); + mg_send(c, opts->pass.buf, opts->pass.len); } - return result; } -char *mg_json_get_b64(struct mg_str json, const char *path, int *slen) { - char *result = NULL; - int len = 0, off = mg_json_get(json, path, &len); - if (off >= 0 && json.buf[off] == '"' && len > 1 && - (result = (char *) calloc(1, (size_t) len)) != NULL) { - size_t k = mg_base64_decode(json.buf + off + 1, (size_t) (len - 2), result, - (size_t) len); - if (slen != NULL) *slen = (int) k; - } - return result; -} +uint16_t mg_mqtt_pub(struct mg_connection *c, const struct mg_mqtt_opts *opts) { + uint16_t id = opts->retransmit_id; + uint8_t flags = (uint8_t) (((opts->qos & 3) << 1) | (opts->retain ? 1 : 0)); + size_t len = 2 + opts->topic.len + opts->message.len; + MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) opts->topic.len, + (char *) opts->topic.buf, (int) opts->message.len, + (char *) opts->message.buf)); + if (opts->qos > 0) len += 2; + if (c->is_mqtt5) len += get_props_size(opts->props, opts->num_props); -char *mg_json_get_hex(struct mg_str json, const char *path, int *slen) { - char *result = NULL; - int len = 0, off = mg_json_get(json, path, &len); - if (off >= 0 && json.buf[off] == '"' && len > 1 && - (result = (char *) calloc(1, (size_t) len / 2)) != NULL) { - int i; - for (i = 0; i < len - 2; i += 2) { - mg_str_to_num(mg_str_n(json.buf + off + 1 + i, 2), 16, &result[i >> 1], - sizeof(uint8_t)); + if (opts->qos > 0 && id != 0) flags |= 1 << 3; + mg_mqtt_send_header(c, MQTT_CMD_PUBLISH, flags, (uint32_t) len); + mg_send_u16(c, mg_htons((uint16_t) opts->topic.len)); + mg_send(c, opts->topic.buf, opts->topic.len); + if (opts->qos > 0) { // need to send 'id' field + if (id == 0) { // generate new one if not resending + if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id; + id = c->mgr->mqtt_id; } - result[len / 2 - 1] = '\0'; - if (slen != NULL) *slen = len / 2 - 1; + mg_send_u16(c, mg_htons(id)); } - return result; + + if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props, opts->num_props); + + if (opts->message.len > 0) mg_send(c, opts->message.buf, opts->message.len); + return id; } -long mg_json_get_long(struct mg_str json, const char *path, long dflt) { - double dv; - long result = dflt; - if (mg_json_get_num(json, path, &dv)) result = (long) dv; - return result; +void mg_mqtt_sub(struct mg_connection *c, const struct mg_mqtt_opts *opts) { + uint8_t qos_ = opts->qos & 3; + size_t plen = c->is_mqtt5 ? get_props_size(opts->props, opts->num_props) : 0; + size_t len = 2 + opts->topic.len + 2 + 1 + plen; + + mg_mqtt_send_header(c, MQTT_CMD_SUBSCRIBE, 2, (uint32_t) len); + if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id; + mg_send_u16(c, mg_htons(c->mgr->mqtt_id)); + if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props, opts->num_props); + + mg_send_u16(c, mg_htons((uint16_t) opts->topic.len)); + mg_send(c, opts->topic.buf, opts->topic.len); + mg_send(c, &qos_, sizeof(qos_)); } -#ifdef MG_ENABLE_LINES -#line 1 "src/log.c" -#endif +int mg_mqtt_parse(const uint8_t *buf, size_t len, uint8_t version, + struct mg_mqtt_message *m) { + uint8_t lc = 0, *p, *end; + uint32_t n = 0, len_len = 0; + memset(m, 0, sizeof(*m)); + m->dgram.buf = (char *) buf; + if (len < 2) return MQTT_INCOMPLETE; + m->cmd = (uint8_t) (buf[0] >> 4); + m->qos = (buf[0] >> 1) & 3; + n = len_len = 0; + p = (uint8_t *) buf + 1; + while ((size_t) (p - buf) < len) { + lc = *((uint8_t *) p++); + n += (uint32_t) ((lc & 0x7f) << 7 * len_len); + len_len++; + if (!(lc & 0x80)) break; + if (len_len >= 4) return MQTT_MALFORMED; + } + end = p + n; + if ((lc & 0x80) || (end > buf + len)) return MQTT_INCOMPLETE; + m->dgram.len = (size_t) (end - buf); + switch (m->cmd) { + case MQTT_CMD_CONNACK: + if (end - p < 2) return MQTT_MALFORMED; + m->ack = p[1]; + break; + case MQTT_CMD_PUBACK: + case MQTT_CMD_PUBREC: + case MQTT_CMD_PUBREL: + case MQTT_CMD_PUBCOMP: + case MQTT_CMD_SUBSCRIBE: + case MQTT_CMD_SUBACK: + case MQTT_CMD_UNSUBSCRIBE: + case MQTT_CMD_UNSUBACK: + if (p + 2 > end) return MQTT_MALFORMED; + m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]); + p += 2; + break; + case MQTT_CMD_PUBLISH: { + if (p + 2 > end) return MQTT_MALFORMED; + m->topic.len = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]); + m->topic.buf = (char *) p + 2; + p += 2 + m->topic.len; + if (p > end) return MQTT_MALFORMED; + if (m->qos > 0) { + if (p + 2 > end) return MQTT_MALFORMED; + m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]); + p += 2; + } + if (p > end) return MQTT_MALFORMED; + if (version == 5 && p + 2 < end) { + len_len = + (uint32_t) decode_varint(p, (size_t) (end - p), &m->props_size); + if (!len_len) return MQTT_MALFORMED; + m->props_start = (size_t) (p + len_len - buf); + p += len_len + m->props_size; + } + if (p > end) return MQTT_MALFORMED; + m->data.buf = (char *) p; + m->data.len = (size_t) (end - p); + break; + } + default: + break; + } + return MQTT_OK; +} +static void mqtt_cb(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_READ) { + for (;;) { + uint8_t version = c->is_mqtt5 ? 5 : 4; + struct mg_mqtt_message mm; + int rc = mg_mqtt_parse(c->recv.buf, c->recv.len, version, &mm); + if (rc == MQTT_MALFORMED) { + MG_ERROR(("%lu MQTT malformed message", c->id)); + c->is_closing = 1; + break; + } else if (rc == MQTT_OK) { + MG_VERBOSE(("%lu MQTT CMD %d len %d [%.*s]", c->id, mm.cmd, + (int) mm.dgram.len, (int) mm.data.len, mm.data.buf)); + switch (mm.cmd) { + case MQTT_CMD_CONNACK: + mg_call(c, MG_EV_MQTT_OPEN, &mm.ack); + if (mm.ack == 0) { + MG_DEBUG(("%lu Connected", c->id)); + } else { + MG_ERROR(("%lu MQTT auth failed, code %d", c->id, mm.ack)); + c->is_closing = 1; + } + break; + case MQTT_CMD_PUBLISH: { + /*MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) mm.topic.len, + mm.topic.buf, (int) mm.data.len, mm.data.buf));*/ + if (mm.qos > 0) { + uint16_t id = mg_ntohs(mm.id); + uint32_t remaining_len = sizeof(id); + if (c->is_mqtt5) remaining_len += 2; // 3.4.2 -int mg_log_level = MG_LL_INFO; -static mg_pfn_t s_log_func = mg_pfn_stdout; -static void *s_log_func_param = NULL; + mg_mqtt_send_header( + c, + (uint8_t) (mm.qos == 2 ? MQTT_CMD_PUBREC : MQTT_CMD_PUBACK), + 0, remaining_len); + mg_send(c, &id, sizeof(id)); -void mg_log_set_fn(mg_pfn_t fn, void *param) { - s_log_func = fn; - s_log_func_param = param; + if (c->is_mqtt5) { + uint16_t zero = 0; + mg_send(c, &zero, sizeof(zero)); + } + } + mg_call(c, MG_EV_MQTT_MSG, &mm); // let the app handle qos stuff + break; + } + case MQTT_CMD_PUBREC: { // MQTT5: 3.5.2-1 TODO(): variable header rc + uint16_t id = mg_ntohs(mm.id); + uint32_t remaining_len = sizeof(id); // MQTT5 3.6.2-1 + mg_mqtt_send_header(c, MQTT_CMD_PUBREL, 2, remaining_len); + mg_send(c, &id, sizeof(id)); // MQTT5 3.6.1-1, flags = 2 + break; + } + case MQTT_CMD_PUBREL: { // MQTT5: 3.6.2-1 TODO(): variable header rc + uint16_t id = mg_ntohs(mm.id); + uint32_t remaining_len = sizeof(id); // MQTT5 3.7.2-1 + mg_mqtt_send_header(c, MQTT_CMD_PUBCOMP, 0, remaining_len); + mg_send(c, &id, sizeof(id)); + break; + } + } + mg_call(c, MG_EV_MQTT_CMD, &mm); + mg_iobuf_del(&c->recv, 0, mm.dgram.len); + } else { + break; + } + } + } + (void) ev_data; } -static void logc(unsigned char c) { - s_log_func((char) c, s_log_func_param); +void mg_mqtt_ping(struct mg_connection *nc) { + mg_mqtt_send_header(nc, MQTT_CMD_PINGREQ, 0, 0); } -static void logs(const char *buf, size_t len) { - size_t i; - for (i = 0; i < len; i++) logc(((unsigned char *) buf)[i]); +void mg_mqtt_pong(struct mg_connection *nc) { + mg_mqtt_send_header(nc, MQTT_CMD_PINGRESP, 0, 0); } -#if MG_ENABLE_CUSTOM_LOG -// Let user define their own mg_log_prefix() and mg_log() -#else -void mg_log_prefix(int level, const char *file, int line, const char *fname) { - const char *p = strrchr(file, '/'); - char buf[41]; - size_t n; - if (p == NULL) p = strrchr(file, '\\'); - n = mg_snprintf(buf, sizeof(buf), "%-6llx %d %s:%d:%s", mg_millis(), level, - p == NULL ? file : p + 1, line, fname); - if (n > sizeof(buf) - 2) n = sizeof(buf) - 2; - while (n < sizeof(buf)) buf[n++] = ' '; - logs(buf, n - 1); -} +void mg_mqtt_disconnect(struct mg_connection *c, + const struct mg_mqtt_opts *opts) { + size_t len = 0; + if (c->is_mqtt5) len = 1 + get_props_size(opts->props, opts->num_props); + mg_mqtt_send_header(c, MQTT_CMD_DISCONNECT, 0, (uint32_t) len); -void mg_log(const char *fmt, ...) { - va_list ap; - va_start(ap, fmt); - mg_vxprintf(s_log_func, s_log_func_param, fmt, &ap); - va_end(ap); - logs("\r\n", 2); + if (c->is_mqtt5) { + uint8_t zero = 0; + mg_send(c, &zero, sizeof(zero)); // reason code + mg_send_mqtt_properties(c, opts->props, opts->num_props); + } } -#endif -static unsigned char nibble(unsigned c) { - return (unsigned char) (c < 10 ? c + '0' : c + 'W'); +struct mg_connection *mg_mqtt_connect(struct mg_mgr *mgr, const char *url, + const struct mg_mqtt_opts *opts, + mg_event_handler_t fn, void *fn_data) { + struct mg_connection *c = mg_connect(mgr, url, fn, fn_data); + if (c != NULL) { + struct mg_mqtt_opts empty; + memset(&empty, 0, sizeof(empty)); + mg_mqtt_login(c, opts == NULL ? &empty : opts); + c->pfn = mqtt_cb; + } + return c; } -#define ISPRINT(x) ((x) >= ' ' && (x) <= '~') -void mg_hexdump(const void *buf, size_t len) { - const unsigned char *p = (const unsigned char *) buf; - unsigned char ascii[16], alen = 0; - size_t i; - for (i = 0; i < len; i++) { - if ((i % 16) == 0) { - // Print buffered ascii chars - if (i > 0) logs(" ", 2), logs((char *) ascii, 16), logc('\n'), alen = 0; - // Print hex address, then \t - logc(nibble((i >> 12) & 15)), logc(nibble((i >> 8) & 15)), - logc(nibble((i >> 4) & 15)), logc('0'), logs(" ", 3); - } - logc(nibble(p[i] >> 4)), logc(nibble(p[i] & 15)); // Two nibbles, e.g. c5 - logc(' '); // Space after hex number - ascii[alen++] = ISPRINT(p[i]) ? p[i] : '.'; // Add to the ascii buf - } - while (alen < 16) logs(" ", 3), ascii[alen++] = ' '; - logs(" ", 2), logs((char *) ascii, 16), logc('\n'); +struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data) { + struct mg_connection *c = mg_listen(mgr, url, fn, fn_data); + if (c != NULL) c->pfn = mqtt_cb, c->pfn_data = mgr; + return c; } #ifdef MG_ENABLE_LINES -#line 1 "src/md5.c" +#line 1 "src/net.c" #endif -// This code implements the MD5 message-digest algorithm. -// The algorithm is due to Ron Rivest. This code was -// written by Colin Plumb in 1993, no copyright is claimed. -// This code is in the public domain; do with it what you wish. -// -// Equivalent code is available from RSA Data Security, Inc. -// This code has been tested against that, and is equivalent, -// except that you don't need to include two pages of legalese -// with every copy. -// -// To compute the message digest of a chunk of bytes, declare an -// MD5Context structure, pass it to MD5Init, call MD5Update as -// needed on buffers full of bytes, and then call MD5Final, which -// will fill a supplied 16-byte array with the digest. -#if defined(MG_ENABLE_MD5) && MG_ENABLE_MD5 -static void mg_byte_reverse(unsigned char *buf, unsigned longs) { - if (MG_BIG_ENDIAN) { - do { - uint32_t t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 | - ((unsigned) buf[1] << 8 | buf[0]); - *(uint32_t *) buf = t; - buf += 4; - } while (--longs); - } else { - (void) buf, (void) longs; // Little endian. Do nothing - } -} -#define F1(x, y, z) (z ^ (x & (y ^ z))) -#define F2(x, y, z) F1(z, x, y) -#define F3(x, y, z) (x ^ y ^ z) -#define F4(x, y, z) (y ^ (x | ~z)) -#define MD5STEP(f, w, x, y, z, data, s) \ - (w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x) -/* - * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious - * initialization constants. - */ -void mg_md5_init(mg_md5_ctx *ctx) { - ctx->buf[0] = 0x67452301; - ctx->buf[1] = 0xefcdab89; - ctx->buf[2] = 0x98badcfe; - ctx->buf[3] = 0x10325476; - ctx->bits[0] = 0; - ctx->bits[1] = 0; +size_t mg_vprintf(struct mg_connection *c, const char *fmt, va_list *ap) { + size_t old = c->send.len; + mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap); + return c->send.len - old; } -static void mg_md5_transform(uint32_t buf[4], uint32_t const in[16]) { - uint32_t a, b, c, d; - - a = buf[0]; - b = buf[1]; - c = buf[2]; - d = buf[3]; - - MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); - MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); - MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); - MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); - MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); - MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); - MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); - MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); - MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); - MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); - MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); - MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); - MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); - MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); - MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); - MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); - - MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); - MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); - MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); - MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); - MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); - MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); - MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); - MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); - MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); - MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); - MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); - MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); - MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); - MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); - MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); - MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); - - MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); - MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); - MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); - MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); - MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); - MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); - MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); - MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); - MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); - MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); - MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); - MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); - MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); - MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); - MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); - MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); - - MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); - MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); - MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); - MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); - MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); - MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); - MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); - MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); - MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); - MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); - MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); - MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); - MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); - MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); - MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); - MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); - - buf[0] += a; - buf[1] += b; - buf[2] += c; - buf[3] += d; +size_t mg_printf(struct mg_connection *c, const char *fmt, ...) { + size_t len = 0; + va_list ap; + va_start(ap, fmt); + len = mg_vprintf(c, fmt, &ap); + va_end(ap); + return len; } -void mg_md5_update(mg_md5_ctx *ctx, const unsigned char *buf, size_t len) { - uint32_t t; - - t = ctx->bits[0]; - if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t) ctx->bits[1]++; - ctx->bits[1] += (uint32_t) len >> 29; - - t = (t >> 3) & 0x3f; +static bool mg_atonl(struct mg_str str, struct mg_addr *addr) { + uint32_t localhost = mg_htonl(0x7f000001); + if (mg_strcasecmp(str, mg_str("localhost")) != 0) return false; + memcpy(addr->ip, &localhost, sizeof(uint32_t)); + addr->is_ip6 = false; + return true; +} - if (t) { - unsigned char *p = (unsigned char *) ctx->in + t; +static bool mg_atone(struct mg_str str, struct mg_addr *addr) { + if (str.len > 0) return false; + memset(addr->ip, 0, sizeof(addr->ip)); + addr->is_ip6 = false; + return true; +} - t = 64 - t; - if (len < t) { - memcpy(p, buf, len); - return; +static bool mg_aton4(struct mg_str str, struct mg_addr *addr) { + uint8_t data[4] = {0, 0, 0, 0}; + size_t i, num_dots = 0; + for (i = 0; i < str.len; i++) { + if (str.buf[i] >= '0' && str.buf[i] <= '9') { + int octet = data[num_dots] * 10 + (str.buf[i] - '0'); + if (octet > 255) return false; + data[num_dots] = (uint8_t) octet; + } else if (str.buf[i] == '.') { + if (num_dots >= 3 || i == 0 || str.buf[i - 1] == '.') return false; + num_dots++; + } else { + return false; } - memcpy(p, buf, t); - mg_byte_reverse(ctx->in, 16); - mg_md5_transform(ctx->buf, (uint32_t *) ctx->in); - buf += t; - len -= t; } + if (num_dots != 3 || str.buf[i - 1] == '.') return false; + memcpy(&addr->ip, data, sizeof(data)); + addr->is_ip6 = false; + return true; +} - while (len >= 64) { - memcpy(ctx->in, buf, 64); - mg_byte_reverse(ctx->in, 16); - mg_md5_transform(ctx->buf, (uint32_t *) ctx->in); - buf += 64; - len -= 64; +static bool mg_v4mapped(struct mg_str str, struct mg_addr *addr) { + int i; + uint32_t ipv4; + if (str.len < 14) return false; + if (str.buf[0] != ':' || str.buf[1] != ':' || str.buf[6] != ':') return false; + for (i = 2; i < 6; i++) { + if (str.buf[i] != 'f' && str.buf[i] != 'F') return false; } - - memcpy(ctx->in, buf, len); + // struct mg_str s = mg_str_n(&str.buf[7], str.len - 7); + if (!mg_aton4(mg_str_n(&str.buf[7], str.len - 7), addr)) return false; + memcpy(&ipv4, addr->ip, sizeof(ipv4)); + memset(addr->ip, 0, sizeof(addr->ip)); + addr->ip[10] = addr->ip[11] = 255; + memcpy(&addr->ip[12], &ipv4, 4); + addr->is_ip6 = true; + return true; } -void mg_md5_final(mg_md5_ctx *ctx, unsigned char digest[16]) { - unsigned count; - unsigned char *p; - uint32_t *a; - - count = (ctx->bits[0] >> 3) & 0x3F; - - p = ctx->in + count; - *p++ = 0x80; - count = 64 - 1 - count; - if (count < 8) { - memset(p, 0, count); - mg_byte_reverse(ctx->in, 16); - mg_md5_transform(ctx->buf, (uint32_t *) ctx->in); - memset(ctx->in, 0, 56); - } else { - memset(p, 0, count - 8); +static bool mg_aton6(struct mg_str str, struct mg_addr *addr) { + size_t i, j = 0, n = 0, dc = 42; + addr->scope_id = 0; + if (str.len > 2 && str.buf[0] == '[') str.buf++, str.len -= 2; + if (mg_v4mapped(str, addr)) return true; + for (i = 0; i < str.len; i++) { + if ((str.buf[i] >= '0' && str.buf[i] <= '9') || + (str.buf[i] >= 'a' && str.buf[i] <= 'f') || + (str.buf[i] >= 'A' && str.buf[i] <= 'F')) { + unsigned long val = 0; // TODO(): This loops on chars, refactor + if (i > j + 3) return false; + // MG_DEBUG(("%lu %lu [%.*s]", i, j, (int) (i - j + 1), &str.buf[j])); + mg_str_to_num(mg_str_n(&str.buf[j], i - j + 1), 16, &val, sizeof(val)); + addr->ip[n] = (uint8_t) ((val >> 8) & 255); + addr->ip[n + 1] = (uint8_t) (val & 255); + } else if (str.buf[i] == ':') { + j = i + 1; + if (i > 0 && str.buf[i - 1] == ':') { + dc = n; // Double colon + if (i > 1 && str.buf[i - 2] == ':') return false; + } else if (i > 0) { + n += 2; + } + if (n > 14) return false; + addr->ip[n] = addr->ip[n + 1] = 0; // For trailing :: + } else if (str.buf[i] == '%') { // Scope ID, last in string + return mg_str_to_num(mg_str_n(&str.buf[i + 1], str.len - i - 1), 10, + &addr->scope_id, sizeof(uint8_t)); + } else { + return false; + } + } + if (n < 14 && dc == 42) return false; + if (n < 14) { + memmove(&addr->ip[dc + (14 - n)], &addr->ip[dc], n - dc + 2); + memset(&addr->ip[dc], 0, 14 - n); } - mg_byte_reverse(ctx->in, 14); - - a = (uint32_t *) ctx->in; - a[14] = ctx->bits[0]; - a[15] = ctx->bits[1]; - mg_md5_transform(ctx->buf, (uint32_t *) ctx->in); - mg_byte_reverse((unsigned char *) ctx->buf, 4); - memcpy(digest, ctx->buf, 16); - memset((char *) ctx, 0, sizeof(*ctx)); + addr->is_ip6 = true; + return true; } -#endif - -#ifdef MG_ENABLE_LINES -#line 1 "src/mqtt.c" -#endif - - +bool mg_aton(struct mg_str str, struct mg_addr *addr) { + // MG_INFO(("[%.*s]", (int) str.len, str.buf)); + return mg_atone(str, addr) || mg_atonl(str, addr) || mg_aton4(str, addr) || + mg_aton6(str, addr); +} +struct mg_connection *mg_alloc_conn(struct mg_mgr *mgr) { + struct mg_connection *c = + (struct mg_connection *) calloc(1, sizeof(*c) + mgr->extraconnsize); + if (c != NULL) { + c->mgr = mgr; + c->send.align = c->recv.align = c->rtls.align = MG_IO_SIZE; + c->id = ++mgr->nextid; + MG_PROF_INIT(c); + } + return c; +} +void mg_close_conn(struct mg_connection *c) { + mg_resolve_cancel(c); // Close any pending DNS query + LIST_DELETE(struct mg_connection, &c->mgr->conns, c); + if (c == c->mgr->dns4.c) c->mgr->dns4.c = NULL; + if (c == c->mgr->dns6.c) c->mgr->dns6.c = NULL; + // Order of operations is important. `MG_EV_CLOSE` event must be fired + // before we deallocate received data, see #1331 + mg_call(c, MG_EV_CLOSE, NULL); + MG_DEBUG(("%lu %ld closed", c->id, c->fd)); + MG_PROF_DUMP(c); + MG_PROF_FREE(c); + mg_tls_free(c); + mg_iobuf_free(&c->recv); + mg_iobuf_free(&c->send); + mg_iobuf_free(&c->rtls); + mg_bzero((unsigned char *) c, sizeof(*c)); + free(c); +} +struct mg_connection *mg_connect(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data) { + struct mg_connection *c = NULL; + if (url == NULL || url[0] == '\0') { + MG_ERROR(("null url")); + } else if ((c = mg_alloc_conn(mgr)) == NULL) { + MG_ERROR(("OOM")); + } else { + LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); + c->is_udp = (strncmp(url, "udp:", 4) == 0); + c->fd = (void *) (size_t) MG_INVALID_SOCKET; + c->fn = fn; + c->is_client = true; + c->fn_data = fn_data; + MG_DEBUG(("%lu %ld %s", c->id, c->fd, url)); + mg_call(c, MG_EV_OPEN, (void *) url); + mg_resolve(c, url); + } + return c; +} -#define MQTT_CLEAN_SESSION 0x02 -#define MQTT_HAS_WILL 0x04 -#define MQTT_WILL_RETAIN 0x20 -#define MQTT_HAS_PASSWORD 0x40 -#define MQTT_HAS_USER_NAME 0x80 +struct mg_connection *mg_listen(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data) { + struct mg_connection *c = NULL; + if ((c = mg_alloc_conn(mgr)) == NULL) { + MG_ERROR(("OOM %s", url)); + } else if (!mg_open_listener(c, url)) { + MG_ERROR(("Failed: %s, errno %d", url, errno)); + MG_PROF_FREE(c); + free(c); + c = NULL; + } else { + c->is_listening = 1; + c->is_udp = strncmp(url, "udp:", 4) == 0; + LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); + c->fn = fn; + c->fn_data = fn_data; + mg_call(c, MG_EV_OPEN, NULL); + if (mg_url_is_ssl(url)) c->is_tls = 1; // Accepted connection must + MG_DEBUG(("%lu %ld %s", c->id, c->fd, url)); + } + return c; +} -struct mg_mqtt_pmap { - uint8_t id; - uint8_t type; -}; +struct mg_connection *mg_wrapfd(struct mg_mgr *mgr, int fd, + mg_event_handler_t fn, void *fn_data) { + struct mg_connection *c = mg_alloc_conn(mgr); + if (c != NULL) { + c->fd = (void *) (size_t) fd; + c->fn = fn; + c->fn_data = fn_data; + MG_EPOLL_ADD(c); + mg_call(c, MG_EV_OPEN, NULL); + LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); + } + return c; +} -static const struct mg_mqtt_pmap s_prop_map[] = { - {MQTT_PROP_PAYLOAD_FORMAT_INDICATOR, MQTT_PROP_TYPE_BYTE}, - {MQTT_PROP_MESSAGE_EXPIRY_INTERVAL, MQTT_PROP_TYPE_INT}, - {MQTT_PROP_CONTENT_TYPE, MQTT_PROP_TYPE_STRING}, - {MQTT_PROP_RESPONSE_TOPIC, MQTT_PROP_TYPE_STRING}, - {MQTT_PROP_CORRELATION_DATA, MQTT_PROP_TYPE_BINARY_DATA}, - {MQTT_PROP_SUBSCRIPTION_IDENTIFIER, MQTT_PROP_TYPE_VARIABLE_INT}, - {MQTT_PROP_SESSION_EXPIRY_INTERVAL, MQTT_PROP_TYPE_INT}, - {MQTT_PROP_ASSIGNED_CLIENT_IDENTIFIER, MQTT_PROP_TYPE_STRING}, - {MQTT_PROP_SERVER_KEEP_ALIVE, MQTT_PROP_TYPE_SHORT}, - {MQTT_PROP_AUTHENTICATION_METHOD, MQTT_PROP_TYPE_STRING}, - {MQTT_PROP_AUTHENTICATION_DATA, MQTT_PROP_TYPE_BINARY_DATA}, - {MQTT_PROP_REQUEST_PROBLEM_INFORMATION, MQTT_PROP_TYPE_BYTE}, - {MQTT_PROP_WILL_DELAY_INTERVAL, MQTT_PROP_TYPE_INT}, - {MQTT_PROP_REQUEST_RESPONSE_INFORMATION, MQTT_PROP_TYPE_BYTE}, - {MQTT_PROP_RESPONSE_INFORMATION, MQTT_PROP_TYPE_STRING}, - {MQTT_PROP_SERVER_REFERENCE, MQTT_PROP_TYPE_STRING}, - {MQTT_PROP_REASON_STRING, MQTT_PROP_TYPE_STRING}, - {MQTT_PROP_RECEIVE_MAXIMUM, MQTT_PROP_TYPE_SHORT}, - {MQTT_PROP_TOPIC_ALIAS_MAXIMUM, MQTT_PROP_TYPE_SHORT}, - {MQTT_PROP_TOPIC_ALIAS, MQTT_PROP_TYPE_SHORT}, - {MQTT_PROP_MAXIMUM_QOS, MQTT_PROP_TYPE_BYTE}, - {MQTT_PROP_RETAIN_AVAILABLE, MQTT_PROP_TYPE_BYTE}, - {MQTT_PROP_USER_PROPERTY, MQTT_PROP_TYPE_STRING_PAIR}, - {MQTT_PROP_MAXIMUM_PACKET_SIZE, MQTT_PROP_TYPE_INT}, - {MQTT_PROP_WILDCARD_SUBSCRIPTION_AVAILABLE, MQTT_PROP_TYPE_BYTE}, - {MQTT_PROP_SUBSCRIPTION_IDENTIFIER_AVAILABLE, MQTT_PROP_TYPE_BYTE}, - {MQTT_PROP_SHARED_SUBSCRIPTION_AVAILABLE, MQTT_PROP_TYPE_BYTE}}; +struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t milliseconds, + unsigned flags, void (*fn)(void *), void *arg) { + struct mg_timer *t = (struct mg_timer *) calloc(1, sizeof(*t)); + if (t != NULL) { + mg_timer_init(&mgr->timers, t, milliseconds, flags, fn, arg); + t->id = mgr->timerid++; + } + return t; +} -void mg_mqtt_send_header(struct mg_connection *c, uint8_t cmd, uint8_t flags, - uint32_t len) { - uint8_t buf[1 + sizeof(len)], *vlen = &buf[1]; - buf[0] = (uint8_t) ((cmd << 4) | flags); - do { - *vlen = len % 0x80; - len /= 0x80; - if (len > 0) *vlen |= 0x80; - vlen++; - } while (len > 0 && vlen < &buf[sizeof(buf)]); - mg_send(c, buf, (size_t) (vlen - buf)); +long mg_io_recv(struct mg_connection *c, void *buf, size_t len) { + if (c->rtls.len == 0) return MG_IO_WAIT; + if (len > c->rtls.len) len = c->rtls.len; + memcpy(buf, c->rtls.buf, len); + mg_iobuf_del(&c->rtls, 0, len); + return (long) len; } -static void mg_send_u16(struct mg_connection *c, uint16_t value) { - mg_send(c, &value, sizeof(value)); +void mg_mgr_free(struct mg_mgr *mgr) { + struct mg_connection *c; + struct mg_timer *tmp, *t = mgr->timers; + while (t != NULL) tmp = t->next, free(t), t = tmp; + mgr->timers = NULL; // Important. Next call to poll won't touch timers + for (c = mgr->conns; c != NULL; c = c->next) c->is_closing = 1; + mg_mgr_poll(mgr, 0); +#if MG_ENABLE_FREERTOS_TCP + FreeRTOS_DeleteSocketSet(mgr->ss); +#endif + MG_DEBUG(("All connections closed")); +#if MG_ENABLE_EPOLL + if (mgr->epoll_fd >= 0) close(mgr->epoll_fd), mgr->epoll_fd = -1; +#endif + mg_tls_ctx_free(mgr); } -static void mg_send_u32(struct mg_connection *c, uint32_t value) { - mg_send(c, &value, sizeof(value)); +void mg_mgr_init(struct mg_mgr *mgr) { + memset(mgr, 0, sizeof(*mgr)); +#if MG_ENABLE_EPOLL + if ((mgr->epoll_fd = epoll_create1(EPOLL_CLOEXEC)) < 0) + MG_ERROR(("epoll_create1 errno %d", errno)); +#else + mgr->epoll_fd = -1; +#endif +#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK + // clang-format off + { WSADATA data; WSAStartup(MAKEWORD(2, 2), &data); } + // clang-format on +#elif MG_ENABLE_FREERTOS_TCP + mgr->ss = FreeRTOS_CreateSocketSet(); +#elif defined(__unix) || defined(__unix__) || defined(__APPLE__) + // Ignore SIGPIPE signal, so if client cancels the request, it + // won't kill the whole process. + signal(SIGPIPE, SIG_IGN); +#elif MG_ENABLE_TCPIP_DRIVER_INIT && defined(MG_TCPIP_DRIVER_INIT) + MG_TCPIP_DRIVER_INIT(mgr); +#endif + mgr->pipe = MG_INVALID_SOCKET; + mgr->dnstimeout = 3000; + mgr->dns4.url = "udp://8.8.8.8:53"; + mgr->dns6.url = "udp://[2001:4860:4860::8888]:53"; + mg_tls_ctx_init(mgr); } -static uint8_t varint_size(size_t length) { - uint8_t bytes_needed = 0; - do { - bytes_needed++; - length /= 0x80; - } while (length > 0); - return bytes_needed; -} +#ifdef MG_ENABLE_LINES +#line 1 "src/net_builtin.c" +#endif -static size_t encode_varint(uint8_t *buf, size_t value) { - size_t len = 0; - do { - uint8_t b = (uint8_t) (value % 128); - value /= 128; - if (value > 0) b |= 0x80; - buf[len++] = b; - } while (value > 0); +#if defined(MG_ENABLE_TCPIP) && MG_ENABLE_TCPIP +#define MG_EPHEMERAL_PORT_BASE 32768 +#define PDIFF(a, b) ((size_t) (((char *) (b)) - ((char *) (a)))) - return len; -} +#ifndef MIP_TCP_KEEPALIVE_MS +#define MIP_TCP_KEEPALIVE_MS 45000 // TCP keep-alive period, ms +#endif -static size_t decode_varint(const uint8_t *buf, size_t len, size_t *value) { - size_t multiplier = 1, offset; - *value = 0; +#define MIP_TCP_ACK_MS 150 // Timeout for ACKing +#define MIP_ARP_RESP_MS 100 // Timeout for ARP response +#define MIP_TCP_SYN_MS 15000 // Timeout for connection establishment +#define MIP_TCP_FIN_MS 1000 // Timeout for closing connection +#define MIP_TCP_WIN 6000 // TCP window size - for (offset = 0; offset < 4 && offset < len; offset++) { - uint8_t encoded_byte = buf[offset]; - *value += (encoded_byte & 0x7f) * multiplier; - multiplier *= 128; +struct connstate { + uint32_t seq, ack; // TCP seq/ack counters + uint64_t timer; // TCP keep-alive / ACK timer + uint32_t acked; // Last ACK-ed number + size_t unacked; // Not acked bytes + uint8_t mac[6]; // Peer MAC address + uint8_t ttype; // Timer type. 0: ack, 1: keep-alive +#define MIP_TTYPE_KEEPALIVE 0 // Connection is idle for long, send keepalive +#define MIP_TTYPE_ACK 1 // Peer sent us data, we have to ack it soon +#define MIP_TTYPE_ARP 2 // ARP resolve sent, waiting for response +#define MIP_TTYPE_SYN 3 // SYN sent, waiting for response +#define MIP_TTYPE_FIN 4 // FIN sent, waiting until terminating the connection + uint8_t tmiss; // Number of keep-alive misses + struct mg_iobuf raw; // For TLS only. Incoming raw data +}; - if ((encoded_byte & 0x80) == 0) return offset + 1; - } +#pragma pack(push, 1) - return 0; -} +struct lcp { + uint8_t addr, ctrl, proto[2], code, id, len[2]; +}; -static int mqtt_prop_type_by_id(uint8_t prop_id) { - size_t i, num_properties = sizeof(s_prop_map) / sizeof(s_prop_map[0]); - for (i = 0; i < num_properties; ++i) { - if (s_prop_map[i].id == prop_id) return s_prop_map[i].type; - } - return -1; // Property ID not found -} +struct eth { + uint8_t dst[6]; // Destination MAC address + uint8_t src[6]; // Source MAC address + uint16_t type; // Ethernet type +}; -// Returns the size of the properties section, without the -// size of the content's length -static size_t get_properties_length(struct mg_mqtt_prop *props, size_t count) { - size_t i, size = 0; - for (i = 0; i < count; i++) { - size++; // identifier - switch (mqtt_prop_type_by_id(props[i].id)) { - case MQTT_PROP_TYPE_STRING_PAIR: - size += (uint32_t) (props[i].val.len + props[i].key.len + - 2 * sizeof(uint16_t)); - break; - case MQTT_PROP_TYPE_STRING: - size += (uint32_t) (props[i].val.len + sizeof(uint16_t)); - break; - case MQTT_PROP_TYPE_BINARY_DATA: - size += (uint32_t) (props[i].val.len + sizeof(uint16_t)); - break; - case MQTT_PROP_TYPE_VARIABLE_INT: - size += varint_size((uint32_t) props[i].iv); - break; - case MQTT_PROP_TYPE_INT: - size += (uint32_t) sizeof(uint32_t); - break; - case MQTT_PROP_TYPE_SHORT: - size += (uint32_t) sizeof(uint16_t); - break; - case MQTT_PROP_TYPE_BYTE: - size += (uint32_t) sizeof(uint8_t); - break; - default: - return size; // cannot parse further down - } - } +struct ip { + uint8_t ver; // Version + uint8_t tos; // Unused + uint16_t len; // Length + uint16_t id; // Unused + uint16_t frag; // Fragmentation +#define IP_FRAG_OFFSET_MSK 0x1fff +#define IP_MORE_FRAGS_MSK 0x2000 + uint8_t ttl; // Time to live + uint8_t proto; // Upper level protocol + uint16_t csum; // Checksum + uint32_t src; // Source IP + uint32_t dst; // Destination IP +}; - return size; -} +struct ip6 { + uint8_t ver; // Version + uint8_t opts[3]; // Options + uint16_t len; // Length + uint8_t proto; // Upper level protocol + uint8_t ttl; // Time to live + uint8_t src[16]; // Source IP + uint8_t dst[16]; // Destination IP +}; -// returns the entire size of the properties section, including the -// size of the variable length of the content -static size_t get_props_size(struct mg_mqtt_prop *props, size_t count) { - size_t size = get_properties_length(props, count); - size += varint_size(size); - return size; -} +struct icmp { + uint8_t type; + uint8_t code; + uint16_t csum; +}; -static void mg_send_mqtt_properties(struct mg_connection *c, - struct mg_mqtt_prop *props, size_t nprops) { - size_t total_size = get_properties_length(props, nprops); - uint8_t buf_v[4] = {0, 0, 0, 0}; - uint8_t buf[4] = {0, 0, 0, 0}; - size_t i, len = encode_varint(buf, total_size); +struct arp { + uint16_t fmt; // Format of hardware address + uint16_t pro; // Format of protocol address + uint8_t hlen; // Length of hardware address + uint8_t plen; // Length of protocol address + uint16_t op; // Operation + uint8_t sha[6]; // Sender hardware address + uint32_t spa; // Sender protocol address + uint8_t tha[6]; // Target hardware address + uint32_t tpa; // Target protocol address +}; - mg_send(c, buf, (size_t) len); - for (i = 0; i < nprops; i++) { - mg_send(c, &props[i].id, sizeof(props[i].id)); - switch (mqtt_prop_type_by_id(props[i].id)) { - case MQTT_PROP_TYPE_STRING_PAIR: - mg_send_u16(c, mg_htons((uint16_t) props[i].key.len)); - mg_send(c, props[i].key.buf, props[i].key.len); - mg_send_u16(c, mg_htons((uint16_t) props[i].val.len)); - mg_send(c, props[i].val.buf, props[i].val.len); - break; - case MQTT_PROP_TYPE_BYTE: - mg_send(c, &props[i].iv, sizeof(uint8_t)); - break; - case MQTT_PROP_TYPE_SHORT: - mg_send_u16(c, mg_htons((uint16_t) props[i].iv)); - break; - case MQTT_PROP_TYPE_INT: - mg_send_u32(c, mg_htonl((uint32_t) props[i].iv)); - break; - case MQTT_PROP_TYPE_STRING: - mg_send_u16(c, mg_htons((uint16_t) props[i].val.len)); - mg_send(c, props[i].val.buf, props[i].val.len); - break; - case MQTT_PROP_TYPE_BINARY_DATA: - mg_send_u16(c, mg_htons((uint16_t) props[i].val.len)); - mg_send(c, props[i].val.buf, props[i].val.len); - break; - case MQTT_PROP_TYPE_VARIABLE_INT: - len = encode_varint(buf_v, props[i].iv); - mg_send(c, buf_v, (size_t) len); - break; - } - } -} +struct tcp { + uint16_t sport; // Source port + uint16_t dport; // Destination port + uint32_t seq; // Sequence number + uint32_t ack; // Acknowledgement number + uint8_t off; // Data offset + uint8_t flags; // TCP flags +#define TH_FIN 0x01 +#define TH_SYN 0x02 +#define TH_RST 0x04 +#define TH_PUSH 0x08 +#define TH_ACK 0x10 +#define TH_URG 0x20 +#define TH_ECE 0x40 +#define TH_CWR 0x80 + uint16_t win; // Window + uint16_t csum; // Checksum + uint16_t urp; // Urgent pointer +}; -size_t mg_mqtt_next_prop(struct mg_mqtt_message *msg, struct mg_mqtt_prop *prop, - size_t ofs) { - uint8_t *i = (uint8_t *) msg->dgram.buf + msg->props_start + ofs; - uint8_t *end = (uint8_t *) msg->dgram.buf + msg->dgram.len; - size_t new_pos = ofs, len; - prop->id = i[0]; +struct udp { + uint16_t sport; // Source port + uint16_t dport; // Destination port + uint16_t len; // UDP length + uint16_t csum; // UDP checksum +}; - if (ofs >= msg->dgram.len || ofs >= msg->props_start + msg->props_size) - return 0; - i++, new_pos++; +struct dhcp { + uint8_t op, htype, hlen, hops; + uint32_t xid; + uint16_t secs, flags; + uint32_t ciaddr, yiaddr, siaddr, giaddr; + uint8_t hwaddr[208]; + uint32_t magic; + uint8_t options[32]; +}; - switch (mqtt_prop_type_by_id(prop->id)) { - case MQTT_PROP_TYPE_STRING_PAIR: - prop->key.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]); - prop->key.buf = (char *) i + 2; - i += 2 + prop->key.len; - prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]); - prop->val.buf = (char *) i + 2; - new_pos += 2 * sizeof(uint16_t) + prop->val.len + prop->key.len; - break; - case MQTT_PROP_TYPE_BYTE: - prop->iv = (uint8_t) i[0]; - new_pos++; - break; - case MQTT_PROP_TYPE_SHORT: - prop->iv = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]); - new_pos += sizeof(uint16_t); - break; - case MQTT_PROP_TYPE_INT: - prop->iv = ((uint32_t) i[0] << 24) | ((uint32_t) i[1] << 16) | - ((uint32_t) i[2] << 8) | i[3]; - new_pos += sizeof(uint32_t); - break; - case MQTT_PROP_TYPE_STRING: - prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]); - prop->val.buf = (char *) i + 2; - new_pos += 2 + prop->val.len; - break; - case MQTT_PROP_TYPE_BINARY_DATA: - prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]); - prop->val.buf = (char *) i + 2; - new_pos += 2 + prop->val.len; - break; - case MQTT_PROP_TYPE_VARIABLE_INT: - len = decode_varint(i, (size_t) (end - i), (size_t *) &prop->iv); - new_pos = (!len) ? 0 : new_pos + len; - break; - default: - new_pos = 0; - } +#pragma pack(pop) - return new_pos; -} +struct pkt { + struct mg_str raw; // Raw packet data + struct mg_str pay; // Payload data + struct eth *eth; + struct llc *llc; + struct arp *arp; + struct ip *ip; + struct ip6 *ip6; + struct icmp *icmp; + struct tcp *tcp; + struct udp *udp; + struct dhcp *dhcp; +}; -void mg_mqtt_login(struct mg_connection *c, const struct mg_mqtt_opts *opts) { - char client_id[21]; - struct mg_str cid = opts->client_id; - size_t total_len = 7 + 1 + 2 + 2; - uint8_t hdr[8] = {0, 4, 'M', 'Q', 'T', 'T', opts->version, 0}; +static void mg_tcpip_call(struct mg_tcpip_if *ifp, int ev, void *ev_data) { + if (ifp->fn != NULL) ifp->fn(ifp, ev, ev_data); +} - if (cid.len == 0) { - mg_random_str(client_id, sizeof(client_id) - 1); - client_id[sizeof(client_id) - 1] = '\0'; - cid = mg_str(client_id); - } +static void send_syn(struct mg_connection *c); - if (hdr[6] == 0) hdr[6] = 4; // If version is not set, use 4 (3.1.1) - c->is_mqtt5 = hdr[6] == 5; // Set version 5 flag - hdr[7] = (uint8_t) ((opts->qos & 3) << 3); // Connection flags - if (opts->user.len > 0) { - total_len += 2 + (uint32_t) opts->user.len; - hdr[7] |= MQTT_HAS_USER_NAME; - } - if (opts->pass.len > 0) { - total_len += 2 + (uint32_t) opts->pass.len; - hdr[7] |= MQTT_HAS_PASSWORD; - } - if (opts->topic.len > 0) { // allow zero-length msgs, message.len is size_t - total_len += 4 + (uint32_t) opts->topic.len + (uint32_t) opts->message.len; - hdr[7] |= MQTT_HAS_WILL; - } - if (opts->clean || cid.len == 0) hdr[7] |= MQTT_CLEAN_SESSION; - if (opts->retain) hdr[7] |= MQTT_WILL_RETAIN; - total_len += (uint32_t) cid.len; - if (c->is_mqtt5) { - total_len += get_props_size(opts->props, opts->num_props); - if (hdr[7] & MQTT_HAS_WILL) - total_len += get_props_size(opts->will_props, opts->num_will_props); - } +static void mkpay(struct pkt *pkt, void *p) { + pkt->pay = + mg_str_n((char *) p, (size_t) (&pkt->raw.buf[pkt->raw.len] - (char *) p)); +} - mg_mqtt_send_header(c, MQTT_CMD_CONNECT, 0, (uint32_t) total_len); - mg_send(c, hdr, sizeof(hdr)); - // keepalive == 0 means "do not disconnect us!" - mg_send_u16(c, mg_htons((uint16_t) opts->keepalive)); +static uint32_t csumup(uint32_t sum, const void *buf, size_t len) { + size_t i; + const uint8_t *p = (const uint8_t *) buf; + for (i = 0; i < len; i++) sum += i & 1 ? p[i] : (uint32_t) (p[i] << 8); + return sum; +} - if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props, opts->num_props); +static uint16_t csumfin(uint32_t sum) { + while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16); + return mg_htons(~sum & 0xffff); +} - mg_send_u16(c, mg_htons((uint16_t) cid.len)); - mg_send(c, cid.buf, cid.len); +static uint16_t ipcsum(const void *buf, size_t len) { + uint32_t sum = csumup(0, buf, len); + return csumfin(sum); +} - if (hdr[7] & MQTT_HAS_WILL) { - if (c->is_mqtt5) - mg_send_mqtt_properties(c, opts->will_props, opts->num_will_props); +static void settmout(struct mg_connection *c, uint8_t type) { + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv; + struct connstate *s = (struct connstate *) (c + 1); + unsigned n = type == MIP_TTYPE_ACK ? MIP_TCP_ACK_MS + : type == MIP_TTYPE_ARP ? MIP_ARP_RESP_MS + : type == MIP_TTYPE_SYN ? MIP_TCP_SYN_MS + : type == MIP_TTYPE_FIN ? MIP_TCP_FIN_MS + : MIP_TCP_KEEPALIVE_MS; + s->timer = ifp->now + n; + s->ttype = type; + MG_VERBOSE(("%lu %d -> %llx", c->id, type, s->timer)); +} - mg_send_u16(c, mg_htons((uint16_t) opts->topic.len)); - mg_send(c, opts->topic.buf, opts->topic.len); - mg_send_u16(c, mg_htons((uint16_t) opts->message.len)); - mg_send(c, opts->message.buf, opts->message.len); - } - if (opts->user.len > 0) { - mg_send_u16(c, mg_htons((uint16_t) opts->user.len)); - mg_send(c, opts->user.buf, opts->user.len); - } - if (opts->pass.len > 0) { - mg_send_u16(c, mg_htons((uint16_t) opts->pass.len)); - mg_send(c, opts->pass.buf, opts->pass.len); - } +static size_t ether_output(struct mg_tcpip_if *ifp, size_t len) { + size_t n = ifp->driver->tx(ifp->tx.buf, len, ifp); + if (n == len) ifp->nsent++; + return n; } -uint16_t mg_mqtt_pub(struct mg_connection *c, const struct mg_mqtt_opts *opts) { - uint16_t id = opts->retransmit_id; - uint8_t flags = (uint8_t) (((opts->qos & 3) << 1) | (opts->retain ? 1 : 0)); - size_t len = 2 + opts->topic.len + opts->message.len; - MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) opts->topic.len, - (char *) opts->topic.buf, (int) opts->message.len, - (char *) opts->message.buf)); - if (opts->qos > 0) len += 2; - if (c->is_mqtt5) len += get_props_size(opts->props, opts->num_props); +void mg_tcpip_arp_request(struct mg_tcpip_if *ifp, uint32_t ip, uint8_t *mac) { + struct eth *eth = (struct eth *) ifp->tx.buf; + struct arp *arp = (struct arp *) (eth + 1); + memset(eth->dst, 255, sizeof(eth->dst)); + memcpy(eth->src, ifp->mac, sizeof(eth->src)); + eth->type = mg_htons(0x806); + memset(arp, 0, sizeof(*arp)); + arp->fmt = mg_htons(1), arp->pro = mg_htons(0x800), arp->hlen = 6, + arp->plen = 4; + arp->op = mg_htons(1), arp->tpa = ip, arp->spa = ifp->ip; + memcpy(arp->sha, ifp->mac, sizeof(arp->sha)); + if (mac != NULL) memcpy(arp->tha, mac, sizeof(arp->tha)); + ether_output(ifp, PDIFF(eth, arp + 1)); +} - if (opts->qos > 0 && id != 0) flags |= 1 << 3; - mg_mqtt_send_header(c, MQTT_CMD_PUBLISH, flags, (uint32_t) len); - mg_send_u16(c, mg_htons((uint16_t) opts->topic.len)); - mg_send(c, opts->topic.buf, opts->topic.len); - if (opts->qos > 0) { // need to send 'id' field - if (id == 0) { // generate new one if not resending - if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id; - id = c->mgr->mqtt_id; - } - mg_send_u16(c, mg_htons(id)); +static void onstatechange(struct mg_tcpip_if *ifp) { + if (ifp->state == MG_TCPIP_STATE_READY) { + MG_INFO(("READY, IP: %M", mg_print_ip4, &ifp->ip)); + MG_INFO((" GW: %M", mg_print_ip4, &ifp->gw)); + MG_INFO((" MAC: %M", mg_print_mac, &ifp->mac)); + } else if (ifp->state == MG_TCPIP_STATE_IP) { + MG_ERROR(("Got IP")); + mg_tcpip_arp_request(ifp, ifp->gw, NULL); // unsolicited GW ARP request + } else if (ifp->state == MG_TCPIP_STATE_UP) { + MG_ERROR(("Link up")); + srand((unsigned int) mg_millis()); + } else if (ifp->state == MG_TCPIP_STATE_DOWN) { + MG_ERROR(("Link down")); } + mg_tcpip_call(ifp, MG_TCPIP_EV_ST_CHG, &ifp->state); +} - if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props, opts->num_props); +static struct ip *tx_ip(struct mg_tcpip_if *ifp, uint8_t *mac_dst, + uint8_t proto, uint32_t ip_src, uint32_t ip_dst, + size_t plen) { + struct eth *eth = (struct eth *) ifp->tx.buf; + struct ip *ip = (struct ip *) (eth + 1); + memcpy(eth->dst, mac_dst, sizeof(eth->dst)); + memcpy(eth->src, ifp->mac, sizeof(eth->src)); // Use our MAC + eth->type = mg_htons(0x800); + memset(ip, 0, sizeof(*ip)); + ip->ver = 0x45; // Version 4, header length 5 words + ip->frag = mg_htons(0x4000); // Don't fragment + ip->len = mg_htons((uint16_t) (sizeof(*ip) + plen)); + ip->ttl = 64; + ip->proto = proto; + ip->src = ip_src; + ip->dst = ip_dst; + ip->csum = ipcsum(ip, sizeof(*ip)); + return ip; +} - if (opts->message.len > 0) mg_send(c, opts->message.buf, opts->message.len); - return id; +static void tx_udp(struct mg_tcpip_if *ifp, uint8_t *mac_dst, uint32_t ip_src, + uint16_t sport, uint32_t ip_dst, uint16_t dport, + const void *buf, size_t len) { + struct ip *ip = + tx_ip(ifp, mac_dst, 17, ip_src, ip_dst, len + sizeof(struct udp)); + struct udp *udp = (struct udp *) (ip + 1); + // MG_DEBUG(("UDP XX LEN %d %d", (int) len, (int) ifp->tx.len)); + udp->sport = sport; + udp->dport = dport; + udp->len = mg_htons((uint16_t) (sizeof(*udp) + len)); + udp->csum = 0; + uint32_t cs = csumup(0, udp, sizeof(*udp)); + cs = csumup(cs, buf, len); + cs = csumup(cs, &ip->src, sizeof(ip->src)); + cs = csumup(cs, &ip->dst, sizeof(ip->dst)); + cs += (uint32_t) (ip->proto + sizeof(*udp) + len); + udp->csum = csumfin(cs); + memmove(udp + 1, buf, len); + // MG_DEBUG(("UDP LEN %d %d", (int) len, (int) ifp->frame_len)); + ether_output(ifp, sizeof(struct eth) + sizeof(*ip) + sizeof(*udp) + len); } -void mg_mqtt_sub(struct mg_connection *c, const struct mg_mqtt_opts *opts) { - uint8_t qos_ = opts->qos & 3; - size_t plen = c->is_mqtt5 ? get_props_size(opts->props, opts->num_props) : 0; - size_t len = 2 + opts->topic.len + 2 + 1 + plen; +static void tx_dhcp(struct mg_tcpip_if *ifp, uint8_t *mac_dst, uint32_t ip_src, + uint32_t ip_dst, uint8_t *opts, size_t optslen, + bool ciaddr) { + // https://datatracker.ietf.org/doc/html/rfc2132#section-9.6 + struct dhcp dhcp = {1, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}}; + dhcp.magic = mg_htonl(0x63825363); + memcpy(&dhcp.hwaddr, ifp->mac, sizeof(ifp->mac)); + memcpy(&dhcp.xid, ifp->mac + 2, sizeof(dhcp.xid)); + memcpy(&dhcp.options, opts, optslen); + if (ciaddr) dhcp.ciaddr = ip_src; + tx_udp(ifp, mac_dst, ip_src, mg_htons(68), ip_dst, mg_htons(67), &dhcp, + sizeof(dhcp)); +} - mg_mqtt_send_header(c, MQTT_CMD_SUBSCRIBE, 2, (uint32_t) len); - if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id; - mg_send_u16(c, mg_htons(c->mgr->mqtt_id)); - if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props, opts->num_props); +static const uint8_t broadcast[] = {255, 255, 255, 255, 255, 255}; - mg_send_u16(c, mg_htons((uint16_t) opts->topic.len)); - mg_send(c, opts->topic.buf, opts->topic.len); - mg_send(c, &qos_, sizeof(qos_)); +// RFC-2131 #4.3.6, #4.4.1; RFC-2132 #9.8 +static void tx_dhcp_request_sel(struct mg_tcpip_if *ifp, uint32_t ip_req, + uint32_t ip_srv) { + uint8_t opts[] = { + 53, 1, 3, // Type: DHCP request + 12, 3, 'm', 'i', 'p', // Host name: "mip" + 54, 4, 0, 0, 0, 0, // DHCP server ID + 50, 4, 0, 0, 0, 0, // Requested IP + 55, 2, 1, 3, 255, 255, // GW, mask [DNS] [SNTP] + 255 // End of options + }; + uint8_t addopts = 0; + memcpy(opts + 10, &ip_srv, sizeof(ip_srv)); + memcpy(opts + 16, &ip_req, sizeof(ip_req)); + if (ifp->enable_req_dns) opts[24 + addopts++] = 6; // DNS + if (ifp->enable_req_sntp) opts[24 + addopts++] = 42; // SNTP + opts[21] += addopts; + tx_dhcp(ifp, (uint8_t *) broadcast, 0, 0xffffffff, opts, + sizeof(opts) + addopts - 2, false); + MG_DEBUG(("DHCP req sent")); } -int mg_mqtt_parse(const uint8_t *buf, size_t len, uint8_t version, - struct mg_mqtt_message *m) { - uint8_t lc = 0, *p, *end; - uint32_t n = 0, len_len = 0; - - memset(m, 0, sizeof(*m)); - m->dgram.buf = (char *) buf; - if (len < 2) return MQTT_INCOMPLETE; - m->cmd = (uint8_t) (buf[0] >> 4); - m->qos = (buf[0] >> 1) & 3; +// RFC-2131 #4.3.6, #4.4.5 (renewing: unicast, rebinding: bcast) +static void tx_dhcp_request_re(struct mg_tcpip_if *ifp, uint8_t *mac_dst, + uint32_t ip_src, uint32_t ip_dst) { + uint8_t opts[] = { + 53, 1, 3, // Type: DHCP request + 255 // End of options + }; + tx_dhcp(ifp, mac_dst, ip_src, ip_dst, opts, sizeof(opts), true); + MG_DEBUG(("DHCP req sent")); +} - n = len_len = 0; - p = (uint8_t *) buf + 1; - while ((size_t) (p - buf) < len) { - lc = *((uint8_t *) p++); - n += (uint32_t) ((lc & 0x7f) << 7 * len_len); - len_len++; - if (!(lc & 0x80)) break; - if (len_len >= 4) return MQTT_MALFORMED; - } - end = p + n; - if ((lc & 0x80) || (end > buf + len)) return MQTT_INCOMPLETE; - m->dgram.len = (size_t) (end - buf); +static void tx_dhcp_discover(struct mg_tcpip_if *ifp) { + uint8_t opts[] = { + 53, 1, 1, // Type: DHCP discover + 55, 2, 1, 3, // Parameters: ip, mask + 255 // End of options + }; + tx_dhcp(ifp, (uint8_t *) broadcast, 0, 0xffffffff, opts, sizeof(opts), false); + MG_DEBUG(("DHCP discover sent. Our MAC: %M", mg_print_mac, ifp->mac)); +} - switch (m->cmd) { - case MQTT_CMD_CONNACK: - if (end - p < 2) return MQTT_MALFORMED; - m->ack = p[1]; - break; - case MQTT_CMD_PUBACK: - case MQTT_CMD_PUBREC: - case MQTT_CMD_PUBREL: - case MQTT_CMD_PUBCOMP: - case MQTT_CMD_SUBSCRIBE: - case MQTT_CMD_SUBACK: - case MQTT_CMD_UNSUBSCRIBE: - case MQTT_CMD_UNSUBACK: - if (p + 2 > end) return MQTT_MALFORMED; - m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]); - p += 2; - break; - case MQTT_CMD_PUBLISH: { - if (p + 2 > end) return MQTT_MALFORMED; - m->topic.len = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]); - m->topic.buf = (char *) p + 2; - p += 2 + m->topic.len; - if (p > end) return MQTT_MALFORMED; - if (m->qos > 0) { - if (p + 2 > end) return MQTT_MALFORMED; - m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]); - p += 2; - } - if (p > end) return MQTT_MALFORMED; - if (version == 5 && p + 2 < end) { - len_len = - (uint32_t) decode_varint(p, (size_t) (end - p), &m->props_size); - if (!len_len) return MQTT_MALFORMED; - m->props_start = (size_t) (p + len_len - buf); - p += len_len + m->props_size; - } - if (p > end) return MQTT_MALFORMED; - m->data.buf = (char *) p; - m->data.len = (size_t) (end - p); +static struct mg_connection *getpeer(struct mg_mgr *mgr, struct pkt *pkt, + bool lsn) { + struct mg_connection *c = NULL; + for (c = mgr->conns; c != NULL; c = c->next) { + if (c->is_arplooking && pkt->arp && + memcmp(&pkt->arp->spa, c->rem.ip, sizeof(pkt->arp->spa)) == 0) break; - } - default: + if (c->is_udp && pkt->udp && c->loc.port == pkt->udp->dport) break; + if (!c->is_udp && pkt->tcp && c->loc.port == pkt->tcp->dport && + lsn == c->is_listening && (lsn || c->rem.port == pkt->tcp->sport)) break; } - return MQTT_OK; + return c; } -static void mqtt_cb(struct mg_connection *c, int ev, void *ev_data) { - if (ev == MG_EV_READ) { - for (;;) { - uint8_t version = c->is_mqtt5 ? 5 : 4; - struct mg_mqtt_message mm; - int rc = mg_mqtt_parse(c->recv.buf, c->recv.len, version, &mm); - if (rc == MQTT_MALFORMED) { - MG_ERROR(("%lu MQTT malformed message", c->id)); - c->is_closing = 1; - break; - } else if (rc == MQTT_OK) { - MG_VERBOSE(("%lu MQTT CMD %d len %d [%.*s]", c->id, mm.cmd, - (int) mm.dgram.len, (int) mm.data.len, mm.data.buf)); - switch (mm.cmd) { - case MQTT_CMD_CONNACK: - mg_call(c, MG_EV_MQTT_OPEN, &mm.ack); - if (mm.ack == 0) { - MG_DEBUG(("%lu Connected", c->id)); - } else { - MG_ERROR(("%lu MQTT auth failed, code %d", c->id, mm.ack)); - c->is_closing = 1; - } - break; - case MQTT_CMD_PUBLISH: { - /*MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) mm.topic.len, - mm.topic.buf, (int) mm.data.len, mm.data.buf));*/ - if (mm.qos > 0) { - uint16_t id = mg_ntohs(mm.id); - uint32_t remaining_len = sizeof(id); - if (c->is_mqtt5) remaining_len += 2; // 3.4.2 - - mg_mqtt_send_header( - c, - (uint8_t) (mm.qos == 2 ? MQTT_CMD_PUBREC : MQTT_CMD_PUBACK), - 0, remaining_len); - mg_send(c, &id, sizeof(id)); +static void mac_resolved(struct mg_connection *c); - if (c->is_mqtt5) { - uint16_t zero = 0; - mg_send(c, &zero, sizeof(zero)); - } - } - mg_call(c, MG_EV_MQTT_MSG, &mm); // let the app handle qos stuff - break; - } - case MQTT_CMD_PUBREC: { // MQTT5: 3.5.2-1 TODO(): variable header rc - uint16_t id = mg_ntohs(mm.id); - uint32_t remaining_len = sizeof(id); // MQTT5 3.6.2-1 - mg_mqtt_send_header(c, MQTT_CMD_PUBREL, 2, remaining_len); - mg_send(c, &id, sizeof(id)); // MQTT5 3.6.1-1, flags = 2 - break; - } - case MQTT_CMD_PUBREL: { // MQTT5: 3.6.2-1 TODO(): variable header rc - uint16_t id = mg_ntohs(mm.id); - uint32_t remaining_len = sizeof(id); // MQTT5 3.7.2-1 - mg_mqtt_send_header(c, MQTT_CMD_PUBCOMP, 0, remaining_len); - mg_send(c, &id, sizeof(id)); - break; - } - } - mg_call(c, MG_EV_MQTT_CMD, &mm); - mg_iobuf_del(&c->recv, 0, mm.dgram.len); - } else { - break; +static void rx_arp(struct mg_tcpip_if *ifp, struct pkt *pkt) { + if (pkt->arp->op == mg_htons(1) && pkt->arp->tpa == ifp->ip) { + // ARP request. Make a response, then send + // MG_DEBUG(("ARP op %d %M: %M", mg_ntohs(pkt->arp->op), mg_print_ip4, + // &pkt->arp->spa, mg_print_ip4, &pkt->arp->tpa)); + struct eth *eth = (struct eth *) ifp->tx.buf; + struct arp *arp = (struct arp *) (eth + 1); + memcpy(eth->dst, pkt->eth->src, sizeof(eth->dst)); + memcpy(eth->src, ifp->mac, sizeof(eth->src)); + eth->type = mg_htons(0x806); + *arp = *pkt->arp; + arp->op = mg_htons(2); + memcpy(arp->tha, pkt->arp->sha, sizeof(pkt->arp->tha)); + memcpy(arp->sha, ifp->mac, sizeof(pkt->arp->sha)); + arp->tpa = pkt->arp->spa; + arp->spa = ifp->ip; + MG_DEBUG(("ARP: tell %M we're %M", mg_print_ip4, &arp->tpa, mg_print_mac, + &ifp->mac)); + ether_output(ifp, PDIFF(eth, arp + 1)); + } else if (pkt->arp->op == mg_htons(2)) { + if (memcmp(pkt->arp->tha, ifp->mac, sizeof(pkt->arp->tha)) != 0) return; + if (pkt->arp->spa == ifp->gw) { + // Got response for the GW ARP request. Set ifp->gwmac and IP -> READY + memcpy(ifp->gwmac, pkt->arp->sha, sizeof(ifp->gwmac)); + if (ifp->state == MG_TCPIP_STATE_IP) { + ifp->state = MG_TCPIP_STATE_READY; + onstatechange(ifp); } - } - } - (void) ev_data; -} - -void mg_mqtt_ping(struct mg_connection *nc) { - mg_mqtt_send_header(nc, MQTT_CMD_PINGREQ, 0, 0); + } else { + struct mg_connection *c = getpeer(ifp->mgr, pkt, false); + if (c != NULL && c->is_arplooking) { + struct connstate *s = (struct connstate *) (c + 1); + memcpy(s->mac, pkt->arp->sha, sizeof(s->mac)); + MG_DEBUG(("%lu ARP resolved %M -> %M", c->id, mg_print_ip4, c->rem.ip, + mg_print_mac, s->mac)); + c->is_arplooking = 0; + mac_resolved(c); + } + } + } } -void mg_mqtt_pong(struct mg_connection *nc) { - mg_mqtt_send_header(nc, MQTT_CMD_PINGRESP, 0, 0); +static void rx_icmp(struct mg_tcpip_if *ifp, struct pkt *pkt) { + // MG_DEBUG(("ICMP %d", (int) len)); + if (pkt->icmp->type == 8 && pkt->ip != NULL && pkt->ip->dst == ifp->ip) { + size_t hlen = sizeof(struct eth) + sizeof(struct ip) + sizeof(struct icmp); + size_t space = ifp->tx.len - hlen, plen = pkt->pay.len; + if (plen > space) plen = space; + struct ip *ip = tx_ip(ifp, pkt->eth->src, 1, ifp->ip, pkt->ip->src, + sizeof(struct icmp) + plen); + struct icmp *icmp = (struct icmp *) (ip + 1); + memset(icmp, 0, sizeof(*icmp)); // Set csum to 0 + memcpy(icmp + 1, pkt->pay.buf, plen); // Copy RX payload to TX + icmp->csum = ipcsum(icmp, sizeof(*icmp) + plen); + ether_output(ifp, hlen + plen); + } } -void mg_mqtt_disconnect(struct mg_connection *c, - const struct mg_mqtt_opts *opts) { - size_t len = 0; - if (c->is_mqtt5) len = 1 + get_props_size(opts->props, opts->num_props); - mg_mqtt_send_header(c, MQTT_CMD_DISCONNECT, 0, (uint32_t) len); - - if (c->is_mqtt5) { - uint8_t zero = 0; - mg_send(c, &zero, sizeof(zero)); // reason code - mg_send_mqtt_properties(c, opts->props, opts->num_props); +static void rx_dhcp_client(struct mg_tcpip_if *ifp, struct pkt *pkt) { + uint32_t ip = 0, gw = 0, mask = 0, lease = 0, dns = 0, sntp = 0; + uint8_t msgtype = 0, state = ifp->state; + // perform size check first, then access fields + uint8_t *p = pkt->dhcp->options, + *end = (uint8_t *) &pkt->raw.buf[pkt->raw.len]; + if (end < (uint8_t *) (pkt->dhcp + 1)) return; + if (memcmp(&pkt->dhcp->xid, ifp->mac + 2, sizeof(pkt->dhcp->xid))) return; + while (p + 1 < end && p[0] != 255) { // Parse options RFC-1533 #9 + if (p[0] == 1 && p[1] == sizeof(ifp->mask) && p + 6 < end) { // Mask + memcpy(&mask, p + 2, sizeof(mask)); + } else if (p[0] == 3 && p[1] == sizeof(ifp->gw) && p + 6 < end) { // GW + memcpy(&gw, p + 2, sizeof(gw)); + ip = pkt->dhcp->yiaddr; + } else if (ifp->enable_req_dns && p[0] == 6 && p[1] == sizeof(dns) && + p + 6 < end) { // DNS + memcpy(&dns, p + 2, sizeof(dns)); + } else if (ifp->enable_req_sntp && p[0] == 42 && p[1] == sizeof(sntp) && + p + 6 < end) { // SNTP + memcpy(&sntp, p + 2, sizeof(sntp)); + } else if (p[0] == 51 && p[1] == 4 && p + 6 < end) { // Lease + memcpy(&lease, p + 2, sizeof(lease)); + lease = mg_ntohl(lease); + } else if (p[0] == 53 && p[1] == 1 && p + 6 < end) { // Msg Type + msgtype = p[2]; + } + p += p[1] + 2; + } + // Process message type, RFC-1533 (9.4); RFC-2131 (3.1, 4) + if (msgtype == 6 && ifp->ip == ip) { // DHCPNACK, release IP + ifp->state = MG_TCPIP_STATE_UP, ifp->ip = 0; + } else if (msgtype == 2 && ifp->state == MG_TCPIP_STATE_UP && ip && gw && + lease) { // DHCPOFFER + // select IP, (4.4.1) (fallback to IP source addr on foul play) + tx_dhcp_request_sel(ifp, ip, + pkt->dhcp->siaddr ? pkt->dhcp->siaddr : pkt->ip->src); + ifp->state = MG_TCPIP_STATE_REQ; // REQUESTING state + } else if (msgtype == 5) { // DHCPACK + if (ifp->state == MG_TCPIP_STATE_REQ && ip && gw && lease) { // got an IP + ifp->lease_expire = ifp->now + lease * 1000; + MG_INFO(("Lease: %u sec (%lld)", lease, ifp->lease_expire / 1000)); + // assume DHCP server = router until ARP resolves + memcpy(ifp->gwmac, pkt->eth->src, sizeof(ifp->gwmac)); + ifp->ip = ip, ifp->gw = gw, ifp->mask = mask; + ifp->state = MG_TCPIP_STATE_IP; // BOUND state + uint64_t rand; + mg_random(&rand, sizeof(rand)); + srand((unsigned int) (rand + mg_millis())); + if (ifp->enable_req_dns && dns != 0) + mg_tcpip_call(ifp, MG_TCPIP_EV_DHCP_DNS, &dns); + if (ifp->enable_req_sntp && sntp != 0) + mg_tcpip_call(ifp, MG_TCPIP_EV_DHCP_SNTP, &sntp); + } else if (ifp->state == MG_TCPIP_STATE_READY && ifp->ip == ip) { // renew + ifp->lease_expire = ifp->now + lease * 1000; + MG_INFO(("Lease: %u sec (%lld)", lease, ifp->lease_expire / 1000)); + } // TODO(): accept provided T1/T2 and store server IP for renewal (4.4) } + if (ifp->state != state) onstatechange(ifp); } -struct mg_connection *mg_mqtt_connect(struct mg_mgr *mgr, const char *url, - const struct mg_mqtt_opts *opts, - mg_event_handler_t fn, void *fn_data) { - struct mg_connection *c = mg_connect(mgr, url, fn, fn_data); - if (c != NULL) { - struct mg_mqtt_opts empty; - memset(&empty, 0, sizeof(empty)); - mg_mqtt_login(c, opts == NULL ? &empty : opts); - c->pfn = mqtt_cb; +// Simple DHCP server that assigns a next IP address: ifp->ip + 1 +static void rx_dhcp_server(struct mg_tcpip_if *ifp, struct pkt *pkt) { + uint8_t op = 0, *p = pkt->dhcp->options, + *end = (uint8_t *) &pkt->raw.buf[pkt->raw.len]; + if (end < (uint8_t *) (pkt->dhcp + 1)) return; + // struct dhcp *req = pkt->dhcp; + struct dhcp res = {2, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}}; + res.yiaddr = ifp->ip; + ((uint8_t *) (&res.yiaddr))[3]++; // Offer our IP + 1 + while (p + 1 < end && p[0] != 255) { // Parse options + if (p[0] == 53 && p[1] == 1 && p + 2 < end) { // Message type + op = p[2]; + } + p += p[1] + 2; + } + if (op == 1 || op == 3) { // DHCP Discover or DHCP Request + uint8_t msg = op == 1 ? 2 : 5; // Message type: DHCP OFFER or DHCP ACK + uint8_t opts[] = { + 53, 1, msg, // Message type + 1, 4, 0, 0, 0, 0, // Subnet mask + 54, 4, 0, 0, 0, 0, // Server ID + 12, 3, 'm', 'i', 'p', // Host name: "mip" + 51, 4, 255, 255, 255, 255, // Lease time + 255 // End of options + }; + memcpy(&res.hwaddr, pkt->dhcp->hwaddr, 6); + memcpy(opts + 5, &ifp->mask, sizeof(ifp->mask)); + memcpy(opts + 11, &ifp->ip, sizeof(ifp->ip)); + memcpy(&res.options, opts, sizeof(opts)); + res.magic = pkt->dhcp->magic; + res.xid = pkt->dhcp->xid; + if (ifp->enable_get_gateway) { + ifp->gw = res.yiaddr; // set gw IP, best-effort gwmac as DHCP server's + memcpy(ifp->gwmac, pkt->eth->src, sizeof(ifp->gwmac)); + } + tx_udp(ifp, pkt->eth->src, ifp->ip, mg_htons(67), + op == 1 ? ~0U : res.yiaddr, mg_htons(68), &res, sizeof(res)); } - return c; } -struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const char *url, - mg_event_handler_t fn, void *fn_data) { - struct mg_connection *c = mg_listen(mgr, url, fn, fn_data); - if (c != NULL) c->pfn = mqtt_cb, c->pfn_data = mgr; - return c; +static void rx_udp(struct mg_tcpip_if *ifp, struct pkt *pkt) { + struct mg_connection *c = getpeer(ifp->mgr, pkt, true); + if (c == NULL) { + // No UDP listener on this port. Should send ICMP, but keep silent. + } else { + c->rem.port = pkt->udp->sport; + memcpy(c->rem.ip, &pkt->ip->src, sizeof(uint32_t)); + struct connstate *s = (struct connstate *) (c + 1); + memcpy(s->mac, pkt->eth->src, sizeof(s->mac)); + if (c->recv.len >= MG_MAX_RECV_SIZE) { + mg_error(c, "max_recv_buf_size reached"); + } else if (c->recv.size - c->recv.len < pkt->pay.len && + !mg_iobuf_resize(&c->recv, c->recv.len + pkt->pay.len)) { + mg_error(c, "oom"); + } else { + memcpy(&c->recv.buf[c->recv.len], pkt->pay.buf, pkt->pay.len); + c->recv.len += pkt->pay.len; + mg_call(c, MG_EV_READ, &pkt->pay.len); + } + } } -#ifdef MG_ENABLE_LINES -#line 1 "src/net.c" +static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *dst_mac, uint32_t dst_ip, + uint8_t flags, uint16_t sport, uint16_t dport, + uint32_t seq, uint32_t ack, const void *buf, size_t len) { +#if 0 + uint8_t opts[] = {2, 4, 5, 0xb4, 4, 2, 0, 0}; // MSS = 1460, SACK permitted + if (flags & TH_SYN) { + // Handshake? Set MSS + buf = opts; + len = sizeof(opts); + } #endif + struct ip *ip = + tx_ip(ifp, dst_mac, 6, ifp->ip, dst_ip, sizeof(struct tcp) + len); + struct tcp *tcp = (struct tcp *) (ip + 1); + memset(tcp, 0, sizeof(*tcp)); + if (buf != NULL && len) memmove(tcp + 1, buf, len); + tcp->sport = sport; + tcp->dport = dport; + tcp->seq = seq; + tcp->ack = ack; + tcp->flags = flags; + tcp->win = mg_htons(MIP_TCP_WIN); + tcp->off = (uint8_t) (sizeof(*tcp) / 4 << 4); + // if (flags & TH_SYN) tcp->off = 0x70; // Handshake? header size 28 bytes - - - - - - - - -size_t mg_vprintf(struct mg_connection *c, const char *fmt, va_list *ap) { - size_t old = c->send.len; - mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap); - return c->send.len - old; -} - -size_t mg_printf(struct mg_connection *c, const char *fmt, ...) { - size_t len = 0; - va_list ap; - va_start(ap, fmt); - len = mg_vprintf(c, fmt, &ap); - va_end(ap); - return len; + uint32_t cs = 0; + uint16_t n = (uint16_t) (sizeof(*tcp) + len); + uint8_t pseudo[] = {0, ip->proto, (uint8_t) (n >> 8), (uint8_t) (n & 255)}; + cs = csumup(cs, tcp, n); + cs = csumup(cs, &ip->src, sizeof(ip->src)); + cs = csumup(cs, &ip->dst, sizeof(ip->dst)); + cs = csumup(cs, pseudo, sizeof(pseudo)); + tcp->csum = csumfin(cs); + MG_VERBOSE(("TCP %M:%hu -> %M:%hu fl %x len %u", mg_print_ip4, &ip->src, + mg_ntohs(tcp->sport), mg_print_ip4, &ip->dst, + mg_ntohs(tcp->dport), tcp->flags, len)); + // mg_hexdump(ifp->tx.buf, PDIFF(ifp->tx.buf, tcp + 1) + len); + return ether_output(ifp, PDIFF(ifp->tx.buf, tcp + 1) + len); } -static bool mg_atonl(struct mg_str str, struct mg_addr *addr) { - uint32_t localhost = mg_htonl(0x7f000001); - if (mg_strcasecmp(str, mg_str("localhost")) != 0) return false; - memcpy(addr->ip, &localhost, sizeof(uint32_t)); - addr->is_ip6 = false; - return true; +static size_t tx_tcp_pkt(struct mg_tcpip_if *ifp, struct pkt *pkt, + uint8_t flags, uint32_t seq, const void *buf, + size_t len) { + uint32_t delta = (pkt->tcp->flags & (TH_SYN | TH_FIN)) ? 1 : 0; + return tx_tcp(ifp, pkt->eth->src, pkt->ip->src, flags, pkt->tcp->dport, + pkt->tcp->sport, seq, mg_htonl(mg_ntohl(pkt->tcp->seq) + delta), + buf, len); } -static bool mg_atone(struct mg_str str, struct mg_addr *addr) { - if (str.len > 0) return false; - memset(addr->ip, 0, sizeof(addr->ip)); - addr->is_ip6 = false; - return true; +static struct mg_connection *accept_conn(struct mg_connection *lsn, + struct pkt *pkt) { + struct mg_connection *c = mg_alloc_conn(lsn->mgr); + if (c == NULL) { + MG_ERROR(("OOM")); + return NULL; + } + struct connstate *s = (struct connstate *) (c + 1); + s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq); + memcpy(s->mac, pkt->eth->src, sizeof(s->mac)); + settmout(c, MIP_TTYPE_KEEPALIVE); + memcpy(c->rem.ip, &pkt->ip->src, sizeof(uint32_t)); + c->rem.port = pkt->tcp->sport; + MG_DEBUG(("%lu accepted %M", c->id, mg_print_ip_port, &c->rem)); + LIST_ADD_HEAD(struct mg_connection, &lsn->mgr->conns, c); + c->is_accepted = 1; + c->is_hexdumping = lsn->is_hexdumping; + c->pfn = lsn->pfn; + c->loc = lsn->loc; + c->pfn_data = lsn->pfn_data; + c->fn = lsn->fn; + c->fn_data = lsn->fn_data; + mg_call(c, MG_EV_OPEN, NULL); + mg_call(c, MG_EV_ACCEPT, NULL); + return c; } -static bool mg_aton4(struct mg_str str, struct mg_addr *addr) { - uint8_t data[4] = {0, 0, 0, 0}; - size_t i, num_dots = 0; - for (i = 0; i < str.len; i++) { - if (str.buf[i] >= '0' && str.buf[i] <= '9') { - int octet = data[num_dots] * 10 + (str.buf[i] - '0'); - if (octet > 255) return false; - data[num_dots] = (uint8_t) octet; - } else if (str.buf[i] == '.') { - if (num_dots >= 3 || i == 0 || str.buf[i - 1] == '.') return false; - num_dots++; - } else { - return false; +static size_t trim_len(struct mg_connection *c, size_t len) { + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv; + size_t eth_h_len = 14, ip_max_h_len = 24, tcp_max_h_len = 60, udp_h_len = 8; + size_t max_headers_len = + eth_h_len + ip_max_h_len + (c->is_udp ? udp_h_len : tcp_max_h_len); + size_t min_mtu = c->is_udp ? 68 /* RFC-791 */ : max_headers_len - eth_h_len; + + // If the frame exceeds the available buffer, trim the length + if (len + max_headers_len > ifp->tx.len) { + len = ifp->tx.len - max_headers_len; + } + // Ensure the MTU isn't lower than the minimum allowed value + if (ifp->mtu < min_mtu) { + MG_ERROR(("MTU is lower than minimum, capping to %lu", min_mtu)); + ifp->mtu = (uint16_t) min_mtu; + } + // If the total packet size exceeds the MTU, trim the length + if (len + max_headers_len - eth_h_len > ifp->mtu) { + len = ifp->mtu - max_headers_len + eth_h_len; + if (c->is_udp) { + MG_ERROR(("UDP datagram exceeds MTU. Truncating it.")); } } - if (num_dots != 3 || str.buf[i - 1] == '.') return false; - memcpy(&addr->ip, data, sizeof(data)); - addr->is_ip6 = false; - return true; -} -static bool mg_v4mapped(struct mg_str str, struct mg_addr *addr) { - int i; - uint32_t ipv4; - if (str.len < 14) return false; - if (str.buf[0] != ':' || str.buf[1] != ':' || str.buf[6] != ':') return false; - for (i = 2; i < 6; i++) { - if (str.buf[i] != 'f' && str.buf[i] != 'F') return false; - } - // struct mg_str s = mg_str_n(&str.buf[7], str.len - 7); - if (!mg_aton4(mg_str_n(&str.buf[7], str.len - 7), addr)) return false; - memcpy(&ipv4, addr->ip, sizeof(ipv4)); - memset(addr->ip, 0, sizeof(addr->ip)); - addr->ip[10] = addr->ip[11] = 255; - memcpy(&addr->ip[12], &ipv4, 4); - addr->is_ip6 = true; - return true; + return len; } -static bool mg_aton6(struct mg_str str, struct mg_addr *addr) { - size_t i, j = 0, n = 0, dc = 42; - addr->scope_id = 0; - if (str.len > 2 && str.buf[0] == '[') str.buf++, str.len -= 2; - if (mg_v4mapped(str, addr)) return true; - for (i = 0; i < str.len; i++) { - if ((str.buf[i] >= '0' && str.buf[i] <= '9') || - (str.buf[i] >= 'a' && str.buf[i] <= 'f') || - (str.buf[i] >= 'A' && str.buf[i] <= 'F')) { - unsigned long val = 0; // TODO(): This loops on chars, refactor - if (i > j + 3) return false; - // MG_DEBUG(("%lu %lu [%.*s]", i, j, (int) (i - j + 1), &str.buf[j])); - mg_str_to_num(mg_str_n(&str.buf[j], i - j + 1), 16, &val, sizeof(val)); - addr->ip[n] = (uint8_t) ((val >> 8) & 255); - addr->ip[n + 1] = (uint8_t) (val & 255); - } else if (str.buf[i] == ':') { - j = i + 1; - if (i > 0 && str.buf[i - 1] == ':') { - dc = n; // Double colon - if (i > 1 && str.buf[i - 2] == ':') return false; - } else if (i > 0) { - n += 2; - } - if (n > 14) return false; - addr->ip[n] = addr->ip[n + 1] = 0; // For trailing :: - } else if (str.buf[i] == '%') { // Scope ID, last in string - return mg_str_to_num(mg_str_n(&str.buf[i + 1], str.len - i - 1), 10, - &addr->scope_id, sizeof(uint8_t)); +long mg_io_send(struct mg_connection *c, const void *buf, size_t len) { + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv; + struct connstate *s = (struct connstate *) (c + 1); + uint32_t dst_ip = *(uint32_t *) c->rem.ip; + len = trim_len(c, len); + if (c->is_udp) { + tx_udp(ifp, s->mac, ifp->ip, c->loc.port, dst_ip, c->rem.port, buf, len); + } else { + size_t sent = + tx_tcp(ifp, s->mac, dst_ip, TH_PUSH | TH_ACK, c->loc.port, c->rem.port, + mg_htonl(s->seq), mg_htonl(s->ack), buf, len); + if (sent == 0) { + return MG_IO_WAIT; + } else if (sent == (size_t) -1) { + return MG_IO_ERR; } else { - return false; + s->seq += (uint32_t) len; + if (s->ttype == MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_KEEPALIVE); } } - if (n < 14 && dc == 42) return false; - if (n < 14) { - memmove(&addr->ip[dc + (14 - n)], &addr->ip[dc], n - dc + 2); - memset(&addr->ip[dc], 0, 14 - n); - } - - addr->is_ip6 = true; - return true; -} - -bool mg_aton(struct mg_str str, struct mg_addr *addr) { - // MG_INFO(("[%.*s]", (int) str.len, str.buf)); - return mg_atone(str, addr) || mg_atonl(str, addr) || mg_aton4(str, addr) || - mg_aton6(str, addr); + return (long) len; } -struct mg_connection *mg_alloc_conn(struct mg_mgr *mgr) { - struct mg_connection *c = - (struct mg_connection *) calloc(1, sizeof(*c) + mgr->extraconnsize); - if (c != NULL) { - c->mgr = mgr; - c->send.align = c->recv.align = c->rtls.align = MG_IO_SIZE; - c->id = ++mgr->nextid; - MG_PROF_INIT(c); +static void handle_tls_recv(struct mg_connection *c, struct mg_iobuf *io) { + long n = mg_tls_recv(c, &io->buf[io->len], io->size - io->len); + if (n == MG_IO_ERR) { + mg_error(c, "TLS recv error"); + } else if (n > 0) { + // Decrypted successfully - trigger MG_EV_READ + io->len += (size_t) n; + mg_call(c, MG_EV_READ, &n); } - return c; } -void mg_close_conn(struct mg_connection *c) { - mg_resolve_cancel(c); // Close any pending DNS query - LIST_DELETE(struct mg_connection, &c->mgr->conns, c); - if (c == c->mgr->dns4.c) c->mgr->dns4.c = NULL; - if (c == c->mgr->dns6.c) c->mgr->dns6.c = NULL; - // Order of operations is important. `MG_EV_CLOSE` event must be fired - // before we deallocate received data, see #1331 - mg_call(c, MG_EV_CLOSE, NULL); - MG_DEBUG(("%lu %ld closed", c->id, c->fd)); - MG_PROF_DUMP(c); - MG_PROF_FREE(c); +static void read_conn(struct mg_connection *c, struct pkt *pkt) { + struct connstate *s = (struct connstate *) (c + 1); + struct mg_iobuf *io = c->is_tls ? &c->rtls : &c->recv; + uint32_t seq = mg_ntohl(pkt->tcp->seq); + uint32_t rem_ip; + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t)); + if (pkt->tcp->flags & TH_FIN) { + // If we initiated the closure, we reply with ACK upon receiving FIN + // If we didn't initiate it, we reply with FIN as part of the normal TCP + // closure process + uint8_t flags = TH_ACK; + s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len + 1); + if (c->is_draining && s->ttype == MIP_TTYPE_FIN) { + if (s->seq == mg_htonl(pkt->tcp->ack)) { // Simultaneous closure ? + s->seq++; // Yes. Increment our SEQ + } else { // Otherwise, + s->seq = mg_htonl(pkt->tcp->ack); // Set to peer's ACK + } + } else { + flags |= TH_FIN; + c->is_draining = 1; + settmout(c, MIP_TTYPE_FIN); + } + tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip, flags, + c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), "", 0); + } else if (pkt->pay.len == 0) { + // TODO(cpq): handle this peer's ACK + } else if (seq != s->ack) { + uint32_t ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len); + if (s->ack == ack) { + MG_VERBOSE(("ignoring duplicate pkt")); + } else { + MG_VERBOSE(("SEQ != ACK: %x %x %x", seq, s->ack, ack)); + tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip, TH_ACK, + c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), "", + 0); + } + } else if (io->size - io->len < pkt->pay.len && + !mg_iobuf_resize(io, io->len + pkt->pay.len)) { + mg_error(c, "oom"); + } else { + // Copy TCP payload into the IO buffer. If the connection is plain text, + // we copy to c->recv. If the connection is TLS, this data is encrypted, + // therefore we copy that encrypted data to the c->rtls iobuffer instead, + // and then call mg_tls_recv() to decrypt it. NOTE: mg_tls_recv() will + // call back mg_io_recv() which grabs raw data from c->rtls + memcpy(&io->buf[io->len], pkt->pay.buf, pkt->pay.len); + io->len += pkt->pay.len; - mg_tls_free(c); - mg_iobuf_free(&c->recv); - mg_iobuf_free(&c->send); - mg_iobuf_free(&c->rtls); - mg_bzero((unsigned char *) c, sizeof(*c)); - free(c); -} + MG_VERBOSE(("%lu SEQ %x -> %x", c->id, mg_htonl(pkt->tcp->seq), s->ack)); + // Advance ACK counter + s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len); + s->unacked += pkt->pay.len; + // size_t diff = s->acked <= s->ack ? s->ack - s->acked : s->ack; + if (s->unacked > MIP_TCP_WIN / 2 && s->acked != s->ack) { + // Send ACK immediately + MG_VERBOSE(("%lu imm ACK %lu", c->id, s->acked)); + tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip, TH_ACK, + c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), NULL, + 0); + s->unacked = 0; + s->acked = s->ack; + if (s->ttype != MIP_TTYPE_KEEPALIVE) settmout(c, MIP_TTYPE_KEEPALIVE); + } else { + // if not already running, setup a timer to send an ACK later + if (s->ttype != MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_ACK); + } -struct mg_connection *mg_connect(struct mg_mgr *mgr, const char *url, - mg_event_handler_t fn, void *fn_data) { - struct mg_connection *c = NULL; - if (url == NULL || url[0] == '\0') { - MG_ERROR(("null url")); - } else if ((c = mg_alloc_conn(mgr)) == NULL) { - MG_ERROR(("OOM")); - } else { - LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); - c->is_udp = (strncmp(url, "udp:", 4) == 0); - c->fd = (void *) (size_t) MG_INVALID_SOCKET; - c->fn = fn; - c->is_client = true; - c->fn_data = fn_data; - MG_DEBUG(("%lu %ld %s", c->id, c->fd, url)); - mg_call(c, MG_EV_OPEN, (void *) url); - mg_resolve(c, url); + if (c->is_tls && c->is_tls_hs) { + mg_tls_handshake(c); + } else if (c->is_tls) { + // TLS connection. Make room for decrypted data in c->recv + io = &c->recv; + if (io->size - io->len < pkt->pay.len && + !mg_iobuf_resize(io, io->len + pkt->pay.len)) { + mg_error(c, "oom"); + } else { + // Decrypt data directly into c->recv + handle_tls_recv(c, io); + } + } else { + // Plain text connection, data is already in c->recv, trigger + // MG_EV_READ + mg_call(c, MG_EV_READ, &pkt->pay.len); + } } - return c; } -struct mg_connection *mg_listen(struct mg_mgr *mgr, const char *url, - mg_event_handler_t fn, void *fn_data) { - struct mg_connection *c = NULL; - if ((c = mg_alloc_conn(mgr)) == NULL) { - MG_ERROR(("OOM %s", url)); - } else if (!mg_open_listener(c, url)) { - MG_ERROR(("Failed: %s, errno %d", url, errno)); - MG_PROF_FREE(c); - free(c); - c = NULL; +static void rx_tcp(struct mg_tcpip_if *ifp, struct pkt *pkt) { + struct mg_connection *c = getpeer(ifp->mgr, pkt, false); + struct connstate *s = c == NULL ? NULL : (struct connstate *) (c + 1); +#if 0 + MG_INFO(("%lu %hhu %d", c ? c->id : 0, pkt->tcp->flags, (int) pkt->pay.len)); +#endif + if (c != NULL && c->is_connecting && pkt->tcp->flags == (TH_SYN | TH_ACK)) { + s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq) + 1; + tx_tcp_pkt(ifp, pkt, TH_ACK, pkt->tcp->ack, NULL, 0); + c->is_connecting = 0; // Client connected + settmout(c, MIP_TTYPE_KEEPALIVE); + mg_call(c, MG_EV_CONNECT, NULL); // Let user know + if (c->is_tls_hs) mg_tls_handshake(c); + } else if (c != NULL && c->is_connecting && pkt->tcp->flags != TH_ACK) { + // mg_hexdump(pkt->raw.buf, pkt->raw.len); + tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0); + } else if (c != NULL && pkt->tcp->flags & TH_RST) { + mg_error(c, "peer RST"); // RFC-1122 4.2.2.13 + } else if (c != NULL) { +#if 0 + MG_DEBUG(("%lu %d %M:%hu -> %M:%hu", c->id, (int) pkt->raw.len, + mg_print_ip4, &pkt->ip->src, mg_ntohs(pkt->tcp->sport), + mg_print_ip4, &pkt->ip->dst, mg_ntohs(pkt->tcp->dport))); + mg_hexdump(pkt->pay.buf, pkt->pay.len); +#endif + s->tmiss = 0; // Reset missed keep-alive counter + if (s->ttype == MIP_TTYPE_KEEPALIVE) // Advance keep-alive timer + settmout(c, + MIP_TTYPE_KEEPALIVE); // unless a former ACK timeout is pending + read_conn(c, pkt); // Override timer with ACK timeout if needed + } else if ((c = getpeer(ifp->mgr, pkt, true)) == NULL) { + tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0); + } else if (pkt->tcp->flags & TH_RST) { + if (c->is_accepted) mg_error(c, "peer RST"); // RFC-1122 4.2.2.13 + // ignore RST if not connected + } else if (pkt->tcp->flags & TH_SYN) { + // Use peer's source port as ISN, in order to recognise the handshake + uint32_t isn = mg_htonl((uint32_t) mg_ntohs(pkt->tcp->sport)); + tx_tcp_pkt(ifp, pkt, TH_SYN | TH_ACK, isn, NULL, 0); + } else if (pkt->tcp->flags & TH_FIN) { + tx_tcp_pkt(ifp, pkt, TH_FIN | TH_ACK, pkt->tcp->ack, NULL, 0); + } else if (mg_htonl(pkt->tcp->ack) == mg_htons(pkt->tcp->sport) + 1U) { + accept_conn(c, pkt); + } else if (!c->is_accepted) { // no peer + tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0); } else { - c->is_listening = 1; - c->is_udp = strncmp(url, "udp:", 4) == 0; - LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); - c->fn = fn; - c->fn_data = fn_data; - mg_call(c, MG_EV_OPEN, NULL); - if (mg_url_is_ssl(url)) c->is_tls = 1; // Accepted connection must - MG_DEBUG(("%lu %ld %s", c->id, c->fd, url)); + // MG_VERBOSE(("dropped silently..")); } - return c; } -struct mg_connection *mg_wrapfd(struct mg_mgr *mgr, int fd, - mg_event_handler_t fn, void *fn_data) { - struct mg_connection *c = mg_alloc_conn(mgr); - if (c != NULL) { - c->fd = (void *) (size_t) fd; - c->fn = fn; - c->fn_data = fn_data; - MG_EPOLL_ADD(c); - mg_call(c, MG_EV_OPEN, NULL); - LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); +static void rx_ip(struct mg_tcpip_if *ifp, struct pkt *pkt) { + uint16_t frag = mg_ntohs(pkt->ip->frag); + if (frag & IP_MORE_FRAGS_MSK || frag & IP_FRAG_OFFSET_MSK) { + if (pkt->ip->proto == 17) pkt->udp = (struct udp *) (pkt->ip + 1); + if (pkt->ip->proto == 6) pkt->tcp = (struct tcp *) (pkt->ip + 1); + struct mg_connection *c = getpeer(ifp->mgr, pkt, false); + if (c) mg_error(c, "Received fragmented packet"); + } else if (pkt->ip->proto == 1) { + pkt->icmp = (struct icmp *) (pkt->ip + 1); + if (pkt->pay.len < sizeof(*pkt->icmp)) return; + mkpay(pkt, pkt->icmp + 1); + rx_icmp(ifp, pkt); + } else if (pkt->ip->proto == 17) { + pkt->udp = (struct udp *) (pkt->ip + 1); + if (pkt->pay.len < sizeof(*pkt->udp)) return; + mkpay(pkt, pkt->udp + 1); + MG_VERBOSE(("UDP %M:%hu -> %M:%hu len %u", mg_print_ip4, &pkt->ip->src, + mg_ntohs(pkt->udp->sport), mg_print_ip4, &pkt->ip->dst, + mg_ntohs(pkt->udp->dport), (int) pkt->pay.len)); + if (ifp->enable_dhcp_client && pkt->udp->dport == mg_htons(68)) { + pkt->dhcp = (struct dhcp *) (pkt->udp + 1); + mkpay(pkt, pkt->dhcp + 1); + rx_dhcp_client(ifp, pkt); + } else if (ifp->enable_dhcp_server && pkt->udp->dport == mg_htons(67)) { + pkt->dhcp = (struct dhcp *) (pkt->udp + 1); + mkpay(pkt, pkt->dhcp + 1); + rx_dhcp_server(ifp, pkt); + } else { + rx_udp(ifp, pkt); + } + } else if (pkt->ip->proto == 6) { + pkt->tcp = (struct tcp *) (pkt->ip + 1); + if (pkt->pay.len < sizeof(*pkt->tcp)) return; + mkpay(pkt, pkt->tcp + 1); + uint16_t iplen = mg_ntohs(pkt->ip->len); + uint16_t off = (uint16_t) (sizeof(*pkt->ip) + ((pkt->tcp->off >> 4) * 4U)); + if (iplen >= off) pkt->pay.len = (size_t) (iplen - off); + MG_VERBOSE(("TCP %M:%hu -> %M:%hu len %u", mg_print_ip4, &pkt->ip->src, + mg_ntohs(pkt->tcp->sport), mg_print_ip4, &pkt->ip->dst, + mg_ntohs(pkt->tcp->dport), (int) pkt->pay.len)); + rx_tcp(ifp, pkt); } - return c; } -struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t milliseconds, - unsigned flags, void (*fn)(void *), void *arg) { - struct mg_timer *t = (struct mg_timer *) calloc(1, sizeof(*t)); - if (t != NULL) { - mg_timer_init(&mgr->timers, t, milliseconds, flags, fn, arg); - t->id = mgr->timerid++; +static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) { + // MG_DEBUG(("IP %d", (int) len)); + if (pkt->ip6->proto == 1 || pkt->ip6->proto == 58) { + pkt->icmp = (struct icmp *) (pkt->ip6 + 1); + if (pkt->pay.len < sizeof(*pkt->icmp)) return; + mkpay(pkt, pkt->icmp + 1); + rx_icmp(ifp, pkt); + } else if (pkt->ip6->proto == 17) { + pkt->udp = (struct udp *) (pkt->ip6 + 1); + if (pkt->pay.len < sizeof(*pkt->udp)) return; + // MG_DEBUG((" UDP %u %u -> %u", len, mg_htons(udp->sport), + // mg_htons(udp->dport))); + mkpay(pkt, pkt->udp + 1); } - return t; -} - -long mg_io_recv(struct mg_connection *c, void *buf, size_t len) { - if (c->rtls.len == 0) return MG_IO_WAIT; - if (len > c->rtls.len) len = c->rtls.len; - memcpy(buf, c->rtls.buf, len); - mg_iobuf_del(&c->rtls, 0, len); - return (long) len; -} - -void mg_mgr_free(struct mg_mgr *mgr) { - struct mg_connection *c; - struct mg_timer *tmp, *t = mgr->timers; - while (t != NULL) tmp = t->next, free(t), t = tmp; - mgr->timers = NULL; // Important. Next call to poll won't touch timers - for (c = mgr->conns; c != NULL; c = c->next) c->is_closing = 1; - mg_mgr_poll(mgr, 0); -#if MG_ENABLE_FREERTOS_TCP - FreeRTOS_DeleteSocketSet(mgr->ss); -#endif - MG_DEBUG(("All connections closed")); -#if MG_ENABLE_EPOLL - if (mgr->epoll_fd >= 0) close(mgr->epoll_fd), mgr->epoll_fd = -1; -#endif - mg_tls_ctx_free(mgr); -} - -void mg_mgr_init(struct mg_mgr *mgr) { - memset(mgr, 0, sizeof(*mgr)); -#if MG_ENABLE_EPOLL - if ((mgr->epoll_fd = epoll_create1(EPOLL_CLOEXEC)) < 0) - MG_ERROR(("epoll_create1 errno %d", errno)); -#else - mgr->epoll_fd = -1; -#endif -#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK - // clang-format off - { WSADATA data; WSAStartup(MAKEWORD(2, 2), &data); } - // clang-format on -#elif MG_ENABLE_FREERTOS_TCP - mgr->ss = FreeRTOS_CreateSocketSet(); -#elif defined(__unix) || defined(__unix__) || defined(__APPLE__) - // Ignore SIGPIPE signal, so if client cancels the request, it - // won't kill the whole process. - signal(SIGPIPE, SIG_IGN); -#elif MG_ENABLE_TCPIP_DRIVER_INIT && defined(MG_TCPIP_DRIVER_INIT) - MG_TCPIP_DRIVER_INIT(mgr); -#endif - mgr->pipe = MG_INVALID_SOCKET; - mgr->dnstimeout = 3000; - mgr->dns4.url = "udp://8.8.8.8:53"; - mgr->dns6.url = "udp://[2001:4860:4860::8888]:53"; - mg_tls_ctx_init(mgr); } -#ifdef MG_ENABLE_LINES -#line 1 "src/net_builtin.c" -#endif - - -#if defined(MG_ENABLE_TCPIP) && MG_ENABLE_TCPIP -#define MG_EPHEMERAL_PORT_BASE 32768 -#define PDIFF(a, b) ((size_t) (((char *) (b)) - ((char *) (a)))) - -#ifndef MIP_TCP_KEEPALIVE_MS -#define MIP_TCP_KEEPALIVE_MS 45000 // TCP keep-alive period, ms -#endif - -#define MIP_TCP_ACK_MS 150 // Timeout for ACKing -#define MIP_ARP_RESP_MS 100 // Timeout for ARP response -#define MIP_TCP_SYN_MS 15000 // Timeout for connection establishment -#define MIP_TCP_FIN_MS 1000 // Timeout for closing connection -#define MIP_TCP_WIN 6000 // TCP window size - -struct connstate { - uint32_t seq, ack; // TCP seq/ack counters - uint64_t timer; // TCP keep-alive / ACK timer - uint32_t acked; // Last ACK-ed number - size_t unacked; // Not acked bytes - uint8_t mac[6]; // Peer MAC address - uint8_t ttype; // Timer type. 0: ack, 1: keep-alive -#define MIP_TTYPE_KEEPALIVE 0 // Connection is idle for long, send keepalive -#define MIP_TTYPE_ACK 1 // Peer sent us data, we have to ack it soon -#define MIP_TTYPE_ARP 2 // ARP resolve sent, waiting for response -#define MIP_TTYPE_SYN 3 // SYN sent, waiting for response -#define MIP_TTYPE_FIN 4 // FIN sent, waiting until terminating the connection - uint8_t tmiss; // Number of keep-alive misses - struct mg_iobuf raw; // For TLS only. Incoming raw data -}; - -#pragma pack(push, 1) - -struct lcp { - uint8_t addr, ctrl, proto[2], code, id, len[2]; -}; - -struct eth { - uint8_t dst[6]; // Destination MAC address - uint8_t src[6]; // Source MAC address - uint16_t type; // Ethernet type -}; +static void mg_tcpip_rx(struct mg_tcpip_if *ifp, void *buf, size_t len) { + struct pkt pkt; + memset(&pkt, 0, sizeof(pkt)); + pkt.raw.buf = (char *) buf; + pkt.raw.len = len; + pkt.eth = (struct eth *) buf; + // mg_hexdump(buf, len > 16 ? 16: len); + if (pkt.raw.len < sizeof(*pkt.eth)) return; // Truncated - runt? + if (ifp->enable_mac_check && + memcmp(pkt.eth->dst, ifp->mac, sizeof(pkt.eth->dst)) != 0 && + memcmp(pkt.eth->dst, broadcast, sizeof(pkt.eth->dst)) != 0) + return; + if (ifp->enable_crc32_check && len > 4) { + len -= 4; // TODO(scaprile): check on bigendian + uint32_t crc = mg_crc32(0, (const char *) buf, len); + if (memcmp((void *) ((size_t) buf + len), &crc, sizeof(crc))) return; + } + if (pkt.eth->type == mg_htons(0x806)) { + pkt.arp = (struct arp *) (pkt.eth + 1); + if (sizeof(*pkt.eth) + sizeof(*pkt.arp) > pkt.raw.len) return; // Truncated + mg_tcpip_call(ifp, MG_TCPIP_EV_ARP, &pkt.raw); + rx_arp(ifp, &pkt); + } else if (pkt.eth->type == mg_htons(0x86dd)) { + pkt.ip6 = (struct ip6 *) (pkt.eth + 1); + if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip6)) return; // Truncated + if ((pkt.ip6->ver >> 4) != 0x6) return; // Not IP + mkpay(&pkt, pkt.ip6 + 1); + rx_ip6(ifp, &pkt); + } else if (pkt.eth->type == mg_htons(0x800)) { + pkt.ip = (struct ip *) (pkt.eth + 1); + if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return; // Truncated + // Truncate frame to what IP header tells us + if ((size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth) < pkt.raw.len) { + pkt.raw.len = (size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth); + } + if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return; // Truncated + if ((pkt.ip->ver >> 4) != 4) return; // Not IP + mkpay(&pkt, pkt.ip + 1); + rx_ip(ifp, &pkt); + } else { + MG_DEBUG(("Unknown eth type %x", mg_htons(pkt.eth->type))); + if (mg_log_level >= MG_LL_VERBOSE) mg_hexdump(buf, len >= 32 ? 32 : len); + } +} -struct ip { - uint8_t ver; // Version - uint8_t tos; // Unused - uint16_t len; // Length - uint16_t id; // Unused - uint16_t frag; // Fragmentation -#define IP_FRAG_OFFSET_MSK 0x1fff -#define IP_MORE_FRAGS_MSK 0x2000 - uint8_t ttl; // Time to live - uint8_t proto; // Upper level protocol - uint16_t csum; // Checksum - uint32_t src; // Source IP - uint32_t dst; // Destination IP -}; +static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) { + struct mg_connection *c; + bool expired_1000ms = mg_timer_expired(&ifp->timer_1000ms, 1000, now); + ifp->now = now; -struct ip6 { - uint8_t ver; // Version - uint8_t opts[3]; // Options - uint16_t len; // Length - uint8_t proto; // Upper level protocol - uint8_t ttl; // Time to live - uint8_t src[16]; // Source IP - uint8_t dst[16]; // Destination IP -}; +#if MG_ENABLE_TCPIP_PRINT_DEBUG_STATS + if (expired_1000ms) { + const char *names[] = {"down", "up", "req", "ip", "ready"}; + MG_INFO(("Status: %s, IP: %M, rx:%u, tx:%u, dr:%u, er:%u", + names[ifp->state], mg_print_ip4, &ifp->ip, ifp->nrecv, ifp->nsent, + ifp->ndrop, ifp->nerr)); + } +#endif + // Handle gw ARP request timeout, order is important + if (expired_1000ms && ifp->state == MG_TCPIP_STATE_IP) { + ifp->state = MG_TCPIP_STATE_READY; // keep best-effort MAC + onstatechange(ifp); + } + // Handle physical interface up/down status + if (expired_1000ms && ifp->driver->up) { + bool up = ifp->driver->up(ifp); + bool current = ifp->state != MG_TCPIP_STATE_DOWN; + if (!up && ifp->enable_dhcp_client) ifp->ip = 0; + if (up != current) { // link state has changed + ifp->state = up == false ? MG_TCPIP_STATE_DOWN + : ifp->enable_dhcp_client || ifp->ip == 0 + ? MG_TCPIP_STATE_UP + : MG_TCPIP_STATE_IP; + onstatechange(ifp); + } else if (!ifp->enable_dhcp_client && ifp->state == MG_TCPIP_STATE_UP && + ifp->ip) { + ifp->state = MG_TCPIP_STATE_IP; // ifp->fn has set an IP + onstatechange(ifp); + } + if (ifp->state == MG_TCPIP_STATE_DOWN) MG_ERROR(("Network is down")); + mg_tcpip_call(ifp, MG_TCPIP_EV_TIMER_1S, NULL); + } + if (ifp->state == MG_TCPIP_STATE_DOWN) return; -struct icmp { - uint8_t type; - uint8_t code; - uint16_t csum; -}; + // DHCP RFC-2131 (4.4) + if (ifp->enable_dhcp_client && expired_1000ms) { + if (ifp->state == MG_TCPIP_STATE_UP) { + tx_dhcp_discover(ifp); // INIT (4.4.1) + } else if (ifp->state == MG_TCPIP_STATE_READY && + ifp->lease_expire > 0) { // BOUND / RENEWING / REBINDING + if (ifp->now >= ifp->lease_expire) { + ifp->state = MG_TCPIP_STATE_UP, ifp->ip = 0; // expired, release IP + onstatechange(ifp); + } else if (ifp->now + 30UL * 60UL * 1000UL > ifp->lease_expire && + ((ifp->now / 1000) % 60) == 0) { + // hack: 30 min before deadline, try to rebind (4.3.6) every min + tx_dhcp_request_re(ifp, (uint8_t *) broadcast, ifp->ip, 0xffffffff); + } // TODO(): Handle T1 (RENEWING) and T2 (REBINDING) (4.4.5) + } + } -struct arp { - uint16_t fmt; // Format of hardware address - uint16_t pro; // Format of protocol address - uint8_t hlen; // Length of hardware address - uint8_t plen; // Length of protocol address - uint16_t op; // Operation - uint8_t sha[6]; // Sender hardware address - uint32_t spa; // Sender protocol address - uint8_t tha[6]; // Target hardware address - uint32_t tpa; // Target protocol address -}; + // Read data from the network + if (ifp->driver->rx != NULL) { // Polling driver. We must call it + size_t len = + ifp->driver->rx(ifp->recv_queue.buf, ifp->recv_queue.size, ifp); + if (len > 0) { + ifp->nrecv++; + mg_tcpip_rx(ifp, ifp->recv_queue.buf, len); + } + } else { // Interrupt-based driver. Fills recv queue itself + char *buf; + size_t len = mg_queue_next(&ifp->recv_queue, &buf); + if (len > 0) { + mg_tcpip_rx(ifp, buf, len); + mg_queue_del(&ifp->recv_queue, len); + } + } -struct tcp { - uint16_t sport; // Source port - uint16_t dport; // Destination port - uint32_t seq; // Sequence number - uint32_t ack; // Acknowledgement number - uint8_t off; // Data offset - uint8_t flags; // TCP flags -#define TH_FIN 0x01 -#define TH_SYN 0x02 -#define TH_RST 0x04 -#define TH_PUSH 0x08 -#define TH_ACK 0x10 -#define TH_URG 0x20 -#define TH_ECE 0x40 -#define TH_CWR 0x80 - uint16_t win; // Window - uint16_t csum; // Checksum - uint16_t urp; // Urgent pointer -}; + // Process timeouts + for (c = ifp->mgr->conns; c != NULL; c = c->next) { + if ((c->is_udp && !c->is_arplooking) || c->is_listening || c->is_resolving) + continue; + struct connstate *s = (struct connstate *) (c + 1); + uint32_t rem_ip; + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t)); + if (now > s->timer) { + if (s->ttype == MIP_TTYPE_ARP) { + mg_error(c, "ARP timeout"); + } else if (c->is_udp) { + continue; + } else if (s->ttype == MIP_TTYPE_ACK && s->acked != s->ack) { + MG_VERBOSE(("%lu ack %x %x", c->id, s->seq, s->ack)); + tx_tcp(ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port, + mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0); + s->acked = s->ack; + } else if (s->ttype == MIP_TTYPE_SYN) { + mg_error(c, "Connection timeout"); + } else if (s->ttype == MIP_TTYPE_FIN) { + c->is_closing = 1; + continue; + } else { + if (s->tmiss++ > 2) { + mg_error(c, "keepalive"); + } else { + MG_VERBOSE(("%lu keepalive", c->id)); + tx_tcp(ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port, + mg_htonl(s->seq - 1), mg_htonl(s->ack), NULL, 0); + } + } -struct udp { - uint16_t sport; // Source port - uint16_t dport; // Destination port - uint16_t len; // UDP length - uint16_t csum; // UDP checksum -}; + settmout(c, MIP_TTYPE_KEEPALIVE); + } + } +} -struct dhcp { - uint8_t op, htype, hlen, hops; - uint32_t xid; - uint16_t secs, flags; - uint32_t ciaddr, yiaddr, siaddr, giaddr; - uint8_t hwaddr[208]; - uint32_t magic; - uint8_t options[32]; -}; +// This function executes in interrupt context, thus it should copy data +// somewhere fast. Note that newlib's malloc is not thread safe, thus use +// our lock-free queue with preallocated buffer to copy data and return asap +void mg_tcpip_qwrite(void *buf, size_t len, struct mg_tcpip_if *ifp) { + char *p; + if (mg_queue_book(&ifp->recv_queue, &p, len) >= len) { + memcpy(p, buf, len); + mg_queue_add(&ifp->recv_queue, len); + ifp->nrecv++; + } else { + ifp->ndrop++; + } +} -#pragma pack(pop) +void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) { + // If MAC address is not set, make a random one + if (ifp->mac[0] == 0 && ifp->mac[1] == 0 && ifp->mac[2] == 0 && + ifp->mac[3] == 0 && ifp->mac[4] == 0 && ifp->mac[5] == 0) { + ifp->mac[0] = 0x02; // Locally administered, unicast + mg_random(&ifp->mac[1], sizeof(ifp->mac) - 1); + MG_INFO(("MAC not set. Generated random: %M", mg_print_mac, ifp->mac)); + } -struct pkt { - struct mg_str raw; // Raw packet data - struct mg_str pay; // Payload data - struct eth *eth; - struct llc *llc; - struct arp *arp; - struct ip *ip; - struct ip6 *ip6; - struct icmp *icmp; - struct tcp *tcp; - struct udp *udp; - struct dhcp *dhcp; -}; + if (ifp->driver->init && !ifp->driver->init(ifp)) { + MG_ERROR(("driver init failed")); + } else { + size_t framesize = 1540; + ifp->tx.buf = (char *) calloc(1, framesize), ifp->tx.len = framesize; + if (ifp->recv_queue.size == 0) + ifp->recv_queue.size = ifp->driver->rx ? framesize : 8192; + ifp->recv_queue.buf = (char *) calloc(1, ifp->recv_queue.size); + ifp->timer_1000ms = mg_millis(); + mgr->priv = ifp; + ifp->mgr = mgr; + ifp->mtu = MG_TCPIP_MTU_DEFAULT; + mgr->extraconnsize = sizeof(struct connstate); + if (ifp->ip == 0) ifp->enable_dhcp_client = true; + memset(ifp->gwmac, 255, sizeof(ifp->gwmac)); // Set best-effort to bcast + mg_random(&ifp->eport, sizeof(ifp->eport)); // Random from 0 to 65535 + ifp->eport |= MG_EPHEMERAL_PORT_BASE; // Random from + // MG_EPHEMERAL_PORT_BASE to 65535 + if (ifp->tx.buf == NULL || ifp->recv_queue.buf == NULL) MG_ERROR(("OOM")); + } +} -static void mg_tcpip_call(struct mg_tcpip_if *ifp, int ev, void *ev_data) { - if (ifp->fn != NULL) ifp->fn(ifp, ev, ev_data); +void mg_tcpip_free(struct mg_tcpip_if *ifp) { + free(ifp->recv_queue.buf); + free(ifp->tx.buf); } -static void send_syn(struct mg_connection *c); +static void send_syn(struct mg_connection *c) { + struct connstate *s = (struct connstate *) (c + 1); + uint32_t isn = mg_htonl((uint32_t) mg_ntohs(c->loc.port)); + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv; + uint32_t rem_ip; + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t)); + tx_tcp(ifp, s->mac, rem_ip, TH_SYN, c->loc.port, c->rem.port, isn, 0, NULL, + 0); +} -static void mkpay(struct pkt *pkt, void *p) { - pkt->pay = - mg_str_n((char *) p, (size_t) (&pkt->raw.buf[pkt->raw.len] - (char *) p)); +static void mac_resolved(struct mg_connection *c) { + if (c->is_udp) { + c->is_connecting = 0; + mg_call(c, MG_EV_CONNECT, NULL); + } else { + send_syn(c); + settmout(c, MIP_TTYPE_SYN); + } } -static uint32_t csumup(uint32_t sum, const void *buf, size_t len) { - size_t i; - const uint8_t *p = (const uint8_t *) buf; - for (i = 0; i < len; i++) sum += i & 1 ? p[i] : (uint32_t) (p[i] << 8); - return sum; +void mg_connect_resolved(struct mg_connection *c) { + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv; + uint32_t rem_ip; + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t)); + c->is_resolving = 0; + if (ifp->eport < MG_EPHEMERAL_PORT_BASE) ifp->eport = MG_EPHEMERAL_PORT_BASE; + memcpy(c->loc.ip, &ifp->ip, sizeof(uint32_t)); + c->loc.port = mg_htons(ifp->eport++); + MG_DEBUG(("%lu %M -> %M", c->id, mg_print_ip_port, &c->loc, mg_print_ip_port, + &c->rem)); + mg_call(c, MG_EV_RESOLVE, NULL); + c->is_connecting = 1; + if (c->is_udp && (rem_ip == 0xffffffff || rem_ip == (ifp->ip | ~ifp->mask))) { + struct connstate *s = (struct connstate *) (c + 1); + memset(s->mac, 0xFF, sizeof(s->mac)); // global or local broadcast + mac_resolved(c); + } else if (ifp->ip && ((rem_ip & ifp->mask) == (ifp->ip & ifp->mask)) && + rem_ip != ifp->gw) { // skip if gw (onstatechange -> READY -> ARP) + // If we're in the same LAN, fire an ARP lookup. + MG_DEBUG(("%lu ARP lookup...", c->id)); + mg_tcpip_arp_request(ifp, rem_ip, NULL); + settmout(c, MIP_TTYPE_ARP); + c->is_arplooking = 1; + } else if ((*((uint8_t *) &rem_ip) & 0xE0) == 0xE0) { + struct connstate *s = (struct connstate *) (c + 1); // 224 to 239, E0 to EF + uint8_t mcastp[3] = {0x01, 0x00, 0x5E}; // multicast group + memcpy(s->mac, mcastp, 3); + memcpy(s->mac + 3, ((uint8_t *) &rem_ip) + 1, 3); // 23 LSb + s->mac[3] &= 0x7F; + mac_resolved(c); + } else { + struct connstate *s = (struct connstate *) (c + 1); + memcpy(s->mac, ifp->gwmac, sizeof(ifp->gwmac)); + mac_resolved(c); + } } -static uint16_t csumfin(uint32_t sum) { - while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16); - return mg_htons(~sum & 0xffff); +bool mg_open_listener(struct mg_connection *c, const char *url) { + c->loc.port = mg_htons(mg_url_port(url)); + return true; } -static uint16_t ipcsum(const void *buf, size_t len) { - uint32_t sum = csumup(0, buf, len); - return csumfin(sum); +static void write_conn(struct mg_connection *c) { + long len = c->is_tls ? mg_tls_send(c, c->send.buf, c->send.len) + : mg_io_send(c, c->send.buf, c->send.len); + if (len == MG_IO_ERR) { + mg_error(c, "tx err"); + } else if (len > 0) { + mg_iobuf_del(&c->send, 0, (size_t) len); + mg_call(c, MG_EV_WRITE, &len); + } } -static void settmout(struct mg_connection *c, uint8_t type) { - struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv; +static void init_closure(struct mg_connection *c) { struct connstate *s = (struct connstate *) (c + 1); - unsigned n = type == MIP_TTYPE_ACK ? MIP_TCP_ACK_MS - : type == MIP_TTYPE_ARP ? MIP_ARP_RESP_MS - : type == MIP_TTYPE_SYN ? MIP_TCP_SYN_MS - : type == MIP_TTYPE_FIN ? MIP_TCP_FIN_MS - : MIP_TCP_KEEPALIVE_MS; - s->timer = ifp->now + n; - s->ttype = type; - MG_VERBOSE(("%lu %d -> %llx", c->id, type, s->timer)); + if (c->is_udp == false && c->is_listening == false && + c->is_connecting == false) { // For TCP conns, + struct mg_tcpip_if *ifp = + (struct mg_tcpip_if *) c->mgr->priv; // send TCP FIN + uint32_t rem_ip; + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t)); + tx_tcp(ifp, s->mac, rem_ip, TH_FIN | TH_ACK, c->loc.port, c->rem.port, + mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0); + settmout(c, MIP_TTYPE_FIN); + } } -static size_t ether_output(struct mg_tcpip_if *ifp, size_t len) { - size_t n = ifp->driver->tx(ifp->tx.buf, len, ifp); - if (n == len) ifp->nsent++; - return n; +static void close_conn(struct mg_connection *c) { + struct connstate *s = (struct connstate *) (c + 1); + mg_iobuf_free(&s->raw); // For TLS connections, release raw data + mg_close_conn(c); } -void mg_tcpip_arp_request(struct mg_tcpip_if *ifp, uint32_t ip, uint8_t *mac) { - struct eth *eth = (struct eth *) ifp->tx.buf; - struct arp *arp = (struct arp *) (eth + 1); - memset(eth->dst, 255, sizeof(eth->dst)); - memcpy(eth->src, ifp->mac, sizeof(eth->src)); - eth->type = mg_htons(0x806); - memset(arp, 0, sizeof(*arp)); - arp->fmt = mg_htons(1), arp->pro = mg_htons(0x800), arp->hlen = 6, - arp->plen = 4; - arp->op = mg_htons(1), arp->tpa = ip, arp->spa = ifp->ip; - memcpy(arp->sha, ifp->mac, sizeof(arp->sha)); - if (mac != NULL) memcpy(arp->tha, mac, sizeof(arp->tha)); - ether_output(ifp, PDIFF(eth, arp + 1)); +static bool can_write(struct mg_connection *c) { + return c->is_connecting == 0 && c->is_resolving == 0 && c->send.len > 0 && + c->is_tls_hs == 0 && c->is_arplooking == 0; } -static void onstatechange(struct mg_tcpip_if *ifp) { - if (ifp->state == MG_TCPIP_STATE_READY) { - MG_INFO(("READY, IP: %M", mg_print_ip4, &ifp->ip)); - MG_INFO((" GW: %M", mg_print_ip4, &ifp->gw)); - MG_INFO((" MAC: %M", mg_print_mac, &ifp->mac)); - } else if (ifp->state == MG_TCPIP_STATE_IP) { - MG_ERROR(("Got IP")); - mg_tcpip_arp_request(ifp, ifp->gw, NULL); // unsolicited GW ARP request - } else if (ifp->state == MG_TCPIP_STATE_UP) { - MG_ERROR(("Link up")); - srand((unsigned int) mg_millis()); - } else if (ifp->state == MG_TCPIP_STATE_DOWN) { - MG_ERROR(("Link down")); +void mg_mgr_poll(struct mg_mgr *mgr, int ms) { + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) mgr->priv; + struct mg_connection *c, *tmp; + uint64_t now = mg_millis(); + mg_timer_poll(&mgr->timers, now); + if (ifp == NULL || ifp->driver == NULL) return; + mg_tcpip_poll(ifp, now); + for (c = mgr->conns; c != NULL; c = tmp) { + tmp = c->next; + struct connstate *s = (struct connstate *) (c + 1); + mg_call(c, MG_EV_POLL, &now); + MG_VERBOSE(("%lu .. %c%c%c%c%c", c->id, c->is_tls ? 'T' : 't', + c->is_connecting ? 'C' : 'c', c->is_tls_hs ? 'H' : 'h', + c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c')); + if (c->is_tls && mg_tls_pending(c) > 0) + handle_tls_recv(c, (struct mg_iobuf *) &c->rtls); + if (can_write(c)) write_conn(c); + if (c->is_draining && c->send.len == 0 && s->ttype != MIP_TTYPE_FIN) + init_closure(c); + if (c->is_closing) close_conn(c); } - mg_tcpip_call(ifp, MG_TCPIP_EV_ST_CHG, &ifp->state); + (void) ms; } -static struct ip *tx_ip(struct mg_tcpip_if *ifp, uint8_t *mac_dst, - uint8_t proto, uint32_t ip_src, uint32_t ip_dst, - size_t plen) { - struct eth *eth = (struct eth *) ifp->tx.buf; - struct ip *ip = (struct ip *) (eth + 1); - memcpy(eth->dst, mac_dst, sizeof(eth->dst)); - memcpy(eth->src, ifp->mac, sizeof(eth->src)); // Use our MAC - eth->type = mg_htons(0x800); - memset(ip, 0, sizeof(*ip)); - ip->ver = 0x45; // Version 4, header length 5 words - ip->frag = mg_htons(0x4000); // Don't fragment - ip->len = mg_htons((uint16_t) (sizeof(*ip) + plen)); - ip->ttl = 64; - ip->proto = proto; - ip->src = ip_src; - ip->dst = ip_dst; - ip->csum = ipcsum(ip, sizeof(*ip)); - return ip; +bool mg_send(struct mg_connection *c, const void *buf, size_t len) { + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv; + bool res = false; + uint32_t rem_ip; + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t)); + if (ifp->ip == 0 || ifp->state != MG_TCPIP_STATE_READY) { + mg_error(c, "net down"); + } else if (c->is_udp && (c->is_arplooking || c->is_resolving)) { + // Fail to send, no target MAC or IP + MG_VERBOSE(("still resolving...")); + } else if (c->is_udp) { + struct connstate *s = (struct connstate *) (c + 1); + len = trim_len(c, len); // Trimming length if necessary + tx_udp(ifp, s->mac, ifp->ip, c->loc.port, rem_ip, c->rem.port, buf, len); + res = true; + } else { + res = mg_iobuf_add(&c->send, c->send.len, buf, len); + } + return res; } +#endif // MG_ENABLE_TCPIP -static void tx_udp(struct mg_tcpip_if *ifp, uint8_t *mac_dst, uint32_t ip_src, - uint16_t sport, uint32_t ip_dst, uint16_t dport, - const void *buf, size_t len) { - struct ip *ip = - tx_ip(ifp, mac_dst, 17, ip_src, ip_dst, len + sizeof(struct udp)); - struct udp *udp = (struct udp *) (ip + 1); - // MG_DEBUG(("UDP XX LEN %d %d", (int) len, (int) ifp->tx.len)); - udp->sport = sport; - udp->dport = dport; - udp->len = mg_htons((uint16_t) (sizeof(*udp) + len)); - udp->csum = 0; - uint32_t cs = csumup(0, udp, sizeof(*udp)); - cs = csumup(cs, buf, len); - cs = csumup(cs, &ip->src, sizeof(ip->src)); - cs = csumup(cs, &ip->dst, sizeof(ip->dst)); - cs += (uint32_t) (ip->proto + sizeof(*udp) + len); - udp->csum = csumfin(cs); - memmove(udp + 1, buf, len); - // MG_DEBUG(("UDP LEN %d %d", (int) len, (int) ifp->frame_len)); - ether_output(ifp, sizeof(struct eth) + sizeof(*ip) + sizeof(*udp) + len); -} +#ifdef MG_ENABLE_LINES +#line 1 "src/ota_ch32v307.c" +#endif -static void tx_dhcp(struct mg_tcpip_if *ifp, uint8_t *mac_dst, uint32_t ip_src, - uint32_t ip_dst, uint8_t *opts, size_t optslen, - bool ciaddr) { - // https://datatracker.ietf.org/doc/html/rfc2132#section-9.6 - struct dhcp dhcp = {1, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}}; - dhcp.magic = mg_htonl(0x63825363); - memcpy(&dhcp.hwaddr, ifp->mac, sizeof(ifp->mac)); - memcpy(&dhcp.xid, ifp->mac + 2, sizeof(dhcp.xid)); - memcpy(&dhcp.options, opts, optslen); - if (ciaddr) dhcp.ciaddr = ip_src; - tx_udp(ifp, mac_dst, ip_src, mg_htons(68), ip_dst, mg_htons(67), &dhcp, - sizeof(dhcp)); -} -static const uint8_t broadcast[] = {255, 255, 255, 255, 255, 255}; -// RFC-2131 #4.3.6, #4.4.1; RFC-2132 #9.8 -static void tx_dhcp_request_sel(struct mg_tcpip_if *ifp, uint32_t ip_req, - uint32_t ip_srv) { - uint8_t opts[] = { - 53, 1, 3, // Type: DHCP request - 12, 3, 'm', 'i', 'p', // Host name: "mip" - 54, 4, 0, 0, 0, 0, // DHCP server ID - 50, 4, 0, 0, 0, 0, // Requested IP - 55, 2, 1, 3, 255, 255, // GW, mask [DNS] [SNTP] - 255 // End of options - }; - uint8_t addopts = 0; - memcpy(opts + 10, &ip_srv, sizeof(ip_srv)); - memcpy(opts + 16, &ip_req, sizeof(ip_req)); - if (ifp->enable_req_dns) opts[24 + addopts++] = 6; // DNS - if (ifp->enable_req_sntp) opts[24 + addopts++] = 42; // SNTP - opts[21] += addopts; - tx_dhcp(ifp, (uint8_t *) broadcast, 0, 0xffffffff, opts, - sizeof(opts) + addopts - 2, false); - MG_DEBUG(("DHCP req sent")); + +#if MG_OTA == MG_OTA_CH32V307 +// RM: https://www.wch-ic.com/downloads/CH32FV2x_V3xRM_PDF.html + +static bool mg_ch32v307_write(void *, const void *, size_t); +static bool mg_ch32v307_swap(void); + +static struct mg_flash s_mg_flash_ch32v307 = { + (void *) 0x08000000, // Start + 480 * 1024, // Size, first 320k is 0-wait + 4 * 1024, // Sector size, 4k + 4, // Align, 32 bit + mg_ch32v307_write, + mg_ch32v307_swap, +}; + +#define FLASH_BASE 0x40022000 +#define FLASH_ACTLR (FLASH_BASE + 0) +#define FLASH_KEYR (FLASH_BASE + 4) +#define FLASH_OBKEYR (FLASH_BASE + 8) +#define FLASH_STATR (FLASH_BASE + 12) +#define FLASH_CTLR (FLASH_BASE + 16) +#define FLASH_ADDR (FLASH_BASE + 20) +#define FLASH_OBR (FLASH_BASE + 28) +#define FLASH_WPR (FLASH_BASE + 32) + +MG_IRAM static void flash_unlock(void) { + static bool unlocked; + if (unlocked == false) { + MG_REG(FLASH_KEYR) = 0x45670123; + MG_REG(FLASH_KEYR) = 0xcdef89ab; + unlocked = true; + } +} + +MG_IRAM static void flash_wait(void) { + while (MG_REG(FLASH_STATR) & MG_BIT(0)) (void) 0; } -// RFC-2131 #4.3.6, #4.4.5 (renewing: unicast, rebinding: bcast) -static void tx_dhcp_request_re(struct mg_tcpip_if *ifp, uint8_t *mac_dst, - uint32_t ip_src, uint32_t ip_dst) { - uint8_t opts[] = { - 53, 1, 3, // Type: DHCP request - 255 // End of options - }; - tx_dhcp(ifp, mac_dst, ip_src, ip_dst, opts, sizeof(opts), true); - MG_DEBUG(("DHCP req sent")); +MG_IRAM static void mg_ch32v307_erase(void *addr) { + // MG_INFO(("%p", addr)); + flash_unlock(); + flash_wait(); + MG_REG(FLASH_ADDR) = (uint32_t) addr; + MG_REG(FLASH_CTLR) |= MG_BIT(1) | MG_BIT(6); // PER | STRT; + flash_wait(); } -static void tx_dhcp_discover(struct mg_tcpip_if *ifp) { - uint8_t opts[] = { - 53, 1, 1, // Type: DHCP discover - 55, 2, 1, 3, // Parameters: ip, mask - 255 // End of options - }; - tx_dhcp(ifp, (uint8_t *) broadcast, 0, 0xffffffff, opts, sizeof(opts), false); - MG_DEBUG(("DHCP discover sent. Our MAC: %M", mg_print_mac, ifp->mac)); +MG_IRAM static bool is_page_boundary(const void *addr) { + uint32_t val = (uint32_t) addr; + return (val & (s_mg_flash_ch32v307.secsz - 1)) == 0; } -static struct mg_connection *getpeer(struct mg_mgr *mgr, struct pkt *pkt, - bool lsn) { - struct mg_connection *c = NULL; - for (c = mgr->conns; c != NULL; c = c->next) { - if (c->is_arplooking && pkt->arp && - memcmp(&pkt->arp->spa, c->rem.ip, sizeof(pkt->arp->spa)) == 0) - break; - if (c->is_udp && pkt->udp && c->loc.port == pkt->udp->dport) break; - if (!c->is_udp && pkt->tcp && c->loc.port == pkt->tcp->dport && - lsn == c->is_listening && (lsn || c->rem.port == pkt->tcp->sport)) - break; +MG_IRAM static bool mg_ch32v307_write(void *addr, const void *buf, size_t len) { + // MG_INFO(("%p %p %lu", addr, buf, len)); + // mg_hexdump(buf, len); + flash_unlock(); + const uint16_t *src = (uint16_t *) buf, *end = &src[len / 2]; + uint16_t *dst = (uint16_t *) addr; + MG_REG(FLASH_CTLR) |= MG_BIT(0); // Set PG + // MG_INFO(("CTLR: %#lx", MG_REG(FLASH_CTLR))); + while (src < end) { + if (is_page_boundary(dst)) mg_ch32v307_erase(dst); + *dst++ = *src++; + flash_wait(); } - return c; + MG_REG(FLASH_CTLR) &= ~MG_BIT(0); // Clear PG + return true; } -static void mac_resolved(struct mg_connection *c); +MG_IRAM bool mg_ch32v307_swap(void) { + return true; +} -static void rx_arp(struct mg_tcpip_if *ifp, struct pkt *pkt) { - if (pkt->arp->op == mg_htons(1) && pkt->arp->tpa == ifp->ip) { - // ARP request. Make a response, then send - // MG_DEBUG(("ARP op %d %M: %M", mg_ntohs(pkt->arp->op), mg_print_ip4, - // &pkt->arp->spa, mg_print_ip4, &pkt->arp->tpa)); - struct eth *eth = (struct eth *) ifp->tx.buf; - struct arp *arp = (struct arp *) (eth + 1); - memcpy(eth->dst, pkt->eth->src, sizeof(eth->dst)); - memcpy(eth->src, ifp->mac, sizeof(eth->src)); - eth->type = mg_htons(0x806); - *arp = *pkt->arp; - arp->op = mg_htons(2); - memcpy(arp->tha, pkt->arp->sha, sizeof(pkt->arp->tha)); - memcpy(arp->sha, ifp->mac, sizeof(pkt->arp->sha)); - arp->tpa = pkt->arp->spa; - arp->spa = ifp->ip; - MG_DEBUG(("ARP: tell %M we're %M", mg_print_ip4, &arp->tpa, mg_print_mac, - &ifp->mac)); - ether_output(ifp, PDIFF(eth, arp + 1)); - } else if (pkt->arp->op == mg_htons(2)) { - if (memcmp(pkt->arp->tha, ifp->mac, sizeof(pkt->arp->tha)) != 0) return; - if (pkt->arp->spa == ifp->gw) { - // Got response for the GW ARP request. Set ifp->gwmac and IP -> READY - memcpy(ifp->gwmac, pkt->arp->sha, sizeof(ifp->gwmac)); - if (ifp->state == MG_TCPIP_STATE_IP) { - ifp->state = MG_TCPIP_STATE_READY; - onstatechange(ifp); - } - } else { - struct mg_connection *c = getpeer(ifp->mgr, pkt, false); - if (c != NULL && c->is_arplooking) { - struct connstate *s = (struct connstate *) (c + 1); - memcpy(s->mac, pkt->arp->sha, sizeof(s->mac)); - MG_DEBUG(("%lu ARP resolved %M -> %M", c->id, mg_print_ip4, c->rem.ip, - mg_print_mac, s->mac)); - c->is_arplooking = 0; - mac_resolved(c); - } - } +// just overwrite instead of swap +MG_IRAM static void single_bank_swap(char *p1, char *p2, size_t s, size_t ss) { + // no stdlib calls here + for (size_t ofs = 0; ofs < s; ofs += ss) { + mg_ch32v307_write(p1 + ofs, p2 + ofs, ss); } + *((volatile uint32_t *) 0xbeef0000) |= 1U << 7; // NVIC_SystemReset() } -static void rx_icmp(struct mg_tcpip_if *ifp, struct pkt *pkt) { - // MG_DEBUG(("ICMP %d", (int) len)); - if (pkt->icmp->type == 8 && pkt->ip != NULL && pkt->ip->dst == ifp->ip) { - size_t hlen = sizeof(struct eth) + sizeof(struct ip) + sizeof(struct icmp); - size_t space = ifp->tx.len - hlen, plen = pkt->pay.len; - if (plen > space) plen = space; - struct ip *ip = tx_ip(ifp, pkt->eth->src, 1, ifp->ip, pkt->ip->src, - sizeof(struct icmp) + plen); - struct icmp *icmp = (struct icmp *) (ip + 1); - memset(icmp, 0, sizeof(*icmp)); // Set csum to 0 - memcpy(icmp + 1, pkt->pay.buf, plen); // Copy RX payload to TX - icmp->csum = ipcsum(icmp, sizeof(*icmp) + plen); - ether_output(ifp, hlen + plen); - } +bool mg_ota_begin(size_t new_firmware_size) { + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_ch32v307); } -static void rx_dhcp_client(struct mg_tcpip_if *ifp, struct pkt *pkt) { - uint32_t ip = 0, gw = 0, mask = 0, lease = 0, dns = 0, sntp = 0; - uint8_t msgtype = 0, state = ifp->state; - // perform size check first, then access fields - uint8_t *p = pkt->dhcp->options, - *end = (uint8_t *) &pkt->raw.buf[pkt->raw.len]; - if (end < (uint8_t *) (pkt->dhcp + 1)) return; - if (memcmp(&pkt->dhcp->xid, ifp->mac + 2, sizeof(pkt->dhcp->xid))) return; - while (p + 1 < end && p[0] != 255) { // Parse options RFC-1533 #9 - if (p[0] == 1 && p[1] == sizeof(ifp->mask) && p + 6 < end) { // Mask - memcpy(&mask, p + 2, sizeof(mask)); - } else if (p[0] == 3 && p[1] == sizeof(ifp->gw) && p + 6 < end) { // GW - memcpy(&gw, p + 2, sizeof(gw)); - ip = pkt->dhcp->yiaddr; - } else if (ifp->enable_req_dns && p[0] == 6 && p[1] == sizeof(dns) && - p + 6 < end) { // DNS - memcpy(&dns, p + 2, sizeof(dns)); - } else if (ifp->enable_req_sntp && p[0] == 42 && p[1] == sizeof(sntp) && - p + 6 < end) { // SNTP - memcpy(&sntp, p + 2, sizeof(sntp)); - } else if (p[0] == 51 && p[1] == 4 && p + 6 < end) { // Lease - memcpy(&lease, p + 2, sizeof(lease)); - lease = mg_ntohl(lease); - } else if (p[0] == 53 && p[1] == 1 && p + 6 < end) { // Msg Type - msgtype = p[2]; - } - p += p[1] + 2; - } - // Process message type, RFC-1533 (9.4); RFC-2131 (3.1, 4) - if (msgtype == 6 && ifp->ip == ip) { // DHCPNACK, release IP - ifp->state = MG_TCPIP_STATE_UP, ifp->ip = 0; - } else if (msgtype == 2 && ifp->state == MG_TCPIP_STATE_UP && ip && gw && - lease) { // DHCPOFFER - // select IP, (4.4.1) (fallback to IP source addr on foul play) - tx_dhcp_request_sel(ifp, ip, - pkt->dhcp->siaddr ? pkt->dhcp->siaddr : pkt->ip->src); - ifp->state = MG_TCPIP_STATE_REQ; // REQUESTING state - } else if (msgtype == 5) { // DHCPACK - if (ifp->state == MG_TCPIP_STATE_REQ && ip && gw && lease) { // got an IP - ifp->lease_expire = ifp->now + lease * 1000; - MG_INFO(("Lease: %u sec (%lld)", lease, ifp->lease_expire / 1000)); - // assume DHCP server = router until ARP resolves - memcpy(ifp->gwmac, pkt->eth->src, sizeof(ifp->gwmac)); - ifp->ip = ip, ifp->gw = gw, ifp->mask = mask; - ifp->state = MG_TCPIP_STATE_IP; // BOUND state - uint64_t rand; - mg_random(&rand, sizeof(rand)); - srand((unsigned int) (rand + mg_millis())); - if (ifp->enable_req_dns && dns != 0) - mg_tcpip_call(ifp, MG_TCPIP_EV_DHCP_DNS, &dns); - if (ifp->enable_req_sntp && sntp != 0) - mg_tcpip_call(ifp, MG_TCPIP_EV_DHCP_SNTP, &sntp); - } else if (ifp->state == MG_TCPIP_STATE_READY && ifp->ip == ip) { // renew - ifp->lease_expire = ifp->now + lease * 1000; - MG_INFO(("Lease: %u sec (%lld)", lease, ifp->lease_expire / 1000)); - } // TODO(): accept provided T1/T2 and store server IP for renewal (4.4) - } - if (ifp->state != state) onstatechange(ifp); +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_ch32v307); } -// Simple DHCP server that assigns a next IP address: ifp->ip + 1 -static void rx_dhcp_server(struct mg_tcpip_if *ifp, struct pkt *pkt) { - uint8_t op = 0, *p = pkt->dhcp->options, - *end = (uint8_t *) &pkt->raw.buf[pkt->raw.len]; - if (end < (uint8_t *) (pkt->dhcp + 1)) return; - // struct dhcp *req = pkt->dhcp; - struct dhcp res = {2, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}}; - res.yiaddr = ifp->ip; - ((uint8_t *) (&res.yiaddr))[3]++; // Offer our IP + 1 - while (p + 1 < end && p[0] != 255) { // Parse options - if (p[0] == 53 && p[1] == 1 && p + 2 < end) { // Message type - op = p[2]; - } - p += p[1] + 2; - } - if (op == 1 || op == 3) { // DHCP Discover or DHCP Request - uint8_t msg = op == 1 ? 2 : 5; // Message type: DHCP OFFER or DHCP ACK - uint8_t opts[] = { - 53, 1, msg, // Message type - 1, 4, 0, 0, 0, 0, // Subnet mask - 54, 4, 0, 0, 0, 0, // Server ID - 12, 3, 'm', 'i', 'p', // Host name: "mip" - 51, 4, 255, 255, 255, 255, // Lease time - 255 // End of options - }; - memcpy(&res.hwaddr, pkt->dhcp->hwaddr, 6); - memcpy(opts + 5, &ifp->mask, sizeof(ifp->mask)); - memcpy(opts + 11, &ifp->ip, sizeof(ifp->ip)); - memcpy(&res.options, opts, sizeof(opts)); - res.magic = pkt->dhcp->magic; - res.xid = pkt->dhcp->xid; - if (ifp->enable_get_gateway) { - ifp->gw = res.yiaddr; // set gw IP, best-effort gwmac as DHCP server's - memcpy(ifp->gwmac, pkt->eth->src, sizeof(ifp->gwmac)); - } - tx_udp(ifp, pkt->eth->src, ifp->ip, mg_htons(67), - op == 1 ? ~0U : res.yiaddr, mg_htons(68), &res, sizeof(res)); +bool mg_ota_end(void) { + if (mg_ota_flash_end(&s_mg_flash_ch32v307)) { + // Swap partitions. Pray power does not go away + MG_INFO(("Swapping partitions, size %u (%u sectors)", + s_mg_flash_ch32v307.size, + s_mg_flash_ch32v307.size / s_mg_flash_ch32v307.secsz)); + MG_INFO(("Do NOT power off...")); + mg_log_level = MG_LL_NONE; + // TODO() disable IRQ, s_flash_irq_disabled = true; + // Runs in RAM, will reset when finished + single_bank_swap( + (char *) s_mg_flash_ch32v307.start, + (char *) s_mg_flash_ch32v307.start + s_mg_flash_ch32v307.size / 2, + s_mg_flash_ch32v307.size / 2, s_mg_flash_ch32v307.secsz); } + return false; } +#endif -static void rx_udp(struct mg_tcpip_if *ifp, struct pkt *pkt) { - struct mg_connection *c = getpeer(ifp->mgr, pkt, true); - if (c == NULL) { - // No UDP listener on this port. Should send ICMP, but keep silent. - } else { - c->rem.port = pkt->udp->sport; - memcpy(c->rem.ip, &pkt->ip->src, sizeof(uint32_t)); - struct connstate *s = (struct connstate *) (c + 1); - memcpy(s->mac, pkt->eth->src, sizeof(s->mac)); - if (c->recv.len >= MG_MAX_RECV_SIZE) { - mg_error(c, "max_recv_buf_size reached"); - } else if (c->recv.size - c->recv.len < pkt->pay.len && - !mg_iobuf_resize(&c->recv, c->recv.len + pkt->pay.len)) { - mg_error(c, "oom"); - } else { - memcpy(&c->recv.buf[c->recv.len], pkt->pay.buf, pkt->pay.len); - c->recv.len += pkt->pay.len; - mg_call(c, MG_EV_READ, &pkt->pay.len); - } - } +#ifdef MG_ENABLE_LINES +#line 1 "src/ota_dummy.c" +#endif + + + +#if MG_OTA == MG_OTA_NONE +bool mg_ota_begin(size_t new_firmware_size) { + (void) new_firmware_size; + return true; +} +bool mg_ota_write(const void *buf, size_t len) { + (void) buf, (void) len; + return true; +} +bool mg_ota_end(void) { + return true; } +#endif -static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *dst_mac, uint32_t dst_ip, - uint8_t flags, uint16_t sport, uint16_t dport, - uint32_t seq, uint32_t ack, const void *buf, size_t len) { -#if 0 - uint8_t opts[] = {2, 4, 5, 0xb4, 4, 2, 0, 0}; // MSS = 1460, SACK permitted - if (flags & TH_SYN) { - // Handshake? Set MSS - buf = opts; - len = sizeof(opts); - } +#ifdef MG_ENABLE_LINES +#line 1 "src/ota_esp32.c" #endif - struct ip *ip = - tx_ip(ifp, dst_mac, 6, ifp->ip, dst_ip, sizeof(struct tcp) + len); - struct tcp *tcp = (struct tcp *) (ip + 1); - memset(tcp, 0, sizeof(*tcp)); - if (buf != NULL && len) memmove(tcp + 1, buf, len); - tcp->sport = sport; - tcp->dport = dport; - tcp->seq = seq; - tcp->ack = ack; - tcp->flags = flags; - tcp->win = mg_htons(MIP_TCP_WIN); - tcp->off = (uint8_t) (sizeof(*tcp) / 4 << 4); - // if (flags & TH_SYN) tcp->off = 0x70; // Handshake? header size 28 bytes - uint32_t cs = 0; - uint16_t n = (uint16_t) (sizeof(*tcp) + len); - uint8_t pseudo[] = {0, ip->proto, (uint8_t) (n >> 8), (uint8_t) (n & 255)}; - cs = csumup(cs, tcp, n); - cs = csumup(cs, &ip->src, sizeof(ip->src)); - cs = csumup(cs, &ip->dst, sizeof(ip->dst)); - cs = csumup(cs, pseudo, sizeof(pseudo)); - tcp->csum = csumfin(cs); - MG_VERBOSE(("TCP %M:%hu -> %M:%hu fl %x len %u", mg_print_ip4, &ip->src, - mg_ntohs(tcp->sport), mg_print_ip4, &ip->dst, - mg_ntohs(tcp->dport), tcp->flags, len)); - // mg_hexdump(ifp->tx.buf, PDIFF(ifp->tx.buf, tcp + 1) + len); - return ether_output(ifp, PDIFF(ifp->tx.buf, tcp + 1) + len); + +#if MG_ARCH == MG_ARCH_ESP32 && MG_OTA == MG_OTA_ESP32 + +static const esp_partition_t *s_ota_update_partition; +static esp_ota_handle_t s_ota_update_handle; +static bool s_ota_success; + +// Those empty macros do nothing, but mark places in the code which could +// potentially trigger a watchdog reboot due to the log flash erase operation +#define disable_wdt() +#define enable_wdt() + +bool mg_ota_begin(size_t new_firmware_size) { + if (s_ota_update_partition != NULL) { + MG_ERROR(("Update in progress. Call mg_ota_end() ?")); + return false; + } else { + s_ota_success = false; + disable_wdt(); + s_ota_update_partition = esp_ota_get_next_update_partition(NULL); + esp_err_t err = esp_ota_begin(s_ota_update_partition, new_firmware_size, + &s_ota_update_handle); + enable_wdt(); + MG_DEBUG(("esp_ota_begin(): %d", err)); + s_ota_success = (err == ESP_OK); + } + return s_ota_success; } -static size_t tx_tcp_pkt(struct mg_tcpip_if *ifp, struct pkt *pkt, - uint8_t flags, uint32_t seq, const void *buf, - size_t len) { - uint32_t delta = (pkt->tcp->flags & (TH_SYN | TH_FIN)) ? 1 : 0; - return tx_tcp(ifp, pkt->eth->src, pkt->ip->src, flags, pkt->tcp->dport, - pkt->tcp->sport, seq, mg_htonl(mg_ntohl(pkt->tcp->seq) + delta), - buf, len); +bool mg_ota_write(const void *buf, size_t len) { + disable_wdt(); + esp_err_t err = esp_ota_write(s_ota_update_handle, buf, len); + enable_wdt(); + MG_INFO(("esp_ota_write(): %d", err)); + s_ota_success = err == ESP_OK; + return s_ota_success; } -static struct mg_connection *accept_conn(struct mg_connection *lsn, - struct pkt *pkt) { - struct mg_connection *c = mg_alloc_conn(lsn->mgr); - if (c == NULL) { - MG_ERROR(("OOM")); - return NULL; +bool mg_ota_end(void) { + esp_err_t err = esp_ota_end(s_ota_update_handle); + MG_DEBUG(("esp_ota_end(%p): %d", s_ota_update_handle, err)); + if (s_ota_success && err == ESP_OK) { + err = esp_ota_set_boot_partition(s_ota_update_partition); + s_ota_success = (err == ESP_OK); } - struct connstate *s = (struct connstate *) (c + 1); - s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq); - memcpy(s->mac, pkt->eth->src, sizeof(s->mac)); - settmout(c, MIP_TTYPE_KEEPALIVE); - memcpy(c->rem.ip, &pkt->ip->src, sizeof(uint32_t)); - c->rem.port = pkt->tcp->sport; - MG_DEBUG(("%lu accepted %M", c->id, mg_print_ip_port, &c->rem)); - LIST_ADD_HEAD(struct mg_connection, &lsn->mgr->conns, c); - c->is_accepted = 1; - c->is_hexdumping = lsn->is_hexdumping; - c->pfn = lsn->pfn; - c->loc = lsn->loc; - c->pfn_data = lsn->pfn_data; - c->fn = lsn->fn; - c->fn_data = lsn->fn_data; - mg_call(c, MG_EV_OPEN, NULL); - mg_call(c, MG_EV_ACCEPT, NULL); - return c; + MG_DEBUG(("Finished ESP32 OTA, success: %d", s_ota_success)); + s_ota_update_partition = NULL; + return s_ota_success; } -static size_t trim_len(struct mg_connection *c, size_t len) { - struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv; - size_t eth_h_len = 14, ip_max_h_len = 24, tcp_max_h_len = 60, udp_h_len = 8; - size_t max_headers_len = - eth_h_len + ip_max_h_len + (c->is_udp ? udp_h_len : tcp_max_h_len); - size_t min_mtu = c->is_udp ? 68 /* RFC-791 */ : max_headers_len - eth_h_len; +#endif - // If the frame exceeds the available buffer, trim the length - if (len + max_headers_len > ifp->tx.len) { - len = ifp->tx.len - max_headers_len; - } - // Ensure the MTU isn't lower than the minimum allowed value - if (ifp->mtu < min_mtu) { - MG_ERROR(("MTU is lower than minimum, capping to %lu", min_mtu)); - ifp->mtu = (uint16_t) min_mtu; - } - // If the total packet size exceeds the MTU, trim the length - if (len + max_headers_len - eth_h_len > ifp->mtu) { - len = ifp->mtu - max_headers_len + eth_h_len; - if (c->is_udp) { - MG_ERROR(("UDP datagram exceeds MTU. Truncating it.")); - } +#ifdef MG_ENABLE_LINES +#line 1 "src/ota_imxrt.c" +#endif + + + + +#if MG_OTA == MG_OTA_RT1020 || MG_OTA == MG_OTA_RT1060 + +static bool mg_imxrt_write(void *, const void *, size_t); +static bool mg_imxrt_swap(void); + +// TODO(): fill at init, support more devices in a dynamic way +// TODO(): then, check alignment is <= 256, see Wizard's #251 +static struct mg_flash s_mg_flash_imxrt = { + (void *) 0x60000000, // Start, + 8 * 1024 * 1024, // Size, 8mb + 4 * 1024, // Sector size, 4k + 256, // Align, + mg_imxrt_write, + mg_imxrt_swap, +}; + +struct mg_flexspi_lut_seq { + uint8_t seqNum; + uint8_t seqId; + uint16_t reserved; +}; + +struct mg_flexspi_mem_config { + uint32_t tag; + uint32_t version; + uint32_t reserved0; + uint8_t readSampleClkSrc; + uint8_t csHoldTime; + uint8_t csSetupTime; + uint8_t columnAddressWidth; + uint8_t deviceModeCfgEnable; + uint8_t deviceModeType; + uint16_t waitTimeCfgCommands; + struct mg_flexspi_lut_seq deviceModeSeq; + uint32_t deviceModeArg; + uint8_t configCmdEnable; + uint8_t configModeType[3]; + struct mg_flexspi_lut_seq configCmdSeqs[3]; + uint32_t reserved1; + uint32_t configCmdArgs[3]; + uint32_t reserved2; + uint32_t controllerMiscOption; + uint8_t deviceType; + uint8_t sflashPadType; + uint8_t serialClkFreq; + uint8_t lutCustomSeqEnable; + uint32_t reserved3[2]; + uint32_t sflashA1Size; + uint32_t sflashA2Size; + uint32_t sflashB1Size; + uint32_t sflashB2Size; + uint32_t csPadSettingOverride; + uint32_t sclkPadSettingOverride; + uint32_t dataPadSettingOverride; + uint32_t dqsPadSettingOverride; + uint32_t timeoutInMs; + uint32_t commandInterval; + uint16_t dataValidTime[2]; + uint16_t busyOffset; + uint16_t busyBitPolarity; + uint32_t lookupTable[64]; + struct mg_flexspi_lut_seq lutCustomSeq[12]; + uint32_t reserved4[4]; +}; + +struct mg_flexspi_nor_config { + struct mg_flexspi_mem_config memConfig; + uint32_t pageSize; + uint32_t sectorSize; + uint8_t ipcmdSerialClkFreq; + uint8_t isUniformBlockSize; + uint8_t reserved0[2]; + uint8_t serialNorType; + uint8_t needExitNoCmdMode; + uint8_t halfClkForNonReadCmd; + uint8_t needRestoreNoCmdMode; + uint32_t blockSize; + uint32_t reserve2[11]; +}; + +/* FLEXSPI memory config block related defintions */ +#define MG_FLEXSPI_CFG_BLK_TAG (0x42464346UL) // ascii "FCFB" Big Endian +#define MG_FLEXSPI_CFG_BLK_VERSION (0x56010400UL) // V1.4.0 + +#define MG_FLEXSPI_LUT_SEQ(cmd0, pad0, op0, cmd1, pad1, op1) \ + (MG_FLEXSPI_LUT_OPERAND0(op0) | MG_FLEXSPI_LUT_NUM_PADS0(pad0) | \ + MG_FLEXSPI_LUT_OPCODE0(cmd0) | MG_FLEXSPI_LUT_OPERAND1(op1) | \ + MG_FLEXSPI_LUT_NUM_PADS1(pad1) | MG_FLEXSPI_LUT_OPCODE1(cmd1)) + +#define MG_CMD_SDR 0x01 +#define MG_CMD_DDR 0x21 +#define MG_DUMMY_SDR 0x0C +#define MG_DUMMY_DDR 0x2C +#define MG_RADDR_SDR 0x02 +#define MG_RADDR_DDR 0x22 +#define MG_READ_SDR 0x09 +#define MG_READ_DDR 0x29 +#define MG_WRITE_SDR 0x08 +#define MG_WRITE_DDR 0x28 +#define MG_STOP 0 + +#define MG_FLEXSPI_1PAD 0 +#define MG_FLEXSPI_2PAD 1 +#define MG_FLEXSPI_4PAD 2 +#define MG_FLEXSPI_8PAD 3 + +#define MG_FLEXSPI_QSPI_LUT \ + { \ + [0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0xEB, MG_RADDR_SDR, \ + MG_FLEXSPI_4PAD, 0x18), \ + [1] = MG_FLEXSPI_LUT_SEQ(MG_DUMMY_SDR, MG_FLEXSPI_4PAD, 0x06, MG_READ_SDR, \ + MG_FLEXSPI_4PAD, 0x04), \ + [4 * 1 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x05, \ + MG_READ_SDR, MG_FLEXSPI_1PAD, 0x04), \ + [4 * 3 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x06, \ + MG_STOP, MG_FLEXSPI_1PAD, 0x0), \ + [4 * 5 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x20, \ + MG_RADDR_SDR, MG_FLEXSPI_1PAD, 0x18), \ + [4 * 8 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0xD8, \ + MG_RADDR_SDR, MG_FLEXSPI_1PAD, 0x18), \ + [4 * 9 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x02, \ + MG_RADDR_SDR, MG_FLEXSPI_1PAD, 0x18), \ + [4 * 9 + 1] = MG_FLEXSPI_LUT_SEQ(MG_WRITE_SDR, MG_FLEXSPI_1PAD, 0x04, \ + MG_STOP, MG_FLEXSPI_1PAD, 0x0), \ + [4 * 11 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x60, \ + MG_STOP, MG_FLEXSPI_1PAD, 0x0), \ } - return len; -} +#define MG_FLEXSPI_LUT_OPERAND0(x) (((uint32_t) (((uint32_t) (x)))) & 0xFFU) +#define MG_FLEXSPI_LUT_NUM_PADS0(x) \ + (((uint32_t) (((uint32_t) (x)) << 8U)) & 0x300U) +#define MG_FLEXSPI_LUT_OPCODE0(x) \ + (((uint32_t) (((uint32_t) (x)) << 10U)) & 0xFC00U) +#define MG_FLEXSPI_LUT_OPERAND1(x) \ + (((uint32_t) (((uint32_t) (x)) << 16U)) & 0xFF0000U) +#define MG_FLEXSPI_LUT_NUM_PADS1(x) \ + (((uint32_t) (((uint32_t) (x)) << 24U)) & 0x3000000U) +#define MG_FLEXSPI_LUT_OPCODE1(x) \ + (((uint32_t) (((uint32_t) (x)) << 26U)) & 0xFC000000U) + +#define FLEXSPI_NOR_INSTANCE 0 + +#if MG_OTA == MG_OTA_RT1020 +struct mg_flexspi_nor_driver_interface { + uint32_t version; + int (*init)(uint32_t instance, struct mg_flexspi_nor_config *config); + int (*program)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t dst_addr, const uint32_t *src); + uint32_t reserved; + int (*erase)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t start, uint32_t lengthInBytes); + uint32_t reserved2; + int (*update_lut)(uint32_t instance, uint32_t seqIndex, + const uint32_t *lutBase, uint32_t seqNumber); + int (*xfer)(uint32_t instance, char *xfer); + void (*clear_cache)(uint32_t instance); +}; +#elif MG_OTA == MG_OTA_RT1060 +struct mg_flexspi_nor_driver_interface { + uint32_t version; + int (*init)(uint32_t instance, struct mg_flexspi_nor_config *config); + int (*program)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t dst_addr, const uint32_t *src); + int (*erase_all)(uint32_t instance, struct mg_flexspi_nor_config *config); + int (*erase)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t start, uint32_t lengthInBytes); + int (*read)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t *dst, uint32_t addr, uint32_t lengthInBytes); + void (*clear_cache)(uint32_t instance); + int (*xfer)(uint32_t instance, char *xfer); + int (*update_lut)(uint32_t instance, uint32_t seqIndex, + const uint32_t *lutBase, uint32_t seqNumber); + int (*get_config)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t *option); +}; +#endif + +#define flexspi_nor \ + (*((struct mg_flexspi_nor_driver_interface **) (*(uint32_t *) 0x0020001c + \ + 16))) -long mg_io_send(struct mg_connection *c, const void *buf, size_t len) { - struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv; - struct connstate *s = (struct connstate *) (c + 1); - uint32_t dst_ip = *(uint32_t *) c->rem.ip; - len = trim_len(c, len); - if (c->is_udp) { - tx_udp(ifp, s->mac, ifp->ip, c->loc.port, dst_ip, c->rem.port, buf, len); - } else { - size_t sent = - tx_tcp(ifp, s->mac, dst_ip, TH_PUSH | TH_ACK, c->loc.port, c->rem.port, - mg_htonl(s->seq), mg_htonl(s->ack), buf, len); - if (sent == 0) { - return MG_IO_WAIT; - } else if (sent == (size_t) -1) { - return MG_IO_ERR; - } else { - s->seq += (uint32_t) len; - if (s->ttype == MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_KEEPALIVE); - } - } - return (long) len; -} +static bool s_flash_irq_disabled; -static void handle_tls_recv(struct mg_connection *c, struct mg_iobuf *io) { - long n = mg_tls_recv(c, &io->buf[io->len], io->size - io->len); - if (n == MG_IO_ERR) { - mg_error(c, "TLS recv error"); - } else if (n > 0) { - // Decrypted successfully - trigger MG_EV_READ - io->len += (size_t) n; - mg_call(c, MG_EV_READ, &n); - } +MG_IRAM static bool flash_page_start(volatile uint32_t *dst) { + char *base = (char *) s_mg_flash_imxrt.start, *end = base + s_mg_flash_imxrt.size; + volatile char *p = (char *) dst; + return p >= base && p < end && ((p - base) % s_mg_flash_imxrt.secsz) == 0; } -static void read_conn(struct mg_connection *c, struct pkt *pkt) { - struct connstate *s = (struct connstate *) (c + 1); - struct mg_iobuf *io = c->is_tls ? &c->rtls : &c->recv; - uint32_t seq = mg_ntohl(pkt->tcp->seq); - uint32_t rem_ip; - memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t)); - if (pkt->tcp->flags & TH_FIN) { - // If we initiated the closure, we reply with ACK upon receiving FIN - // If we didn't initiate it, we reply with FIN as part of the normal TCP - // closure process - uint8_t flags = TH_ACK; - s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len + 1); - if (c->is_draining && s->ttype == MIP_TTYPE_FIN) { - if (s->seq == mg_htonl(pkt->tcp->ack)) { // Simultaneous closure ? - s->seq++; // Yes. Increment our SEQ - } else { // Otherwise, - s->seq = mg_htonl(pkt->tcp->ack); // Set to peer's ACK - } - } else { - flags |= TH_FIN; - c->is_draining = 1; - settmout(c, MIP_TTYPE_FIN); - } - tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip, flags, - c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), "", 0); - } else if (pkt->pay.len == 0) { - // TODO(cpq): handle this peer's ACK - } else if (seq != s->ack) { - uint32_t ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len); - if (s->ack == ack) { - MG_VERBOSE(("ignoring duplicate pkt")); - } else { - MG_VERBOSE(("SEQ != ACK: %x %x %x", seq, s->ack, ack)); - tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip, TH_ACK, - c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), "", - 0); - } - } else if (io->size - io->len < pkt->pay.len && - !mg_iobuf_resize(io, io->len + pkt->pay.len)) { - mg_error(c, "oom"); - } else { - // Copy TCP payload into the IO buffer. If the connection is plain text, - // we copy to c->recv. If the connection is TLS, this data is encrypted, - // therefore we copy that encrypted data to the c->rtls iobuffer instead, - // and then call mg_tls_recv() to decrypt it. NOTE: mg_tls_recv() will - // call back mg_io_recv() which grabs raw data from c->rtls - memcpy(&io->buf[io->len], pkt->pay.buf, pkt->pay.len); - io->len += pkt->pay.len; - - MG_VERBOSE(("%lu SEQ %x -> %x", c->id, mg_htonl(pkt->tcp->seq), s->ack)); - // Advance ACK counter - s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len); - s->unacked += pkt->pay.len; - // size_t diff = s->acked <= s->ack ? s->ack - s->acked : s->ack; - if (s->unacked > MIP_TCP_WIN / 2 && s->acked != s->ack) { - // Send ACK immediately - MG_VERBOSE(("%lu imm ACK %lu", c->id, s->acked)); - tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip, TH_ACK, - c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), NULL, - 0); - s->unacked = 0; - s->acked = s->ack; - if (s->ttype != MIP_TTYPE_KEEPALIVE) settmout(c, MIP_TTYPE_KEEPALIVE); - } else { - // if not already running, setup a timer to send an ACK later - if (s->ttype != MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_ACK); - } +// Note: the get_config function below works both for RT1020 and 1060 +#if MG_OTA == MG_OTA_RT1020 +MG_IRAM static int flexspi_nor_get_config( + struct mg_flexspi_nor_config *config) { + struct mg_flexspi_nor_config default_config = { + .memConfig = {.tag = MG_FLEXSPI_CFG_BLK_TAG, + .version = MG_FLEXSPI_CFG_BLK_VERSION, + .readSampleClkSrc = 1, // ReadSampleClk_LoopbackFromDqsPad + .csHoldTime = 3, + .csSetupTime = 3, + .controllerMiscOption = MG_BIT(4), + .deviceType = 1, // serial NOR + .sflashPadType = 4, + .serialClkFreq = 7, // 133MHz + .sflashA1Size = 8 * 1024 * 1024, + .lookupTable = MG_FLEXSPI_QSPI_LUT}, + .pageSize = 256, + .sectorSize = 4 * 1024, + .ipcmdSerialClkFreq = 1, + .blockSize = 64 * 1024, + .isUniformBlockSize = false}; - if (c->is_tls && c->is_tls_hs) { - mg_tls_handshake(c); - } else if (c->is_tls) { - // TLS connection. Make room for decrypted data in c->recv - io = &c->recv; - if (io->size - io->len < pkt->pay.len && - !mg_iobuf_resize(io, io->len + pkt->pay.len)) { - mg_error(c, "oom"); - } else { - // Decrypt data directly into c->recv - handle_tls_recv(c, io); - } - } else { - // Plain text connection, data is already in c->recv, trigger - // MG_EV_READ - mg_call(c, MG_EV_READ, &pkt->pay.len); - } - } + *config = default_config; + return 0; } +#else +MG_IRAM static int flexspi_nor_get_config( + struct mg_flexspi_nor_config *config) { + uint32_t options[] = {0xc0000000, 0x00}; -static void rx_tcp(struct mg_tcpip_if *ifp, struct pkt *pkt) { - struct mg_connection *c = getpeer(ifp->mgr, pkt, false); - struct connstate *s = c == NULL ? NULL : (struct connstate *) (c + 1); -#if 0 - MG_INFO(("%lu %hhu %d", c ? c->id : 0, pkt->tcp->flags, (int) pkt->pay.len)); -#endif - if (c != NULL && c->is_connecting && pkt->tcp->flags == (TH_SYN | TH_ACK)) { - s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq) + 1; - tx_tcp_pkt(ifp, pkt, TH_ACK, pkt->tcp->ack, NULL, 0); - c->is_connecting = 0; // Client connected - settmout(c, MIP_TTYPE_KEEPALIVE); - mg_call(c, MG_EV_CONNECT, NULL); // Let user know - if (c->is_tls_hs) mg_tls_handshake(c); - } else if (c != NULL && c->is_connecting && pkt->tcp->flags != TH_ACK) { - // mg_hexdump(pkt->raw.buf, pkt->raw.len); - tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0); - } else if (c != NULL && pkt->tcp->flags & TH_RST) { - mg_error(c, "peer RST"); // RFC-1122 4.2.2.13 - } else if (c != NULL) { -#if 0 - MG_DEBUG(("%lu %d %M:%hu -> %M:%hu", c->id, (int) pkt->raw.len, - mg_print_ip4, &pkt->ip->src, mg_ntohs(pkt->tcp->sport), - mg_print_ip4, &pkt->ip->dst, mg_ntohs(pkt->tcp->dport))); - mg_hexdump(pkt->pay.buf, pkt->pay.len); -#endif - s->tmiss = 0; // Reset missed keep-alive counter - if (s->ttype == MIP_TTYPE_KEEPALIVE) // Advance keep-alive timer - settmout(c, - MIP_TTYPE_KEEPALIVE); // unless a former ACK timeout is pending - read_conn(c, pkt); // Override timer with ACK timeout if needed - } else if ((c = getpeer(ifp->mgr, pkt, true)) == NULL) { - tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0); - } else if (pkt->tcp->flags & TH_RST) { - if (c->is_accepted) mg_error(c, "peer RST"); // RFC-1122 4.2.2.13 - // ignore RST if not connected - } else if (pkt->tcp->flags & TH_SYN) { - // Use peer's source port as ISN, in order to recognise the handshake - uint32_t isn = mg_htonl((uint32_t) mg_ntohs(pkt->tcp->sport)); - tx_tcp_pkt(ifp, pkt, TH_SYN | TH_ACK, isn, NULL, 0); - } else if (pkt->tcp->flags & TH_FIN) { - tx_tcp_pkt(ifp, pkt, TH_FIN | TH_ACK, pkt->tcp->ack, NULL, 0); - } else if (mg_htonl(pkt->tcp->ack) == mg_htons(pkt->tcp->sport) + 1U) { - accept_conn(c, pkt); - } else if (!c->is_accepted) { // no peer - tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0); - } else { - // MG_VERBOSE(("dropped silently..")); + MG_ARM_DISABLE_IRQ(); + uint32_t status = + flexspi_nor->get_config(FLEXSPI_NOR_INSTANCE, config, options); + if (!s_flash_irq_disabled) { + MG_ARM_ENABLE_IRQ(); + } + if (status) { + MG_ERROR(("Failed to extract flash configuration: status %u", status)); } + return status; } +#endif -static void rx_ip(struct mg_tcpip_if *ifp, struct pkt *pkt) { - uint16_t frag = mg_ntohs(pkt->ip->frag); - if (frag & IP_MORE_FRAGS_MSK || frag & IP_FRAG_OFFSET_MSK) { - if (pkt->ip->proto == 17) pkt->udp = (struct udp *) (pkt->ip + 1); - if (pkt->ip->proto == 6) pkt->tcp = (struct tcp *) (pkt->ip + 1); - struct mg_connection *c = getpeer(ifp->mgr, pkt, false); - if (c) mg_error(c, "Received fragmented packet"); - } else if (pkt->ip->proto == 1) { - pkt->icmp = (struct icmp *) (pkt->ip + 1); - if (pkt->pay.len < sizeof(*pkt->icmp)) return; - mkpay(pkt, pkt->icmp + 1); - rx_icmp(ifp, pkt); - } else if (pkt->ip->proto == 17) { - pkt->udp = (struct udp *) (pkt->ip + 1); - if (pkt->pay.len < sizeof(*pkt->udp)) return; - mkpay(pkt, pkt->udp + 1); - MG_VERBOSE(("UDP %M:%hu -> %M:%hu len %u", mg_print_ip4, &pkt->ip->src, - mg_ntohs(pkt->udp->sport), mg_print_ip4, &pkt->ip->dst, - mg_ntohs(pkt->udp->dport), (int) pkt->pay.len)); - if (ifp->enable_dhcp_client && pkt->udp->dport == mg_htons(68)) { - pkt->dhcp = (struct dhcp *) (pkt->udp + 1); - mkpay(pkt, pkt->dhcp + 1); - rx_dhcp_client(ifp, pkt); - } else if (ifp->enable_dhcp_server && pkt->udp->dport == mg_htons(67)) { - pkt->dhcp = (struct dhcp *) (pkt->udp + 1); - mkpay(pkt, pkt->dhcp + 1); - rx_dhcp_server(ifp, pkt); - } else { - rx_udp(ifp, pkt); - } - } else if (pkt->ip->proto == 6) { - pkt->tcp = (struct tcp *) (pkt->ip + 1); - if (pkt->pay.len < sizeof(*pkt->tcp)) return; - mkpay(pkt, pkt->tcp + 1); - uint16_t iplen = mg_ntohs(pkt->ip->len); - uint16_t off = (uint16_t) (sizeof(*pkt->ip) + ((pkt->tcp->off >> 4) * 4U)); - if (iplen >= off) pkt->pay.len = (size_t) (iplen - off); - MG_VERBOSE(("TCP %M:%hu -> %M:%hu len %u", mg_print_ip4, &pkt->ip->src, - mg_ntohs(pkt->tcp->sport), mg_print_ip4, &pkt->ip->dst, - mg_ntohs(pkt->tcp->dport), (int) pkt->pay.len)); - rx_tcp(ifp, pkt); +MG_IRAM static bool flash_erase(struct mg_flexspi_nor_config *config, + void *addr) { + if (flash_page_start(addr) == false) { + MG_ERROR(("%p is not on a sector boundary", addr)); + return false; } + + void *dst = (void *) ((char *) addr - (char *) s_mg_flash_imxrt.start); + + bool ok = (flexspi_nor->erase(FLEXSPI_NOR_INSTANCE, config, (uint32_t) dst, + s_mg_flash_imxrt.secsz) == 0); + MG_DEBUG(("Sector starting at %p erasure: %s", addr, ok ? "ok" : "fail")); + return ok; } -static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) { - // MG_DEBUG(("IP %d", (int) len)); - if (pkt->ip6->proto == 1 || pkt->ip6->proto == 58) { - pkt->icmp = (struct icmp *) (pkt->ip6 + 1); - if (pkt->pay.len < sizeof(*pkt->icmp)) return; - mkpay(pkt, pkt->icmp + 1); - rx_icmp(ifp, pkt); - } else if (pkt->ip6->proto == 17) { - pkt->udp = (struct udp *) (pkt->ip6 + 1); - if (pkt->pay.len < sizeof(*pkt->udp)) return; - // MG_DEBUG((" UDP %u %u -> %u", len, mg_htons(udp->sport), - // mg_htons(udp->dport))); - mkpay(pkt, pkt->udp + 1); - } +#if 0 +// standalone erase call +MG_IRAM static bool mg_imxrt_erase(void *addr) { + struct mg_flexspi_nor_config config; + bool ret; + // Interrupts must be disabled before calls to ROM API in RT1020 and 1060 + MG_ARM_DISABLE_IRQ(); + ret = (flexspi_nor_get_config(&config) == 0); + if (ret) ret = flash_erase(&config, addr); + MG_ARM_ENABLE_IRQ(); + return ret; } +#endif -static void mg_tcpip_rx(struct mg_tcpip_if *ifp, void *buf, size_t len) { - struct pkt pkt; - memset(&pkt, 0, sizeof(pkt)); - pkt.raw.buf = (char *) buf; - pkt.raw.len = len; - pkt.eth = (struct eth *) buf; - // mg_hexdump(buf, len > 16 ? 16: len); - if (pkt.raw.len < sizeof(*pkt.eth)) return; // Truncated - runt? - if (ifp->enable_mac_check && - memcmp(pkt.eth->dst, ifp->mac, sizeof(pkt.eth->dst)) != 0 && - memcmp(pkt.eth->dst, broadcast, sizeof(pkt.eth->dst)) != 0) - return; - if (ifp->enable_crc32_check && len > 4) { - len -= 4; // TODO(scaprile): check on bigendian - uint32_t crc = mg_crc32(0, (const char *) buf, len); - if (memcmp((void *) ((size_t) buf + len), &crc, sizeof(crc))) return; - } - if (pkt.eth->type == mg_htons(0x806)) { - pkt.arp = (struct arp *) (pkt.eth + 1); - if (sizeof(*pkt.eth) + sizeof(*pkt.arp) > pkt.raw.len) return; // Truncated - mg_tcpip_call(ifp, MG_TCPIP_EV_ARP, &pkt.raw); - rx_arp(ifp, &pkt); - } else if (pkt.eth->type == mg_htons(0x86dd)) { - pkt.ip6 = (struct ip6 *) (pkt.eth + 1); - if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip6)) return; // Truncated - if ((pkt.ip6->ver >> 4) != 0x6) return; // Not IP - mkpay(&pkt, pkt.ip6 + 1); - rx_ip6(ifp, &pkt); - } else if (pkt.eth->type == mg_htons(0x800)) { - pkt.ip = (struct ip *) (pkt.eth + 1); - if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return; // Truncated - // Truncate frame to what IP header tells us - if ((size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth) < pkt.raw.len) { - pkt.raw.len = (size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth); - } - if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return; // Truncated - if ((pkt.ip->ver >> 4) != 4) return; // Not IP - mkpay(&pkt, pkt.ip + 1); - rx_ip(ifp, &pkt); - } else { - MG_DEBUG(("Unknown eth type %x", mg_htons(pkt.eth->type))); - if (mg_log_level >= MG_LL_VERBOSE) mg_hexdump(buf, len >= 32 ? 32 : len); - } +MG_IRAM bool mg_imxrt_swap(void) { + return true; } -static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) { - struct mg_connection *c; - bool expired_1000ms = mg_timer_expired(&ifp->timer_1000ms, 1000, now); - ifp->now = now; +static inline void spin(volatile uint32_t count) { + while (count--) (void) 0; +} -#if MG_ENABLE_TCPIP_PRINT_DEBUG_STATS - if (expired_1000ms) { - const char *names[] = {"down", "up", "req", "ip", "ready"}; - MG_INFO(("Status: %s, IP: %M, rx:%u, tx:%u, dr:%u, er:%u", - names[ifp->state], mg_print_ip4, &ifp->ip, ifp->nrecv, ifp->nsent, - ifp->ndrop, ifp->nerr)); - } -#endif - // Handle gw ARP request timeout, order is important - if (expired_1000ms && ifp->state == MG_TCPIP_STATE_IP) { - ifp->state = MG_TCPIP_STATE_READY; // keep best-effort MAC - onstatechange(ifp); +static inline void flash_wait(void) { + while ((*((volatile uint32_t *) (0x402A8000 + 0xE0)) & MG_BIT(1)) == 0) + spin(1); +} + +MG_IRAM static bool mg_imxrt_write(void *addr, const void *buf, size_t len) { + struct mg_flexspi_nor_config config; + bool ok = false; + // Interrupts must be disabled before calls to ROM API in RT1020 and 1060 + MG_ARM_DISABLE_IRQ(); + if (flexspi_nor_get_config(&config) != 0) goto fwxit; + if ((len % s_mg_flash_imxrt.align) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, s_mg_flash_imxrt.align)); + goto fwxit; } - // Handle physical interface up/down status - if (expired_1000ms && ifp->driver->up) { - bool up = ifp->driver->up(ifp); - bool current = ifp->state != MG_TCPIP_STATE_DOWN; - if (!up && ifp->enable_dhcp_client) ifp->ip = 0; - if (up != current) { // link state has changed - ifp->state = up == false ? MG_TCPIP_STATE_DOWN - : ifp->enable_dhcp_client || ifp->ip == 0 - ? MG_TCPIP_STATE_UP - : MG_TCPIP_STATE_IP; - onstatechange(ifp); - } else if (!ifp->enable_dhcp_client && ifp->state == MG_TCPIP_STATE_UP && - ifp->ip) { - ifp->state = MG_TCPIP_STATE_IP; // ifp->fn has set an IP - onstatechange(ifp); - } - if (ifp->state == MG_TCPIP_STATE_DOWN) MG_ERROR(("Network is down")); - mg_tcpip_call(ifp, MG_TCPIP_EV_TIMER_1S, NULL); + if ((char *) addr < (char *) s_mg_flash_imxrt.start) { + MG_ERROR(("Invalid flash write address: %p", addr)); + goto fwxit; } - if (ifp->state == MG_TCPIP_STATE_DOWN) return; - // DHCP RFC-2131 (4.4) - if (ifp->enable_dhcp_client && expired_1000ms) { - if (ifp->state == MG_TCPIP_STATE_UP) { - tx_dhcp_discover(ifp); // INIT (4.4.1) - } else if (ifp->state == MG_TCPIP_STATE_READY && - ifp->lease_expire > 0) { // BOUND / RENEWING / REBINDING - if (ifp->now >= ifp->lease_expire) { - ifp->state = MG_TCPIP_STATE_UP, ifp->ip = 0; // expired, release IP - onstatechange(ifp); - } else if (ifp->now + 30UL * 60UL * 1000UL > ifp->lease_expire && - ((ifp->now / 1000) % 60) == 0) { - // hack: 30 min before deadline, try to rebind (4.3.6) every min - tx_dhcp_request_re(ifp, (uint8_t *) broadcast, ifp->ip, 0xffffffff); - } // TODO(): Handle T1 (RENEWING) and T2 (REBINDING) (4.4.5) - } - } + uint32_t *dst = (uint32_t *) addr; + uint32_t *src = (uint32_t *) buf; + uint32_t *end = (uint32_t *) ((char *) buf + len); + ok = true; - // Read data from the network - if (ifp->driver->rx != NULL) { // Polling driver. We must call it - size_t len = - ifp->driver->rx(ifp->recv_queue.buf, ifp->recv_queue.size, ifp); - if (len > 0) { - ifp->nrecv++; - mg_tcpip_rx(ifp, ifp->recv_queue.buf, len); - } - } else { // Interrupt-based driver. Fills recv queue itself - char *buf; - size_t len = mg_queue_next(&ifp->recv_queue, &buf); - if (len > 0) { - mg_tcpip_rx(ifp, buf, len); - mg_queue_del(&ifp->recv_queue, len); + while (ok && src < end) { + if (flash_page_start(dst) && flash_erase(&config, dst) == false) { + ok = false; + break; } - } + uint32_t status; + uint32_t dst_ofs = (uint32_t) dst - (uint32_t) s_mg_flash_imxrt.start; + if ((char *) buf >= (char *) s_mg_flash_imxrt.start) { + // If we copy from FLASH to FLASH, then we first need to copy the source + // to RAM + size_t tmp_buf_size = s_mg_flash_imxrt.align / sizeof(uint32_t); + uint32_t tmp[tmp_buf_size]; - // Process timeouts - for (c = ifp->mgr->conns; c != NULL; c = c->next) { - if ((c->is_udp && !c->is_arplooking) || c->is_listening || c->is_resolving) - continue; - struct connstate *s = (struct connstate *) (c + 1); - uint32_t rem_ip; - memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t)); - if (now > s->timer) { - if (s->ttype == MIP_TTYPE_ARP) { - mg_error(c, "ARP timeout"); - } else if (c->is_udp) { - continue; - } else if (s->ttype == MIP_TTYPE_ACK && s->acked != s->ack) { - MG_VERBOSE(("%lu ack %x %x", c->id, s->seq, s->ack)); - tx_tcp(ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port, - mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0); - s->acked = s->ack; - } else if (s->ttype == MIP_TTYPE_SYN) { - mg_error(c, "Connection timeout"); - } else if (s->ttype == MIP_TTYPE_FIN) { - c->is_closing = 1; - continue; - } else { - if (s->tmiss++ > 2) { - mg_error(c, "keepalive"); - } else { - MG_VERBOSE(("%lu keepalive", c->id)); - tx_tcp(ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port, - mg_htonl(s->seq - 1), mg_htonl(s->ack), NULL, 0); - } + for (size_t i = 0; i < tmp_buf_size; i++) { + flash_wait(); + tmp[i] = src[i]; } - - settmout(c, MIP_TTYPE_KEEPALIVE); + status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, &config, + (uint32_t) dst_ofs, tmp); + } else { + status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, &config, + (uint32_t) dst_ofs, src); + } + src = (uint32_t *) ((char *) src + s_mg_flash_imxrt.align); + dst = (uint32_t *) ((char *) dst + s_mg_flash_imxrt.align); + if (status != 0) { + ok = false; } } + MG_DEBUG(("Flash write %lu bytes @ %p: %s.", len, dst, ok ? "ok" : "fail")); +fwxit: + if (!s_flash_irq_disabled) MG_ARM_ENABLE_IRQ(); + return ok; } -// This function executes in interrupt context, thus it should copy data -// somewhere fast. Note that newlib's malloc is not thread safe, thus use -// our lock-free queue with preallocated buffer to copy data and return asap -void mg_tcpip_qwrite(void *buf, size_t len, struct mg_tcpip_if *ifp) { - char *p; - if (mg_queue_book(&ifp->recv_queue, &p, len) >= len) { - memcpy(p, buf, len); - mg_queue_add(&ifp->recv_queue, len); - ifp->nrecv++; - } else { - ifp->ndrop++; +// just overwrite instead of swap +MG_IRAM static void single_bank_swap(char *p1, char *p2, size_t s, size_t ss) { + // no stdlib calls here + for (size_t ofs = 0; ofs < s; ofs += ss) { + mg_imxrt_write(p1 + ofs, p2 + ofs, ss); } + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; } -void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) { - // If MAC address is not set, make a random one - if (ifp->mac[0] == 0 && ifp->mac[1] == 0 && ifp->mac[2] == 0 && - ifp->mac[3] == 0 && ifp->mac[4] == 0 && ifp->mac[5] == 0) { - ifp->mac[0] = 0x02; // Locally administered, unicast - mg_random(&ifp->mac[1], sizeof(ifp->mac) - 1); - MG_INFO(("MAC not set. Generated random: %M", mg_print_mac, ifp->mac)); - } +bool mg_ota_begin(size_t new_firmware_size) { + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_imxrt); +} - if (ifp->driver->init && !ifp->driver->init(ifp)) { - MG_ERROR(("driver init failed")); - } else { - size_t framesize = 1540; - ifp->tx.buf = (char *) calloc(1, framesize), ifp->tx.len = framesize; - if (ifp->recv_queue.size == 0) - ifp->recv_queue.size = ifp->driver->rx ? framesize : 8192; - ifp->recv_queue.buf = (char *) calloc(1, ifp->recv_queue.size); - ifp->timer_1000ms = mg_millis(); - mgr->priv = ifp; - ifp->mgr = mgr; - ifp->mtu = MG_TCPIP_MTU_DEFAULT; - mgr->extraconnsize = sizeof(struct connstate); - if (ifp->ip == 0) ifp->enable_dhcp_client = true; - memset(ifp->gwmac, 255, sizeof(ifp->gwmac)); // Set best-effort to bcast - mg_random(&ifp->eport, sizeof(ifp->eport)); // Random from 0 to 65535 - ifp->eport |= MG_EPHEMERAL_PORT_BASE; // Random from - // MG_EPHEMERAL_PORT_BASE to 65535 - if (ifp->tx.buf == NULL || ifp->recv_queue.buf == NULL) MG_ERROR(("OOM")); +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_imxrt); +} + +bool mg_ota_end(void) { + if (mg_ota_flash_end(&s_mg_flash_imxrt)) { + if (0) { // is_dualbank() + // TODO(): no devices so far + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; + } else { + // Swap partitions. Pray power does not go away + char *tmpsector = malloc(s_mg_flash_imxrt.secsz); + bool ramtmp = (tmpsector != NULL); + if (!ramtmp) { + MG_ERROR(("OOM")); + return false; + } + MG_INFO(("Swapping partitions, size %u (%u sectors)", + s_mg_flash_imxrt.size, + s_mg_flash_imxrt.size / s_mg_flash_imxrt.secsz)); + MG_INFO(("Do NOT power off...")); + mg_log_level = MG_LL_NONE; + s_flash_irq_disabled = true; + // Runs in RAM, will reset when finished + single_bank_swap( + (char *) s_mg_flash_imxrt.start, + (char *) s_mg_flash_imxrt.start + s_mg_flash_imxrt.size / 2, + s_mg_flash_imxrt.size / 2, s_mg_flash_imxrt.secsz); + } } + return false; } -void mg_tcpip_free(struct mg_tcpip_if *ifp) { - free(ifp->recv_queue.buf); - free(ifp->tx.buf); -} +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/ota_mcxn.c" +#endif + + -static void send_syn(struct mg_connection *c) { - struct connstate *s = (struct connstate *) (c + 1); - uint32_t isn = mg_htonl((uint32_t) mg_ntohs(c->loc.port)); - struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv; - uint32_t rem_ip; - memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t)); - tx_tcp(ifp, s->mac, rem_ip, TH_SYN, c->loc.port, c->rem.port, isn, 0, NULL, - 0); + +#if MG_OTA == MG_OTA_MCXN + +// - Flash phrase: 16 bytes; smallest portion programmed in one operation. +// - Flash page: 128 bytes; largest portion programmed in one operation. +// - Flash sector: 8 KB; smallest portion that can be erased in one operation. +// - Flash API mg_flash_driver->program: "start" and "len" must be page-size +// aligned; to use 'phrase', FMU register access is needed. Using ROM + +static bool mg_mcxn_write(void *, const void *, size_t); +static bool mg_mcxn_swap(void); + +static struct mg_flash s_mg_flash_mcxn = { + (void *) 0, // Start, filled at init + 0, // Size, filled at init + 0, // Sector size, filled at init + 0, // Align, filled at init + mg_mcxn_write, + mg_mcxn_swap, +}; + +struct mg_flash_config { + uint32_t addr; + uint32_t size; + uint32_t blocks; + uint32_t page_size; + uint32_t sector_size; + uint32_t ffr[6]; + uint32_t reserved0[5]; + uint32_t *bootctx; + bool useahb; +}; + +struct mg_flash_driver_interface { + uint32_t version; + uint32_t (*init)(struct mg_flash_config *); + uint32_t (*erase)(struct mg_flash_config *, uint32_t start, uint32_t len, + uint32_t key); + uint32_t (*program)(struct mg_flash_config *, uint32_t start, uint8_t *src, + uint32_t len); + uint32_t (*verify_erase)(struct mg_flash_config *, uint32_t start, + uint32_t len); + uint32_t (*verify_program)(struct mg_flash_config *, uint32_t start, + uint32_t len, const uint8_t *expected, + uint32_t *addr, uint32_t *failed); + uint32_t reserved1[12]; + uint32_t (*read)(struct mg_flash_config *, uint32_t start, uint8_t *dest, + uint32_t len); + uint32_t reserved2[4]; + uint32_t (*deinit)(struct mg_flash_config *); +}; +#define mg_flash_driver \ + ((struct mg_flash_driver_interface *) (*((uint32_t *) 0x1303fc00 + 4))) +#define MG_MCXN_FLASK_KEY (('k' << 24) | ('e' << 16) | ('f' << 8) | 'l') + +MG_IRAM static bool flash_sector_start(volatile uint32_t *dst) { + char *base = (char *) s_mg_flash_mcxn.start, + *end = base + s_mg_flash_mcxn.size; + volatile char *p = (char *) dst; + return p >= base && p < end && ((p - base) % s_mg_flash_mcxn.secsz) == 0; } -static void mac_resolved(struct mg_connection *c) { - if (c->is_udp) { - c->is_connecting = 0; - mg_call(c, MG_EV_CONNECT, NULL); - } else { - send_syn(c); - settmout(c, MIP_TTYPE_SYN); +MG_IRAM static bool flash_erase(struct mg_flash_config *config, void *addr) { + if (flash_sector_start(addr) == false) { + MG_ERROR(("%p is not on a sector boundary", addr)); + return false; } + uint32_t dst = + (uint32_t) addr - (uint32_t) s_mg_flash_mcxn.start; // future-proof + uint32_t status = mg_flash_driver->erase(config, dst, s_mg_flash_mcxn.secsz, + MG_MCXN_FLASK_KEY); + bool ok = (status == 0); + if (!ok) MG_ERROR(("Flash write error: %lu", status)); + MG_DEBUG(("Sector starting at %p erasure: %s", addr, ok ? "ok" : "fail")); + return ok; } -void mg_connect_resolved(struct mg_connection *c) { - struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv; - uint32_t rem_ip; - memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t)); - c->is_resolving = 0; - if (ifp->eport < MG_EPHEMERAL_PORT_BASE) ifp->eport = MG_EPHEMERAL_PORT_BASE; - memcpy(c->loc.ip, &ifp->ip, sizeof(uint32_t)); - c->loc.port = mg_htons(ifp->eport++); - MG_DEBUG(("%lu %M -> %M", c->id, mg_print_ip_port, &c->loc, mg_print_ip_port, - &c->rem)); - mg_call(c, MG_EV_RESOLVE, NULL); - c->is_connecting = 1; - if (c->is_udp && (rem_ip == 0xffffffff || rem_ip == (ifp->ip | ~ifp->mask))) { - struct connstate *s = (struct connstate *) (c + 1); - memset(s->mac, 0xFF, sizeof(s->mac)); // global or local broadcast - mac_resolved(c); - } else if (ifp->ip && ((rem_ip & ifp->mask) == (ifp->ip & ifp->mask)) && - rem_ip != ifp->gw) { // skip if gw (onstatechange -> READY -> ARP) - // If we're in the same LAN, fire an ARP lookup. - MG_DEBUG(("%lu ARP lookup...", c->id)); - mg_tcpip_arp_request(ifp, rem_ip, NULL); - settmout(c, MIP_TTYPE_ARP); - c->is_arplooking = 1; - } else if ((*((uint8_t *) &rem_ip) & 0xE0) == 0xE0) { - struct connstate *s = (struct connstate *) (c + 1); // 224 to 239, E0 to EF - uint8_t mcastp[3] = {0x01, 0x00, 0x5E}; // multicast group - memcpy(s->mac, mcastp, 3); - memcpy(s->mac + 3, ((uint8_t *) &rem_ip) + 1, 3); // 23 LSb - s->mac[3] &= 0x7F; - mac_resolved(c); - } else { - struct connstate *s = (struct connstate *) (c + 1); - memcpy(s->mac, ifp->gwmac, sizeof(ifp->gwmac)); - mac_resolved(c); +#if 0 +// read-while-write, no need to disable IRQs for standalone usage +MG_IRAM static bool mg_mcxn_erase(void *addr) { + uint32_t status; + struct mg_flash_config config; + if ((status = mg_flash_driver->init(&config)) != 0) { + MG_ERROR(("Flash driver init error: %lu", status)); + return false; } + bool ok = flash_erase(&config, addr); + mg_flash_driver->deinit(&config); + return ok; } +#endif -bool mg_open_listener(struct mg_connection *c, const char *url) { - c->loc.port = mg_htons(mg_url_port(url)); +MG_IRAM static bool mg_mcxn_swap(void) { + // TODO(): no devices so far return true; } -static void write_conn(struct mg_connection *c) { - long len = c->is_tls ? mg_tls_send(c, c->send.buf, c->send.len) - : mg_io_send(c, c->send.buf, c->send.len); - if (len == MG_IO_ERR) { - mg_error(c, "tx err"); - } else if (len > 0) { - mg_iobuf_del(&c->send, 0, (size_t) len); - mg_call(c, MG_EV_WRITE, &len); +static bool s_flash_irq_disabled; + +MG_IRAM static bool mg_mcxn_write(void *addr, const void *buf, size_t len) { + bool ok = false; + uint32_t status; + struct mg_flash_config config; + if ((status = mg_flash_driver->init(&config)) != 0) { + MG_ERROR(("Flash driver init error: %lu", status)); + return false; + } + if ((len % s_mg_flash_mcxn.align) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, s_mg_flash_mcxn.align)); + goto fwxit; + } + if ((((size_t) addr - (size_t) s_mg_flash_mcxn.start) % + s_mg_flash_mcxn.align) != 0) { + MG_ERROR(("%p is not on a page boundary", addr)); + goto fwxit; } -} -static void init_closure(struct mg_connection *c) { - struct connstate *s = (struct connstate *) (c + 1); - if (c->is_udp == false && c->is_listening == false && - c->is_connecting == false) { // For TCP conns, - struct mg_tcpip_if *ifp = - (struct mg_tcpip_if *) c->mgr->priv; // send TCP FIN - uint32_t rem_ip; - memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t)); - tx_tcp(ifp, s->mac, rem_ip, TH_FIN | TH_ACK, c->loc.port, c->rem.port, - mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0); - settmout(c, MIP_TTYPE_FIN); + uint32_t *dst = (uint32_t *) addr; + uint32_t *src = (uint32_t *) buf; + uint32_t *end = (uint32_t *) ((char *) buf + len); + ok = true; + + MG_ARM_DISABLE_IRQ(); + while (ok && src < end) { + if (flash_sector_start(dst) && flash_erase(&config, dst) == false) { + ok = false; + break; + } + uint32_t dst_ofs = (uint32_t) dst - (uint32_t) s_mg_flash_mcxn.start; + // assume source is in RAM or in a different bank or read-while-write + status = mg_flash_driver->program(&config, dst_ofs, (uint8_t *) src, + s_mg_flash_mcxn.align); + src = (uint32_t *) ((char *) src + s_mg_flash_mcxn.align); + dst = (uint32_t *) ((char *) dst + s_mg_flash_mcxn.align); + if (status != 0) { + MG_ERROR(("Flash write error: %lu", status)); + ok = false; + } } -} + if (!s_flash_irq_disabled) MG_ARM_ENABLE_IRQ(); + MG_DEBUG(("Flash write %lu bytes @ %p: %s.", len, dst, ok ? "ok" : "fail")); -static void close_conn(struct mg_connection *c) { - struct connstate *s = (struct connstate *) (c + 1); - mg_iobuf_free(&s->raw); // For TLS connections, release raw data - mg_close_conn(c); +fwxit: + mg_flash_driver->deinit(&config); + return ok; } -static bool can_write(struct mg_connection *c) { - return c->is_connecting == 0 && c->is_resolving == 0 && c->send.len > 0 && - c->is_tls_hs == 0 && c->is_arplooking == 0; +// try to swap (honor dual image), otherwise just overwrite +MG_IRAM static void single_bank_swap(char *p1, char *p2, size_t s, size_t ss) { + char *tmp = malloc(ss); + // no stdlib calls here + for (size_t ofs = 0; ofs < s; ofs += ss) { + if (tmp != NULL) + for (size_t i = 0; i < ss; i++) tmp[i] = p1[ofs + i]; + mg_mcxn_write(p1 + ofs, p2 + ofs, ss); + if (tmp != NULL) mg_mcxn_write(p2 + ofs, tmp, ss); + } + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; } -void mg_mgr_poll(struct mg_mgr *mgr, int ms) { - struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) mgr->priv; - struct mg_connection *c, *tmp; - uint64_t now = mg_millis(); - mg_timer_poll(&mgr->timers, now); - if (ifp == NULL || ifp->driver == NULL) return; - mg_tcpip_poll(ifp, now); - for (c = mgr->conns; c != NULL; c = tmp) { - tmp = c->next; - struct connstate *s = (struct connstate *) (c + 1); - mg_call(c, MG_EV_POLL, &now); - MG_VERBOSE(("%lu .. %c%c%c%c%c", c->id, c->is_tls ? 'T' : 't', - c->is_connecting ? 'C' : 'c', c->is_tls_hs ? 'H' : 'h', - c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c')); - if (c->is_tls && mg_tls_pending(c) > 0) - handle_tls_recv(c, (struct mg_iobuf *) &c->rtls); - if (can_write(c)) write_conn(c); - if (c->is_draining && c->send.len == 0 && s->ttype != MIP_TTYPE_FIN) - init_closure(c); - if (c->is_closing) close_conn(c); +bool mg_ota_begin(size_t new_firmware_size) { + uint32_t status; + struct mg_flash_config config; + if ((status = mg_flash_driver->init(&config)) != 0) { + MG_ERROR(("Flash driver init error: %lu", status)); + return false; } - (void) ms; + s_mg_flash_mcxn.start = (void *) config.addr; + s_mg_flash_mcxn.size = config.size; + s_mg_flash_mcxn.secsz = config.sector_size; + s_mg_flash_mcxn.align = config.page_size; + mg_flash_driver->deinit(&config); + MG_DEBUG( + ("%lu-byte flash @%p, using %lu-byte sectors with %lu-byte-aligned pages", + s_mg_flash_mcxn.size, s_mg_flash_mcxn.start, s_mg_flash_mcxn.secsz, + s_mg_flash_mcxn.align)); + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_mcxn); } -bool mg_send(struct mg_connection *c, const void *buf, size_t len) { - struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv; - bool res = false; - uint32_t rem_ip; - memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t)); - if (ifp->ip == 0 || ifp->state != MG_TCPIP_STATE_READY) { - mg_error(c, "net down"); - } else if (c->is_udp && (c->is_arplooking || c->is_resolving)) { - // Fail to send, no target MAC or IP - MG_VERBOSE(("still resolving...")); - } else if (c->is_udp) { - struct connstate *s = (struct connstate *) (c + 1); - len = trim_len(c, len); // Trimming length if necessary - tx_udp(ifp, s->mac, ifp->ip, c->loc.port, rem_ip, c->rem.port, buf, len); - res = true; - } else { - res = mg_iobuf_add(&c->send, c->send.len, buf, len); +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_mcxn); +} + +bool mg_ota_end(void) { + if (mg_ota_flash_end(&s_mg_flash_mcxn)) { + if (0) { // is_dualbank() + // TODO(): no devices so far + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; + } else { + // Swap partitions. Pray power does not go away + MG_INFO(("Swapping partitions, size %u (%u sectors)", + s_mg_flash_mcxn.size, + s_mg_flash_mcxn.size / s_mg_flash_mcxn.secsz)); + MG_INFO(("Do NOT power off...")); + mg_log_level = MG_LL_NONE; + s_flash_irq_disabled = true; + // Runs in RAM, will reset when finished + single_bank_swap( + (char *) s_mg_flash_mcxn.start, + (char *) s_mg_flash_mcxn.start + s_mg_flash_mcxn.size / 2, + s_mg_flash_mcxn.size / 2, s_mg_flash_mcxn.secsz); + } } - return res; + return false; } -#endif // MG_ENABLE_TCPIP +#endif #ifdef MG_ENABLE_LINES -#line 1 "src/ota_dummy.c" +#line 1 "src/ota_stm32h5.c" #endif -#if MG_OTA == MG_OTA_NONE -bool mg_ota_begin(size_t new_firmware_size) { - (void) new_firmware_size; - return true; + +#if MG_OTA == MG_OTA_STM32H5 + +static bool mg_stm32h5_write(void *, const void *, size_t); +static bool mg_stm32h5_swap(void); + +static struct mg_flash s_mg_flash_stm32h5 = { + (void *) 0x08000000, // Start + 2 * 1024 * 1024, // Size, 2Mb + 8 * 1024, // Sector size, 8k + 16, // Align, 128 bit + mg_stm32h5_write, + mg_stm32h5_swap, +}; + +#define FLASH_BASE 0x40022000 // Base address of the flash controller +#define FLASH_KEYR (FLASH_BASE + 0x4) // See RM0481 7.11 +#define FLASH_OPTKEYR (FLASH_BASE + 0xc) +#define FLASH_OPTCR (FLASH_BASE + 0x1c) +#define FLASH_NSSR (FLASH_BASE + 0x20) +#define FLASH_NSCR (FLASH_BASE + 0x28) +#define FLASH_NSCCR (FLASH_BASE + 0x30) +#define FLASH_OPTSR_CUR (FLASH_BASE + 0x50) +#define FLASH_OPTSR_PRG (FLASH_BASE + 0x54) + +static void flash_unlock(void) { + static bool unlocked = false; + if (unlocked == false) { + MG_REG(FLASH_KEYR) = 0x45670123; + MG_REG(FLASH_KEYR) = 0Xcdef89ab; + MG_REG(FLASH_OPTKEYR) = 0x08192a3b; + MG_REG(FLASH_OPTKEYR) = 0x4c5d6e7f; + unlocked = true; + } } -bool mg_ota_write(const void *buf, size_t len) { - (void) buf, (void) len; - return true; + +static int flash_page_start(volatile uint32_t *dst) { + char *base = (char *) s_mg_flash_stm32h5.start, + *end = base + s_mg_flash_stm32h5.size; + volatile char *p = (char *) dst; + return p >= base && p < end && ((p - base) % s_mg_flash_stm32h5.secsz) == 0; } -bool mg_ota_end(void) { - return true; + +static bool flash_is_err(void) { + return MG_REG(FLASH_NSSR) & ((MG_BIT(8) - 1) << 17); // RM0481 7.11.9 } -bool mg_ota_commit(void) { - return true; + +static void flash_wait(void) { + while ((MG_REG(FLASH_NSSR) & MG_BIT(0)) && + (MG_REG(FLASH_NSSR) & MG_BIT(16)) == 0) { + (void) 0; + } +} + +static void flash_clear_err(void) { + flash_wait(); // Wait until ready + MG_REG(FLASH_NSCCR) = ((MG_BIT(9) - 1) << 16U); // Clear all errors +} + +static bool flash_bank_is_swapped(void) { + return MG_REG(FLASH_OPTCR) & MG_BIT(31); // RM0481 7.11.8 +} + +static bool mg_stm32h5_erase(void *location) { + bool ok = false; + if (flash_page_start(location) == false) { + MG_ERROR(("%p is not on a sector boundary")); + } else { + uintptr_t diff = (char *) location - (char *) s_mg_flash_stm32h5.start; + uint32_t sector = diff / s_mg_flash_stm32h5.secsz; + uint32_t saved_cr = MG_REG(FLASH_NSCR); // Save CR value + flash_unlock(); + flash_clear_err(); + MG_REG(FLASH_NSCR) = 0; + if ((sector < 128 && flash_bank_is_swapped()) || + (sector > 127 && !flash_bank_is_swapped())) { + MG_REG(FLASH_NSCR) |= MG_BIT(31); // Set FLASH_CR_BKSEL + } + if (sector > 127) sector -= 128; + MG_REG(FLASH_NSCR) |= MG_BIT(2) | (sector << 6); // Erase | sector_num + MG_REG(FLASH_NSCR) |= MG_BIT(5); // Start erasing + flash_wait(); + ok = !flash_is_err(); + MG_DEBUG(("Erase sector %lu @ %p: %s. CR %#lx SR %#lx", sector, location, + ok ? "ok" : "fail", MG_REG(FLASH_NSCR), MG_REG(FLASH_NSSR))); + // mg_hexdump(location, 32); + MG_REG(FLASH_NSCR) = saved_cr; // Restore saved CR + } + return ok; } -bool mg_ota_rollback(void) { + +static bool mg_stm32h5_swap(void) { + uint32_t desired = flash_bank_is_swapped() ? 0 : MG_BIT(31); + flash_unlock(); + flash_clear_err(); + // printf("OPTSR_PRG 1 %#lx\n", FLASH->OPTSR_PRG); + MG_SET_BITS(MG_REG(FLASH_OPTSR_PRG), MG_BIT(31), desired); + // printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG); + MG_REG(FLASH_OPTCR) |= MG_BIT(1); // OPTSTART + while ((MG_REG(FLASH_OPTSR_CUR) & MG_BIT(31)) != desired) (void) 0; return true; } -int mg_ota_status(int fw) { - (void) fw; - return 0; -} -uint32_t mg_ota_crc32(int fw) { - (void) fw; - return 0; -} -uint32_t mg_ota_timestamp(int fw) { - (void) fw; - return 0; -} -size_t mg_ota_size(int fw) { - (void) fw; - return 0; -} -MG_IRAM void mg_ota_boot(void) { -} -#endif - -#ifdef MG_ENABLE_LINES -#line 1 "src/ota_esp32.c" -#endif - -#if MG_ARCH == MG_ARCH_ESP32 && MG_OTA == MG_OTA_ESP32 - -static const esp_partition_t *s_ota_update_partition; -static esp_ota_handle_t s_ota_update_handle; -static bool s_ota_success; - -// Those empty macros do nothing, but mark places in the code which could -// potentially trigger a watchdog reboot due to the log flash erase operation -#define disable_wdt() -#define enable_wdt() - -bool mg_ota_begin(size_t new_firmware_size) { - if (s_ota_update_partition != NULL) { - MG_ERROR(("Update in progress. Call mg_ota_end() ?")); +static bool mg_stm32h5_write(void *addr, const void *buf, size_t len) { + if ((len % s_mg_flash_stm32h5.align) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, s_mg_flash_stm32h5.align)); return false; - } else { - s_ota_success = false; - disable_wdt(); - s_ota_update_partition = esp_ota_get_next_update_partition(NULL); - esp_err_t err = esp_ota_begin(s_ota_update_partition, new_firmware_size, - &s_ota_update_handle); - enable_wdt(); - MG_DEBUG(("esp_ota_begin(): %d", err)); - s_ota_success = (err == ESP_OK); } - return s_ota_success; + uint32_t *dst = (uint32_t *) addr; + uint32_t *src = (uint32_t *) buf; + uint32_t *end = (uint32_t *) ((char *) buf + len); + bool ok = true; + MG_ARM_DISABLE_IRQ(); + flash_unlock(); + flash_clear_err(); + MG_REG(FLASH_NSCR) = MG_BIT(1); // Set programming flag + while (ok && src < end) { + if (flash_page_start(dst) && mg_stm32h5_erase(dst) == false) { + ok = false; + break; + } + *(volatile uint32_t *) dst++ = *src++; + flash_wait(); + if (flash_is_err()) ok = false; + } + MG_ARM_ENABLE_IRQ(); + MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst, + flash_is_err() ? "fail" : "ok", MG_REG(FLASH_NSCR), + MG_REG(FLASH_NSSR))); + MG_REG(FLASH_NSCR) = 0; // Clear flags + return ok; +} + +bool mg_ota_begin(size_t new_firmware_size) { + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_stm32h5); } bool mg_ota_write(const void *buf, size_t len) { - disable_wdt(); - esp_err_t err = esp_ota_write(s_ota_update_handle, buf, len); - enable_wdt(); - MG_INFO(("esp_ota_write(): %d", err)); - s_ota_success = err == ESP_OK; - return s_ota_success; + return mg_ota_flash_write(buf, len, &s_mg_flash_stm32h5); } +// Actual bank swap is deferred until reset, it is safe to execute in flash bool mg_ota_end(void) { - esp_err_t err = esp_ota_end(s_ota_update_handle); - MG_DEBUG(("esp_ota_end(%p): %d", s_ota_update_handle, err)); - if (s_ota_success && err == ESP_OK) { - err = esp_ota_set_boot_partition(s_ota_update_partition); - s_ota_success = (err == ESP_OK); - } - MG_DEBUG(("Finished ESP32 OTA, success: %d", s_ota_success)); - s_ota_update_partition = NULL; - return s_ota_success; + if(!mg_ota_flash_end(&s_mg_flash_stm32h5)) return false; + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; + return true; } - #endif #ifdef MG_ENABLE_LINES -#line 1 "src/ota_flash.c" +#line 1 "src/ota_stm32h7.c" #endif +#if MG_OTA == MG_OTA_STM32H7 -// This OTA implementation uses the internal flash API outlined in device.h -// It splits flash into 2 equal partitions, and stores OTA status in the -// last sector of the partition. +// - H723/735 RM 4.3.3: Note: The application can simultaneously request a read +// and a write operation through the AXI interface. +// - We only need IRAM for partition swapping in the H723, however, all +// related functions must reside in IRAM for this to be possible. +// - Linker files for other devices won't define a .iram section so there's no +// associated penalty -#if MG_OTA == MG_OTA_FLASH +static bool mg_stm32h7_write(void *, const void *, size_t); +static bool mg_stm32h7_swap(void); -#define MG_OTADATA_KEY 0xb07afed0 +static struct mg_flash s_mg_flash_stm32h7 = { + (void *) 0x08000000, // Start + 0, // Size, FLASH_SIZE_REG + 128 * 1024, // Sector size, 128k + 32, // Align, 256 bit + mg_stm32h7_write, + mg_stm32h7_swap, +}; -static char *s_addr; // Current address to write to -static size_t s_size; // Firmware size to flash. In-progress indicator -static uint32_t s_crc32; // Firmware checksum +#define FLASH_BASE1 0x52002000 // Base address for bank1 +#define FLASH_BASE2 0x52002100 // Base address for bank2 +#define FLASH_KEYR 0x04 // See RM0433 4.9.2 +#define FLASH_OPTKEYR 0x08 +#define FLASH_OPTCR 0x18 +#define FLASH_SR 0x10 +#define FLASH_CR 0x0c +#define FLASH_CCR 0x14 +#define FLASH_OPTSR_CUR 0x1c +#define FLASH_OPTSR_PRG 0x20 +#define FLASH_SIZE_REG 0x1ff1e880 -struct mg_otadata { - uint32_t crc32, size, timestamp, status; -}; +MG_IRAM static bool is_dualbank(void) { + return (s_mg_flash_stm32h7.size < 2 * 1024 * 1024) ? false : true; +} -bool mg_ota_begin(size_t new_firmware_size) { - bool ok = false; - if (s_size) { - MG_ERROR(("OTA already in progress. Call mg_ota_end()")); - } else { - size_t half = mg_flash_size() / 2, max = half - mg_flash_sector_size(); - s_crc32 = 0; - s_addr = (char *) mg_flash_start() + half; - MG_DEBUG(("Firmware %lu bytes, max %lu", new_firmware_size, max)); - if (new_firmware_size < max) { - ok = true; - s_size = new_firmware_size; - MG_INFO(("Starting OTA, firmware size %lu", s_size)); - } else { - MG_ERROR(("Firmware %lu is too big to fit %lu", new_firmware_size, max)); +MG_IRAM static void flash_unlock(void) { + static bool unlocked = false; + if (unlocked == false) { + MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0x45670123; + MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0xcdef89ab; + if (is_dualbank()) { + MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0x45670123; + MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0xcdef89ab; } + MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x08192a3b; // opt reg is "shared" + MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x4c5d6e7f; // thus unlock once + unlocked = true; } - return ok; } -bool mg_ota_write(const void *buf, size_t len) { - bool ok = false; - if (s_size == 0) { - MG_ERROR(("OTA is not started, call mg_ota_begin()")); - } else { - size_t align = mg_flash_write_align(); - size_t len_aligned_down = MG_ROUND_DOWN(len, align); - if (len_aligned_down) ok = mg_flash_write(s_addr, buf, len_aligned_down); - if (len_aligned_down < len) { - size_t left = len - len_aligned_down; - char tmp[align]; - memset(tmp, 0xff, sizeof(tmp)); - memcpy(tmp, (char *) buf + len_aligned_down, left); - ok = mg_flash_write(s_addr + len_aligned_down, tmp, sizeof(tmp)); - } - s_crc32 = mg_crc32(s_crc32, (char *) buf, len); // Update CRC - MG_DEBUG(("%#x %p %lu -> %d", s_addr - len, buf, len, ok)); - s_addr += len; - } - return ok; +MG_IRAM static bool flash_page_start(volatile uint32_t *dst) { + char *base = (char *) s_mg_flash_stm32h7.start, + *end = base + s_mg_flash_stm32h7.size; + volatile char *p = (char *) dst; + return p >= base && p < end && ((p - base) % s_mg_flash_stm32h7.secsz) == 0; } -MG_IRAM static uint32_t mg_fwkey(int fw) { - uint32_t key = MG_OTADATA_KEY + fw; - int bank = mg_flash_bank(); - if (bank == 2 && fw == MG_FIRMWARE_PREVIOUS) key--; - if (bank == 2 && fw == MG_FIRMWARE_CURRENT) key++; - return key; +MG_IRAM static bool flash_is_err(uint32_t bank) { + return MG_REG(bank + FLASH_SR) & ((MG_BIT(11) - 1) << 17); // RM0433 4.9.5 } -bool mg_ota_end(void) { - char *base = (char *) mg_flash_start() + mg_flash_size() / 2; - bool ok = false; - if (s_size) { - size_t size = s_addr - base; - uint32_t crc32 = mg_crc32(0, base, s_size); - if (size == s_size && crc32 == s_crc32) { - uint32_t now = (uint32_t) (mg_now() / 1000); - struct mg_otadata od = {crc32, size, now, MG_OTA_FIRST_BOOT}; - uint32_t key = mg_fwkey(MG_FIRMWARE_PREVIOUS); - ok = mg_flash_save(NULL, key, &od, sizeof(od)); - } - MG_DEBUG(("CRC: %x/%x, size: %lu/%lu, status: %s", s_crc32, crc32, s_size, - size, ok ? "ok" : "fail")); - s_size = 0; - if (ok) ok = mg_flash_swap_bank(); - } - MG_INFO(("Finishing OTA: %s", ok ? "ok" : "fail")); - return ok; +MG_IRAM static void flash_wait(uint32_t bank) { + while (MG_REG(bank + FLASH_SR) & (MG_BIT(0) | MG_BIT(2))) (void) 0; } -MG_IRAM static struct mg_otadata mg_otadata(int fw) { - uint32_t key = mg_fwkey(fw); - struct mg_otadata od = {}; - MG_INFO(("Loading %s OTA data", fw == MG_FIRMWARE_CURRENT ? "curr" : "prev")); - mg_flash_load(NULL, key, &od, sizeof(od)); - // MG_DEBUG(("Loaded OTA data. fw %d, bank %d, key %p", fw, bank, key)); - // mg_hexdump(&od, sizeof(od)); - return od; +MG_IRAM static void flash_clear_err(uint32_t bank) { + flash_wait(bank); // Wait until ready + MG_REG(bank + FLASH_CCR) = ((MG_BIT(11) - 1) << 16U); // Clear all errors } -int mg_ota_status(int fw) { - struct mg_otadata od = mg_otadata(fw); - return od.status; +MG_IRAM static bool flash_bank_is_swapped(uint32_t bank) { + return MG_REG(bank + FLASH_OPTCR) & MG_BIT(31); // RM0433 4.9.7 } -uint32_t mg_ota_crc32(int fw) { - struct mg_otadata od = mg_otadata(fw); - return od.crc32; + +// Figure out flash bank based on the address +MG_IRAM static uint32_t flash_bank(void *addr) { + size_t ofs = (char *) addr - (char *) s_mg_flash_stm32h7.start; + if (!is_dualbank()) return FLASH_BASE1; + return ofs < s_mg_flash_stm32h7.size / 2 ? FLASH_BASE1 : FLASH_BASE2; } -uint32_t mg_ota_timestamp(int fw) { - struct mg_otadata od = mg_otadata(fw); - return od.timestamp; + +// read-while-write, no need to disable IRQs for standalone usage +MG_IRAM static bool mg_stm32h7_erase(void *addr) { + bool ok = false; + if (flash_page_start(addr) == false) { + MG_ERROR(("%p is not on a sector boundary", addr)); + } else { + uintptr_t diff = (char *) addr - (char *) s_mg_flash_stm32h7.start; + uint32_t sector = diff / s_mg_flash_stm32h7.secsz; + uint32_t bank = flash_bank(addr); + uint32_t saved_cr = MG_REG(bank + FLASH_CR); // Save CR value + + flash_unlock(); + if (sector > 7) sector -= 8; + + flash_clear_err(bank); + MG_REG(bank + FLASH_CR) = MG_BIT(5); // 32-bit write parallelism + MG_REG(bank + FLASH_CR) |= (sector & 7U) << 8U; // Sector to erase + MG_REG(bank + FLASH_CR) |= MG_BIT(2); // Sector erase bit + MG_REG(bank + FLASH_CR) |= MG_BIT(7); // Start erasing + ok = !flash_is_err(bank); + MG_DEBUG(("Erase sector %lu @ %p %s. CR %#lx SR %#lx", sector, addr, + ok ? "ok" : "fail", MG_REG(bank + FLASH_CR), + MG_REG(bank + FLASH_SR))); + MG_REG(bank + FLASH_CR) = saved_cr; // Restore CR + } + return ok; } -size_t mg_ota_size(int fw) { - struct mg_otadata od = mg_otadata(fw); - return od.size; + +MG_IRAM static bool mg_stm32h7_swap(void) { + if (!is_dualbank()) return true; + uint32_t bank = FLASH_BASE1; + uint32_t desired = flash_bank_is_swapped(bank) ? 0 : MG_BIT(31); + flash_unlock(); + flash_clear_err(bank); + // printf("OPTSR_PRG 1 %#lx\n", FLASH->OPTSR_PRG); + MG_SET_BITS(MG_REG(bank + FLASH_OPTSR_PRG), MG_BIT(31), desired); + // printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG); + MG_REG(bank + FLASH_OPTCR) |= MG_BIT(1); // OPTSTART + while ((MG_REG(bank + FLASH_OPTSR_CUR) & MG_BIT(31)) != desired) (void) 0; + return true; } -MG_IRAM bool mg_ota_commit(void) { +static bool s_flash_irq_disabled; + +MG_IRAM static bool mg_stm32h7_write(void *addr, const void *buf, size_t len) { + if ((len % s_mg_flash_stm32h7.align) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, s_mg_flash_stm32h7.align)); + return false; + } + uint32_t bank = flash_bank(addr); + uint32_t *dst = (uint32_t *) addr; + uint32_t *src = (uint32_t *) buf; + uint32_t *end = (uint32_t *) ((char *) buf + len); bool ok = true; - struct mg_otadata od = mg_otadata(MG_FIRMWARE_CURRENT); - if (od.status != MG_OTA_COMMITTED) { - od.status = MG_OTA_COMMITTED; - MG_INFO(("Committing current firmware, OD size %lu", sizeof(od))); - ok = mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_CURRENT), &od, sizeof(od)); + MG_ARM_DISABLE_IRQ(); + flash_unlock(); + flash_clear_err(bank); + MG_REG(bank + FLASH_CR) = MG_BIT(1); // Set programming flag + MG_REG(bank + FLASH_CR) |= MG_BIT(5); // 32-bit write parallelism + while (ok && src < end) { + if (flash_page_start(dst) && mg_stm32h7_erase(dst) == false) { + ok = false; + break; + } + *(volatile uint32_t *) dst++ = *src++; + flash_wait(bank); + if (flash_is_err(bank)) ok = false; } + if (!s_flash_irq_disabled) MG_ARM_ENABLE_IRQ(); + MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst, + ok ? "ok" : "fail", MG_REG(bank + FLASH_CR), + MG_REG(bank + FLASH_SR))); + MG_REG(bank + FLASH_CR) &= ~MG_BIT(1); // Clear programming flag return ok; } -bool mg_ota_rollback(void) { - MG_DEBUG(("Rolling firmware back")); - if (mg_flash_bank() == 0) { - // No dual bank support. Mark previous firmware as FIRST_BOOT - struct mg_otadata prev = mg_otadata(MG_FIRMWARE_PREVIOUS); - prev.status = MG_OTA_FIRST_BOOT; - return mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_PREVIOUS, &prev, - sizeof(prev)); - } else { - return mg_flash_swap_bank(); +// just overwrite instead of swap +MG_IRAM static void single_bank_swap(char *p1, char *p2, size_t s, size_t ss) { + // no stdlib calls here + for (size_t ofs = 0; ofs < s; ofs += ss) { + mg_stm32h7_write(p1 + ofs, p2 + ofs, ss); } + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; } -MG_IRAM void mg_ota_boot(void) { - MG_INFO(("Booting. Flash bank: %d", mg_flash_bank())); - struct mg_otadata curr = mg_otadata(MG_FIRMWARE_CURRENT); - struct mg_otadata prev = mg_otadata(MG_FIRMWARE_PREVIOUS); - - if (curr.status == MG_OTA_FIRST_BOOT) { - if (prev.status == MG_OTA_UNAVAILABLE) { - MG_INFO(("Setting previous firmware state to committed")); - prev.status = MG_OTA_COMMITTED; - mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_PREVIOUS), &prev, sizeof(prev)); - } - curr.status = MG_OTA_UNCOMMITTED; - MG_INFO(("First boot, setting status to UNCOMMITTED")); - mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_CURRENT), &curr, sizeof(curr)); - } else if (prev.status == MG_OTA_FIRST_BOOT && mg_flash_bank() == 0) { - // Swap paritions. Pray power does not disappear - size_t fs = mg_flash_size(), ss = mg_flash_sector_size(); - char *partition1 = mg_flash_start(); - char *partition2 = mg_flash_start() + fs / 2; - size_t ofs, max = fs / 2 - ss; // Set swap size to the whole partition - - if (curr.status != MG_OTA_UNAVAILABLE && - prev.status != MG_OTA_UNAVAILABLE) { - // We know exact sizes of both firmwares. - // Shrink swap size to the MAX(firmware1, firmware2) - size_t sz = curr.size > prev.size ? curr.size : prev.size; - if (sz > 0 && sz < max) max = sz; - } - - // MG_OTA_FIRST_BOOT -> MG_OTA_UNCOMMITTED - prev.status = MG_OTA_UNCOMMITTED; - mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_CURRENT, &prev, - sizeof(prev)); - mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_PREVIOUS, &curr, - sizeof(curr)); +bool mg_ota_begin(size_t new_firmware_size) { + s_mg_flash_stm32h7.size = MG_REG(FLASH_SIZE_REG) * 1024; + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_stm32h7); +} - MG_INFO(("Swapping partitions, size %u (%u sectors)", max, max / ss)); - MG_INFO(("Do NOT power off...")); - mg_log_level = MG_LL_NONE; +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_stm32h7); +} - // We use the last sector of partition2 for OTA data/config storage - // Therefore we can use last sector of partition1 for swapping - char *tmpsector = partition1 + fs / 2 - ss; // Last sector of partition1 - (void) tmpsector; - for (ofs = 0; ofs < max; ofs += ss) { - // mg_flash_erase(tmpsector); - mg_flash_write(tmpsector, partition1 + ofs, ss); - // mg_flash_erase(partition1 + ofs); - mg_flash_write(partition1 + ofs, partition2 + ofs, ss); - // mg_flash_erase(partition2 + ofs); - mg_flash_write(partition2 + ofs, tmpsector, ss); +bool mg_ota_end(void) { + if (mg_ota_flash_end(&s_mg_flash_stm32h7)) { + if (is_dualbank()) { + // Bank swap is deferred until reset, been executing in flash, reset + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; + } else { + // Swap partitions. Pray power does not go away + MG_INFO(("Swapping partitions, size %u (%u sectors)", + s_mg_flash_stm32h7.size, + s_mg_flash_stm32h7.size / s_mg_flash_stm32h7.secsz)); + MG_INFO(("Do NOT power off...")); + mg_log_level = MG_LL_NONE; + s_flash_irq_disabled = true; + // Runs in RAM, will reset when finished + single_bank_swap( + (char *) s_mg_flash_stm32h7.start, + (char *) s_mg_flash_stm32h7.start + s_mg_flash_stm32h7.size / 2, + s_mg_flash_stm32h7.size / 2, s_mg_flash_stm32h7.secsz); } - mg_device_reset(); } + return false; } #endif diff --git a/mongoose.h b/mongoose.h index 52a454fc4a..ce3a838548 100644 --- a/mongoose.h +++ b/mongoose.h @@ -2640,49 +2640,43 @@ void mg_rpc_list(struct mg_rpc_req *r); -#define MG_OTA_NONE 0 // No OTA support -#define MG_OTA_FLASH 1 // OTA via an internal flash -#define MG_OTA_ESP32 2 // ESP32 OTA implementation -#define MG_OTA_STM32H5 3 // STM32H5 OTA implementation -#define MG_OTA_CUSTOM 100 // Custom implementation +#define MG_OTA_NONE 0 // No OTA support +#define MG_OTA_STM32H5 1 // STM32 H5 +#define MG_OTA_STM32H7 2 // STM32 H7 +#define MG_OTA_CH32V307 100 // WCH CH32V307 +#define MG_OTA_U2A 200 // Renesas U2A16, U2A8, U2A6 +#define MG_OTA_RT1020 300 // IMXRT1020 +#define MG_OTA_RT1060 301 // IMXRT1060 +#define MG_OTA_MCXN 310 // MCXN947 +#define MG_OTA_FLASH 900 // OTA via an internal flash +#define MG_OTA_ESP32 910 // ESP32 OTA implementation +#define MG_OTA_CUSTOM 1000 // Custom implementation #ifndef MG_OTA #define MG_OTA MG_OTA_NONE -#endif - -#if defined(__GNUC__) && !defined(__APPLE__) +#else +#ifndef MG_IRAM +#if defined(__GNUC__) #define MG_IRAM __attribute__((section(".iram"))) #else #define MG_IRAM -#endif +#endif // compiler +#endif // IRAM +#endif // OTA // Firmware update API bool mg_ota_begin(size_t new_firmware_size); // Start writing bool mg_ota_write(const void *buf, size_t len); // Write chunk, aligned to 1k bool mg_ota_end(void); // Stop writing -enum { - MG_OTA_UNAVAILABLE = 0, // No OTA information is present - MG_OTA_FIRST_BOOT = 1, // Device booting the first time after the OTA - MG_OTA_UNCOMMITTED = 2, // Ditto, but marking us for the rollback - MG_OTA_COMMITTED = 3 // The firmware is good -}; -enum { MG_FIRMWARE_CURRENT = 0, MG_FIRMWARE_PREVIOUS = 1 }; - -int mg_ota_status(int firmware); // Return firmware status MG_OTA_* -uint32_t mg_ota_crc32(int firmware); // Return firmware checksum -uint32_t mg_ota_timestamp(int firmware); // Firmware timestamp, UNIX UTC epoch -size_t mg_ota_size(int firmware); // Firmware size - -bool mg_ota_commit(void); // Commit current firmware -bool mg_ota_rollback(void); // Rollback to the previous firmware -MG_IRAM void mg_ota_boot(void); // Bootloader function +#if MG_OTA != MG_OTA_NONE && MG_OTA != MG_OTA_CUSTOM struct mg_flash { - void *start; // Address at which flash starts - size_t size; // Flash size - size_t align; // Write alignment + void *start; // Address at which flash starts + size_t size; // Flash size + size_t secsz; // Sector size + size_t align; // Write alignment bool (*write_fn)(void *, const void *, size_t); // Write function bool (*swap_fn)(void); // Swap partitions }; @@ -2690,46 +2684,9 @@ struct mg_flash { bool mg_ota_flash_begin(size_t new_firmware_size, struct mg_flash *flash); bool mg_ota_flash_write(const void *buf, size_t len, struct mg_flash *flash); bool mg_ota_flash_end(struct mg_flash *flash); -// Copyright (c) 2023 Cesanta Software Limited -// All rights reserved - - - - -#define MG_DEVICE_NONE 0 // Dummy system - -#define MG_DEVICE_STM32H5 1 // STM32 H5 -#define MG_DEVICE_STM32H7 2 // STM32 H7 -#define MG_DEVICE_CH32V307 100 // WCH CH32V307 -#define MG_DEVICE_U2A 200 // Renesas U2A16, U2A8, U2A6 -#define MG_DEVICE_RT1020 300 // IMXRT1020 -#define MG_DEVICE_RT1060 301 // IMXRT1060 -#define MG_DEVICE_CUSTOM 1000 // Custom implementation - -#ifndef MG_DEVICE -#define MG_DEVICE MG_DEVICE_NONE #endif -// Flash information -void *mg_flash_start(void); // Return flash start address -size_t mg_flash_size(void); // Return flash size -size_t mg_flash_sector_size(void); // Return flash sector size -size_t mg_flash_write_align(void); // Return flash write align, minimum 4 -int mg_flash_bank(void); // 0: not dual bank, 1: bank1, 2: bank2 - -// Write, erase, swap bank -bool mg_flash_write(void *addr, const void *buf, size_t len); -bool mg_flash_erase(void *sector); -bool mg_flash_swap_bank(void); - -// Convenience functions to store data on a flash sector with wear levelling -// If `sector` is NULL, then the last sector of flash is used -bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t len); -bool mg_flash_save(void *sector, uint32_t key, const void *buf, size_t len); - -void mg_device_reset(void); // Reboot device immediately - diff --git a/reference-projects/windows-macos-linux/web-ui-dashboard/net.c b/reference-projects/windows-macos-linux/web-ui-dashboard/net.c index 9a80e4daa6..fa73b5bd4c 100644 --- a/reference-projects/windows-macos-linux/web-ui-dashboard/net.c +++ b/reference-projects/windows-macos-linux/web-ui-dashboard/net.c @@ -197,49 +197,9 @@ static void handle_firmware_upload(struct mg_connection *c, mg_http_reply(c, 500, "", "mg_ota_end() failed\n", tot); } else { mg_http_reply(c, 200, s_json_header, "true\n"); - if (data.len == 0) { - // Successful mg_ota_end() called, schedule device reboot - mg_timer_add(c->mgr, 500, 0, (void (*)(void *)) mg_device_reset, NULL); - } } } -static void handle_firmware_commit(struct mg_connection *c) { - mg_http_reply(c, 200, s_json_header, "%s\n", - mg_ota_commit() ? "true" : "false"); -} - -static void handle_firmware_rollback(struct mg_connection *c) { - mg_http_reply(c, 200, s_json_header, "%s\n", - mg_ota_rollback() ? "true" : "false"); -} - -static size_t print_status(void (*out)(char, void *), void *ptr, va_list *ap) { - int fw = va_arg(*ap, int); - return mg_xprintf(out, ptr, "{%m:%d,%m:%c%lx%c,%m:%u,%m:%u}\n", - MG_ESC("status"), mg_ota_status(fw), MG_ESC("crc32"), '"', - mg_ota_crc32(fw), '"', MG_ESC("size"), mg_ota_size(fw), - MG_ESC("timestamp"), mg_ota_timestamp(fw)); -} - -static void handle_firmware_status(struct mg_connection *c) { - mg_http_reply(c, 200, s_json_header, "[%M,%M]\n", print_status, - MG_FIRMWARE_CURRENT, print_status, MG_FIRMWARE_PREVIOUS); -} - -static void handle_device_reset(struct mg_connection *c) { - mg_http_reply(c, 200, s_json_header, "true\n"); - mg_timer_add(c->mgr, 500, 0, (void (*)(void *)) mg_device_reset, NULL); -} - -static void handle_device_eraselast(struct mg_connection *c) { - size_t ss = mg_flash_sector_size(), size = mg_flash_size(); - char *base = (char *) mg_flash_start(), *last = base + size - ss; - if (mg_flash_bank() == 2) last -= size / 2; - mg_flash_erase(last); - mg_http_reply(c, 200, s_json_header, "true\n"); -} - // HTTP request handler function static void ev_handler(struct mg_connection *c, int ev, void *ev_data) { if (ev == MG_EV_ACCEPT) { @@ -271,16 +231,6 @@ static void ev_handler(struct mg_connection *c, int ev, void *ev_data) { handle_settings_set(c, hm->body); } else if (mg_match(hm->uri, mg_str("/api/firmware/upload"), NULL)) { handle_firmware_upload(c, hm); - } else if (mg_match(hm->uri, mg_str("/api/firmware/commit"), NULL)) { - handle_firmware_commit(c); - } else if (mg_match(hm->uri, mg_str("/api/firmware/rollback"), NULL)) { - handle_firmware_rollback(c); - } else if (mg_match(hm->uri, mg_str("/api/firmware/status"), NULL)) { - handle_firmware_status(c); - } else if (mg_match(hm->uri, mg_str("/api/device/reset"), NULL)) { - handle_device_reset(c); - } else if (mg_match(hm->uri, mg_str("/api/device/eraselast"), NULL)) { - handle_device_eraselast(c); } else { struct mg_http_serve_opts opts; memset(&opts, 0, sizeof(opts)); diff --git a/src/device.h b/src/device.h deleted file mode 100644 index 187a79d618..0000000000 --- a/src/device.h +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2023 Cesanta Software Limited -// All rights reserved - -#pragma once - -#include "arch.h" - -#define MG_DEVICE_NONE 0 // Dummy system - -#define MG_DEVICE_STM32H5 1 // STM32 H5 -#define MG_DEVICE_STM32H7 2 // STM32 H7 -#define MG_DEVICE_CH32V307 100 // WCH CH32V307 -#define MG_DEVICE_U2A 200 // Renesas U2A16, U2A8, U2A6 -#define MG_DEVICE_RT1020 300 // IMXRT1020 -#define MG_DEVICE_RT1060 301 // IMXRT1060 -#define MG_DEVICE_CUSTOM 1000 // Custom implementation - -#ifndef MG_DEVICE -#define MG_DEVICE MG_DEVICE_NONE -#endif - -// Flash information -void *mg_flash_start(void); // Return flash start address -size_t mg_flash_size(void); // Return flash size -size_t mg_flash_sector_size(void); // Return flash sector size -size_t mg_flash_write_align(void); // Return flash write align, minimum 4 -int mg_flash_bank(void); // 0: not dual bank, 1: bank1, 2: bank2 - -// Write, erase, swap bank -bool mg_flash_write(void *addr, const void *buf, size_t len); -bool mg_flash_erase(void *sector); -bool mg_flash_swap_bank(void); - -// Convenience functions to store data on a flash sector with wear levelling -// If `sector` is NULL, then the last sector of flash is used -bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t len); -bool mg_flash_save(void *sector, uint32_t key, const void *buf, size_t len); - -void mg_device_reset(void); // Reboot device immediately diff --git a/src/device_ch32v307.c b/src/device_ch32v307.c deleted file mode 100644 index 49d339d821..0000000000 --- a/src/device_ch32v307.c +++ /dev/null @@ -1,78 +0,0 @@ -#include "device.h" -#include "log.h" - -#if MG_DEVICE == MG_DEVICE_CH32V307 -// RM: https://www.wch-ic.com/downloads/CH32FV2x_V3xRM_PDF.html - -#define FLASH_BASE 0x40022000 -#define FLASH_ACTLR (FLASH_BASE + 0) -#define FLASH_KEYR (FLASH_BASE + 4) -#define FLASH_OBKEYR (FLASH_BASE + 8) -#define FLASH_STATR (FLASH_BASE + 12) -#define FLASH_CTLR (FLASH_BASE + 16) -#define FLASH_ADDR (FLASH_BASE + 20) -#define FLASH_OBR (FLASH_BASE + 28) -#define FLASH_WPR (FLASH_BASE + 32) - -void *mg_flash_start(void) { - return (void *) 0x08000000; -} -size_t mg_flash_size(void) { - return 480 * 1024; // First 320k is 0-wait -} -size_t mg_flash_sector_size(void) { - return 4096; -} -size_t mg_flash_write_align(void) { - return 4; -} -int mg_flash_bank(void) { - return 0; -} -void mg_device_reset(void) { - *((volatile uint32_t *) 0xbeef0000) |= 1U << 7; // NVIC_SystemReset() -} -static void flash_unlock(void) { - static bool unlocked; - if (unlocked == false) { - MG_REG(FLASH_KEYR) = 0x45670123; - MG_REG(FLASH_KEYR) = 0xcdef89ab; - unlocked = true; - } -} -static void flash_wait(void) { - while (MG_REG(FLASH_STATR) & MG_BIT(0)) (void) 0; -} - -bool mg_flash_erase(void *addr) { - //MG_INFO(("%p", addr)); - flash_unlock(); - flash_wait(); - MG_REG(FLASH_ADDR) = (uint32_t) addr; - MG_REG(FLASH_CTLR) |= MG_BIT(1) | MG_BIT(6); // PER | STRT; - flash_wait(); - return true; -} - -static bool is_page_boundary(const void *addr) { - uint32_t val = (uint32_t) addr; - return (val & (mg_flash_sector_size() - 1)) == 0; -} - -bool mg_flash_write(void *addr, const void *buf, size_t len) { - //MG_INFO(("%p %p %lu", addr, buf, len)); - //mg_hexdump(buf, len); - flash_unlock(); - const uint16_t *src = (uint16_t *) buf, *end = &src[len / 2]; - uint16_t *dst = (uint16_t *) addr; - MG_REG(FLASH_CTLR) |= MG_BIT(0); // Set PG - //MG_INFO(("CTLR: %#lx", MG_REG(FLASH_CTLR))); - while (src < end) { - if (is_page_boundary(dst)) mg_flash_erase(dst); - *dst++ = *src++; - flash_wait(); - } - MG_REG(FLASH_CTLR) &= ~MG_BIT(0); // Clear PG - return true; -} -#endif diff --git a/src/device_dummy.c b/src/device_dummy.c deleted file mode 100644 index 8eb5a759bb..0000000000 --- a/src/device_dummy.c +++ /dev/null @@ -1,32 +0,0 @@ -#include "device.h" - -#if MG_DEVICE == MG_DEVICE_NONE -void *mg_flash_start(void) { - return NULL; -} -size_t mg_flash_size(void) { - return 0; -} -size_t mg_flash_sector_size(void) { - return 0; -} -size_t mg_flash_write_align(void) { - return 0; -} -int mg_flash_bank(void) { - return 0; -} -bool mg_flash_erase(void *location) { - (void) location; - return false; -} -bool mg_flash_swap_bank(void) { - return true; -} -bool mg_flash_write(void *addr, const void *buf, size_t len) { - (void) addr, (void) buf, (void) len; - return false; -} -void mg_device_reset(void) { -} -#endif diff --git a/src/device_flash.c b/src/device_flash.c deleted file mode 100644 index 0c8c336697..0000000000 --- a/src/device_flash.c +++ /dev/null @@ -1,172 +0,0 @@ -#include "device.h" - -#if MG_DEVICE == MG_DEVICE_STM32H7 || MG_DEVICE == MG_DEVICE_STM32H5 || \ - MG_DEVICE == MG_DEVICE_RT1020 || MG_DEVICE == MG_DEVICE_RT1060 -// Flash can be written only if it is erased. Erased flash is 0xff (all bits 1) -// Writes must be mg_flash_write_align() - aligned. Thus if we want to save an -// object, we pad it at the end for alignment. -// -// Objects in the flash sector are stored sequentially: -// | 32-bit size | 32-bit KEY | ..data.. | ..pad.. | 32-bit size | ...... -// -// In order to get to the next object, read its size, then align up. - -// Traverse the list of saved objects -size_t mg_flash_next(char *p, char *end, uint32_t *key, size_t *size) { - size_t aligned_size = 0, align = mg_flash_write_align(), left = end - p; - uint32_t *p32 = (uint32_t *) p, min_size = sizeof(uint32_t) * 2; - if (p32[0] != 0xffffffff && left > MG_ROUND_UP(min_size, align)) { - if (size) *size = (size_t) p32[0]; - if (key) *key = p32[1]; - aligned_size = MG_ROUND_UP(p32[0] + sizeof(uint32_t) * 2, align); - if (left < aligned_size) aligned_size = 0; // Out of bounds, fail - } - return aligned_size; -} - -// Return the last sector of Bank 2 -static char *flash_last_sector(void) { - size_t ss = mg_flash_sector_size(), size = mg_flash_size(); - char *base = (char *) mg_flash_start(), *last = base + size - ss; - if (mg_flash_bank() == 2) last -= size / 2; - return last; -} - -// Find a saved object with a given key -bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t len) { - char *base = (char *) mg_flash_start(), *s = (char *) sector, *res = NULL; - size_t ss = mg_flash_sector_size(), ofs = 0, n, sz; - bool ok = false; - if (s == NULL) s = flash_last_sector(); - if (s < base || s >= base + mg_flash_size()) { - MG_ERROR(("%p is outsize of flash", sector)); - } else if (((s - base) % ss) != 0) { - MG_ERROR(("%p is not a sector boundary", sector)); - } else { - uint32_t k, scanned = 0; - while ((n = mg_flash_next(s + ofs, s + ss, &k, &sz)) > 0) { - // MG_DEBUG((" > obj %lu, ofs %lu, key %x/%x", scanned, ofs, k, key)); - // mg_hexdump(s + ofs, n); - if (k == key && sz == len) { - res = s + ofs + sizeof(uint32_t) * 2; - memcpy(buf, res, len); // Copy object - ok = true; // Keep scanning for the newer versions of it - } - ofs += n, scanned++; - } - MG_DEBUG(("Scanned %u objects, key %x is @ %p", scanned, key, res)); - } - return ok; -} - -// For all saved objects in the sector, delete old versions of objects -static void mg_flash_sector_cleanup(char *sector) { - // Buffer all saved objects into an IO buffer (backed by RAM) - // erase sector, and re-save them. - struct mg_iobuf io = {0, 0, 0, 2048}; - size_t ss = mg_flash_sector_size(); - size_t n, size, size2, ofs = 0, hs = sizeof(uint32_t) * 2; - uint32_t key; - // Traverse all objects - MG_DEBUG(("Cleaning up sector %p", sector)); - while ((n = mg_flash_next(sector + ofs, sector + ss, &key, &size)) > 0) { - // Delete an old copy of this object in the cache - for (size_t o = 0; o < io.len; o += size2 + hs) { - uint32_t k = *(uint32_t *) (io.buf + o + sizeof(uint32_t)); - size2 = *(uint32_t *) (io.buf + o); - if (k == key) { - mg_iobuf_del(&io, o, size2 + hs); - break; - } - } - // And add the new copy - mg_iobuf_add(&io, io.len, sector + ofs, size + hs); - ofs += n; - } - // All objects are cached in RAM now - if (mg_flash_erase(sector)) { // Erase sector. If successful, - for (ofs = 0; ofs < io.len; ofs += size + hs) { // Traverse cached objects - size = *(uint32_t *) (io.buf + ofs); - key = *(uint32_t *) (io.buf + ofs + sizeof(uint32_t)); - mg_flash_save(sector, key, io.buf + ofs + hs, size); // Save to flash - } - } - mg_iobuf_free(&io); -} - -// Save an object with a given key - append to the end of an object list -bool mg_flash_save(void *sector, uint32_t key, const void *buf, size_t len) { - char *base = (char *) mg_flash_start(), *s = (char *) sector; - size_t ss = mg_flash_sector_size(), ofs = 0, n; - bool ok = false; - if (s == NULL) s = flash_last_sector(); - if (s < base || s >= base + mg_flash_size()) { - MG_ERROR(("%p is outsize of flash", sector)); - } else if (((s - base) % ss) != 0) { - MG_ERROR(("%p is not a sector boundary", sector)); - } else { - char ab[mg_flash_write_align()]; // Aligned write block - uint32_t hdr[2] = {(uint32_t) len, key}; - size_t needed = sizeof(hdr) + len; - size_t needed_aligned = MG_ROUND_UP(needed, sizeof(ab)); - while ((n = mg_flash_next(s + ofs, s + ss, NULL, NULL)) > 0) ofs += n; - - // If there is not enough space left, cleanup sector and re-eval ofs - if (ofs + needed_aligned >= ss) { - mg_flash_sector_cleanup(s); - ofs = 0; - while ((n = mg_flash_next(s + ofs, s + ss, NULL, NULL)) > 0) ofs += n; - } - - if (ofs + needed_aligned <= ss) { - // Enough space to save this object - if (sizeof(ab) < sizeof(hdr)) { - // Flash write granularity is 32 bit or less, write with no buffering - ok = mg_flash_write(s + ofs, hdr, sizeof(hdr)); - if (ok) mg_flash_write(s + ofs + sizeof(hdr), buf, len); - } else { - // Flash granularity is sizeof(hdr) or more. We need to save in - // 3 chunks: initial block, bulk, rest. This is because we have - // two memory chunks to write: hdr and buf, on aligned boundaries. - n = sizeof(ab) - sizeof(hdr); // Initial chunk that we write - if (n > len) n = len; // is - memset(ab, 0xff, sizeof(ab)); // initialized to all-one - memcpy(ab, hdr, sizeof(hdr)); // contains the header (key + size) - memcpy(ab + sizeof(hdr), buf, n); // and an initial part of buf - MG_INFO(("saving initial block of %lu", sizeof(ab))); - ok = mg_flash_write(s + ofs, ab, sizeof(ab)); - if (ok && len > n) { - size_t n2 = MG_ROUND_DOWN(len - n, sizeof(ab)); - if (n2 > 0) { - MG_INFO(("saving bulk, %lu", n2)); - ok = mg_flash_write(s + ofs + sizeof(ab), (char *) buf + n, n2); - } - if (ok && len > n) { - size_t n3 = len - n - n2; - if (n3 > sizeof(ab)) n3 = sizeof(ab); - memset(ab, 0xff, sizeof(ab)); - memcpy(ab, (char *) buf + n + n2, n3); - MG_INFO(("saving rest, %lu", n3)); - ok = mg_flash_write(s + ofs + sizeof(ab) + n2, ab, sizeof(ab)); - } - } - } - MG_DEBUG(("Saved %lu/%lu bytes @ %p, key %x: %d", len, needed_aligned, - s + ofs, key, ok)); - MG_DEBUG(("Sector space left: %lu bytes", ss - ofs - needed_aligned)); - } else { - MG_ERROR(("Sector is full")); - } - } - return ok; -} -#else -bool mg_flash_save(void *sector, uint32_t key, const void *buf, size_t len) { - (void) sector, (void) key, (void) buf, (void) len; - return false; -} -bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t len) { - (void) sector, (void) key, (void) buf, (void) len; - return false; -} -#endif diff --git a/src/flash.c b/src/flash.c index 084943948e..5213511d55 100644 --- a/src/flash.c +++ b/src/flash.c @@ -3,6 +3,8 @@ #include "log.h" #include "ota.h" +#if MG_OTA != MG_OTA_NONE && MG_OTA != MG_OTA_CUSTOM + static char *s_addr; // Current address to write to static size_t s_size; // Firmware size to flash. In-progress indicator static uint32_t s_crc32; // Firmware checksum @@ -63,3 +65,5 @@ bool mg_ota_flash_end(struct mg_flash *flash) { MG_INFO(("Finishing OTA: %s", ok ? "ok" : "fail")); return ok; } + +#endif diff --git a/src/flash.h b/src/flash.h index 6f6d8a6497..9fa04a4cad 100644 --- a/src/flash.h +++ b/src/flash.h @@ -1,9 +1,13 @@ #include "arch.h" +#include "ota.h" + +#if MG_OTA != MG_OTA_NONE && MG_OTA != MG_OTA_CUSTOM struct mg_flash { - void *start; // Address at which flash starts - size_t size; // Flash size - size_t align; // Write alignment + void *start; // Address at which flash starts + size_t size; // Flash size + size_t secsz; // Sector size + size_t align; // Write alignment bool (*write_fn)(void *, const void *, size_t); // Write function bool (*swap_fn)(void); // Swap partitions }; @@ -11,3 +15,5 @@ struct mg_flash { bool mg_ota_flash_begin(size_t new_firmware_size, struct mg_flash *flash); bool mg_ota_flash_write(const void *buf, size_t len, struct mg_flash *flash); bool mg_ota_flash_end(struct mg_flash *flash); + +#endif diff --git a/src/ota.h b/src/ota.h index d8b63273d9..30d9ec3679 100644 --- a/src/ota.h +++ b/src/ota.h @@ -5,40 +5,31 @@ #include "arch.h" -#define MG_OTA_NONE 0 // No OTA support -#define MG_OTA_FLASH 1 // OTA via an internal flash -#define MG_OTA_ESP32 2 // ESP32 OTA implementation -#define MG_OTA_STM32H5 3 // STM32H5 OTA implementation -#define MG_OTA_CUSTOM 100 // Custom implementation +#define MG_OTA_NONE 0 // No OTA support +#define MG_OTA_STM32H5 1 // STM32 H5 +#define MG_OTA_STM32H7 2 // STM32 H7 +#define MG_OTA_CH32V307 100 // WCH CH32V307 +#define MG_OTA_U2A 200 // Renesas U2A16, U2A8, U2A6 +#define MG_OTA_RT1020 300 // IMXRT1020 +#define MG_OTA_RT1060 301 // IMXRT1060 +#define MG_OTA_MCXN 310 // MCXN947 +#define MG_OTA_FLASH 900 // OTA via an internal flash +#define MG_OTA_ESP32 910 // ESP32 OTA implementation +#define MG_OTA_CUSTOM 1000 // Custom implementation #ifndef MG_OTA #define MG_OTA MG_OTA_NONE -#endif - -#if defined(__GNUC__) && !defined(__APPLE__) +#else +#ifndef MG_IRAM +#if defined(__GNUC__) #define MG_IRAM __attribute__((section(".iram"))) #else #define MG_IRAM -#endif +#endif // compiler +#endif // IRAM +#endif // OTA // Firmware update API bool mg_ota_begin(size_t new_firmware_size); // Start writing bool mg_ota_write(const void *buf, size_t len); // Write chunk, aligned to 1k bool mg_ota_end(void); // Stop writing - -enum { - MG_OTA_UNAVAILABLE = 0, // No OTA information is present - MG_OTA_FIRST_BOOT = 1, // Device booting the first time after the OTA - MG_OTA_UNCOMMITTED = 2, // Ditto, but marking us for the rollback - MG_OTA_COMMITTED = 3 // The firmware is good -}; -enum { MG_FIRMWARE_CURRENT = 0, MG_FIRMWARE_PREVIOUS = 1 }; - -int mg_ota_status(int firmware); // Return firmware status MG_OTA_* -uint32_t mg_ota_crc32(int firmware); // Return firmware checksum -uint32_t mg_ota_timestamp(int firmware); // Firmware timestamp, UNIX UTC epoch -size_t mg_ota_size(int firmware); // Firmware size - -bool mg_ota_commit(void); // Commit current firmware -bool mg_ota_rollback(void); // Rollback to the previous firmware -MG_IRAM void mg_ota_boot(void); // Bootloader function diff --git a/src/ota_ch32v307.c b/src/ota_ch32v307.c new file mode 100644 index 0000000000..d9347ac439 --- /dev/null +++ b/src/ota_ch32v307.c @@ -0,0 +1,112 @@ +#include "flash.h" +#include "log.h" +#include "ota.h" + +#if MG_OTA == MG_OTA_CH32V307 +// RM: https://www.wch-ic.com/downloads/CH32FV2x_V3xRM_PDF.html + +static bool mg_ch32v307_write(void *, const void *, size_t); +static bool mg_ch32v307_swap(void); + +static struct mg_flash s_mg_flash_ch32v307 = { + (void *) 0x08000000, // Start + 480 * 1024, // Size, first 320k is 0-wait + 4 * 1024, // Sector size, 4k + 4, // Align, 32 bit + mg_ch32v307_write, + mg_ch32v307_swap, +}; + +#define FLASH_BASE 0x40022000 +#define FLASH_ACTLR (FLASH_BASE + 0) +#define FLASH_KEYR (FLASH_BASE + 4) +#define FLASH_OBKEYR (FLASH_BASE + 8) +#define FLASH_STATR (FLASH_BASE + 12) +#define FLASH_CTLR (FLASH_BASE + 16) +#define FLASH_ADDR (FLASH_BASE + 20) +#define FLASH_OBR (FLASH_BASE + 28) +#define FLASH_WPR (FLASH_BASE + 32) + +MG_IRAM static void flash_unlock(void) { + static bool unlocked; + if (unlocked == false) { + MG_REG(FLASH_KEYR) = 0x45670123; + MG_REG(FLASH_KEYR) = 0xcdef89ab; + unlocked = true; + } +} + +MG_IRAM static void flash_wait(void) { + while (MG_REG(FLASH_STATR) & MG_BIT(0)) (void) 0; +} + +MG_IRAM static void mg_ch32v307_erase(void *addr) { + // MG_INFO(("%p", addr)); + flash_unlock(); + flash_wait(); + MG_REG(FLASH_ADDR) = (uint32_t) addr; + MG_REG(FLASH_CTLR) |= MG_BIT(1) | MG_BIT(6); // PER | STRT; + flash_wait(); +} + +MG_IRAM static bool is_page_boundary(const void *addr) { + uint32_t val = (uint32_t) addr; + return (val & (s_mg_flash_ch32v307.secsz - 1)) == 0; +} + +MG_IRAM static bool mg_ch32v307_write(void *addr, const void *buf, size_t len) { + // MG_INFO(("%p %p %lu", addr, buf, len)); + // mg_hexdump(buf, len); + flash_unlock(); + const uint16_t *src = (uint16_t *) buf, *end = &src[len / 2]; + uint16_t *dst = (uint16_t *) addr; + MG_REG(FLASH_CTLR) |= MG_BIT(0); // Set PG + // MG_INFO(("CTLR: %#lx", MG_REG(FLASH_CTLR))); + while (src < end) { + if (is_page_boundary(dst)) mg_ch32v307_erase(dst); + *dst++ = *src++; + flash_wait(); + } + MG_REG(FLASH_CTLR) &= ~MG_BIT(0); // Clear PG + return true; +} + +MG_IRAM bool mg_ch32v307_swap(void) { + return true; +} + +// just overwrite instead of swap +MG_IRAM static void single_bank_swap(char *p1, char *p2, size_t s, size_t ss) { + // no stdlib calls here + for (size_t ofs = 0; ofs < s; ofs += ss) { + mg_ch32v307_write(p1 + ofs, p2 + ofs, ss); + } + *((volatile uint32_t *) 0xbeef0000) |= 1U << 7; // NVIC_SystemReset() +} + +bool mg_ota_begin(size_t new_firmware_size) { + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_ch32v307); +} + +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_ch32v307); +} + +bool mg_ota_end(void) { + if (mg_ota_flash_end(&s_mg_flash_ch32v307)) { + // Swap partitions. Pray power does not go away + MG_INFO(("Swapping partitions, size %u (%u sectors)", + s_mg_flash_ch32v307.size, + s_mg_flash_ch32v307.size / s_mg_flash_ch32v307.secsz)); + MG_INFO(("Do NOT power off...")); + mg_log_level = MG_LL_NONE; + // TODO() disable IRQ, s_flash_irq_disabled = true; + // Runs in RAM, will reset when finished + single_bank_swap( + (char *) s_mg_flash_ch32v307.start, + (char *) s_mg_flash_ch32v307.start + s_mg_flash_ch32v307.size / 2, + s_mg_flash_ch32v307.size / 2, s_mg_flash_ch32v307.secsz); + } + return false; +} +#endif diff --git a/src/ota_dummy.c b/src/ota_dummy.c index 5af659c8e9..cf31976ee0 100644 --- a/src/ota_dummy.c +++ b/src/ota_dummy.c @@ -13,28 +13,4 @@ bool mg_ota_write(const void *buf, size_t len) { bool mg_ota_end(void) { return true; } -bool mg_ota_commit(void) { - return true; -} -bool mg_ota_rollback(void) { - return true; -} -int mg_ota_status(int fw) { - (void) fw; - return 0; -} -uint32_t mg_ota_crc32(int fw) { - (void) fw; - return 0; -} -uint32_t mg_ota_timestamp(int fw) { - (void) fw; - return 0; -} -size_t mg_ota_size(int fw) { - (void) fw; - return 0; -} -MG_IRAM void mg_ota_boot(void) { -} #endif diff --git a/src/ota_flash.c b/src/ota_flash.c deleted file mode 100644 index d52d90c336..0000000000 --- a/src/ota_flash.c +++ /dev/null @@ -1,199 +0,0 @@ -#include "arch.h" -#include "device.h" -#include "log.h" -#include "ota.h" - -// This OTA implementation uses the internal flash API outlined in device.h -// It splits flash into 2 equal partitions, and stores OTA status in the -// last sector of the partition. - -#if MG_OTA == MG_OTA_FLASH - -#define MG_OTADATA_KEY 0xb07afed0 - -static char *s_addr; // Current address to write to -static size_t s_size; // Firmware size to flash. In-progress indicator -static uint32_t s_crc32; // Firmware checksum - -struct mg_otadata { - uint32_t crc32, size, timestamp, status; -}; - -bool mg_ota_begin(size_t new_firmware_size) { - bool ok = false; - if (s_size) { - MG_ERROR(("OTA already in progress. Call mg_ota_end()")); - } else { - size_t half = mg_flash_size() / 2, max = half - mg_flash_sector_size(); - s_crc32 = 0; - s_addr = (char *) mg_flash_start() + half; - MG_DEBUG(("Firmware %lu bytes, max %lu", new_firmware_size, max)); - if (new_firmware_size < max) { - ok = true; - s_size = new_firmware_size; - MG_INFO(("Starting OTA, firmware size %lu", s_size)); - } else { - MG_ERROR(("Firmware %lu is too big to fit %lu", new_firmware_size, max)); - } - } - return ok; -} - -bool mg_ota_write(const void *buf, size_t len) { - bool ok = false; - if (s_size == 0) { - MG_ERROR(("OTA is not started, call mg_ota_begin()")); - } else { - size_t align = mg_flash_write_align(); - size_t len_aligned_down = MG_ROUND_DOWN(len, align); - if (len_aligned_down) ok = mg_flash_write(s_addr, buf, len_aligned_down); - if (len_aligned_down < len) { - size_t left = len - len_aligned_down; - char tmp[align]; - memset(tmp, 0xff, sizeof(tmp)); - memcpy(tmp, (char *) buf + len_aligned_down, left); - ok = mg_flash_write(s_addr + len_aligned_down, tmp, sizeof(tmp)); - } - s_crc32 = mg_crc32(s_crc32, (char *) buf, len); // Update CRC - MG_DEBUG(("%#x %p %lu -> %d", s_addr - len, buf, len, ok)); - s_addr += len; - } - return ok; -} - -MG_IRAM static uint32_t mg_fwkey(int fw) { - uint32_t key = MG_OTADATA_KEY + fw; - int bank = mg_flash_bank(); - if (bank == 2 && fw == MG_FIRMWARE_PREVIOUS) key--; - if (bank == 2 && fw == MG_FIRMWARE_CURRENT) key++; - return key; -} - -bool mg_ota_end(void) { - char *base = (char *) mg_flash_start() + mg_flash_size() / 2; - bool ok = false; - if (s_size) { - size_t size = s_addr - base; - uint32_t crc32 = mg_crc32(0, base, s_size); - if (size == s_size && crc32 == s_crc32) { - uint32_t now = (uint32_t) (mg_now() / 1000); - struct mg_otadata od = {crc32, size, now, MG_OTA_FIRST_BOOT}; - uint32_t key = mg_fwkey(MG_FIRMWARE_PREVIOUS); - ok = mg_flash_save(NULL, key, &od, sizeof(od)); - } - MG_DEBUG(("CRC: %x/%x, size: %lu/%lu, status: %s", s_crc32, crc32, s_size, - size, ok ? "ok" : "fail")); - s_size = 0; - if (ok) ok = mg_flash_swap_bank(); - } - MG_INFO(("Finishing OTA: %s", ok ? "ok" : "fail")); - return ok; -} - -MG_IRAM static struct mg_otadata mg_otadata(int fw) { - uint32_t key = mg_fwkey(fw); - struct mg_otadata od = {}; - MG_INFO(("Loading %s OTA data", fw == MG_FIRMWARE_CURRENT ? "curr" : "prev")); - mg_flash_load(NULL, key, &od, sizeof(od)); - // MG_DEBUG(("Loaded OTA data. fw %d, bank %d, key %p", fw, bank, key)); - // mg_hexdump(&od, sizeof(od)); - return od; -} - -int mg_ota_status(int fw) { - struct mg_otadata od = mg_otadata(fw); - return od.status; -} -uint32_t mg_ota_crc32(int fw) { - struct mg_otadata od = mg_otadata(fw); - return od.crc32; -} -uint32_t mg_ota_timestamp(int fw) { - struct mg_otadata od = mg_otadata(fw); - return od.timestamp; -} -size_t mg_ota_size(int fw) { - struct mg_otadata od = mg_otadata(fw); - return od.size; -} - -MG_IRAM bool mg_ota_commit(void) { - bool ok = true; - struct mg_otadata od = mg_otadata(MG_FIRMWARE_CURRENT); - if (od.status != MG_OTA_COMMITTED) { - od.status = MG_OTA_COMMITTED; - MG_INFO(("Committing current firmware, OD size %lu", sizeof(od))); - ok = mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_CURRENT), &od, sizeof(od)); - } - return ok; -} - -bool mg_ota_rollback(void) { - MG_DEBUG(("Rolling firmware back")); - if (mg_flash_bank() == 0) { - // No dual bank support. Mark previous firmware as FIRST_BOOT - struct mg_otadata prev = mg_otadata(MG_FIRMWARE_PREVIOUS); - prev.status = MG_OTA_FIRST_BOOT; - return mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_PREVIOUS, &prev, - sizeof(prev)); - } else { - return mg_flash_swap_bank(); - } -} - -MG_IRAM void mg_ota_boot(void) { - MG_INFO(("Booting. Flash bank: %d", mg_flash_bank())); - struct mg_otadata curr = mg_otadata(MG_FIRMWARE_CURRENT); - struct mg_otadata prev = mg_otadata(MG_FIRMWARE_PREVIOUS); - - if (curr.status == MG_OTA_FIRST_BOOT) { - if (prev.status == MG_OTA_UNAVAILABLE) { - MG_INFO(("Setting previous firmware state to committed")); - prev.status = MG_OTA_COMMITTED; - mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_PREVIOUS), &prev, sizeof(prev)); - } - curr.status = MG_OTA_UNCOMMITTED; - MG_INFO(("First boot, setting status to UNCOMMITTED")); - mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_CURRENT), &curr, sizeof(curr)); - } else if (prev.status == MG_OTA_FIRST_BOOT && mg_flash_bank() == 0) { - // Swap paritions. Pray power does not disappear - size_t fs = mg_flash_size(), ss = mg_flash_sector_size(); - char *partition1 = mg_flash_start(); - char *partition2 = mg_flash_start() + fs / 2; - size_t ofs, max = fs / 2 - ss; // Set swap size to the whole partition - - if (curr.status != MG_OTA_UNAVAILABLE && - prev.status != MG_OTA_UNAVAILABLE) { - // We know exact sizes of both firmwares. - // Shrink swap size to the MAX(firmware1, firmware2) - size_t sz = curr.size > prev.size ? curr.size : prev.size; - if (sz > 0 && sz < max) max = sz; - } - - // MG_OTA_FIRST_BOOT -> MG_OTA_UNCOMMITTED - prev.status = MG_OTA_UNCOMMITTED; - mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_CURRENT, &prev, - sizeof(prev)); - mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_PREVIOUS, &curr, - sizeof(curr)); - - MG_INFO(("Swapping partitions, size %u (%u sectors)", max, max / ss)); - MG_INFO(("Do NOT power off...")); - mg_log_level = MG_LL_NONE; - - // We use the last sector of partition2 for OTA data/config storage - // Therefore we can use last sector of partition1 for swapping - char *tmpsector = partition1 + fs / 2 - ss; // Last sector of partition1 - (void) tmpsector; - for (ofs = 0; ofs < max; ofs += ss) { - // mg_flash_erase(tmpsector); - mg_flash_write(tmpsector, partition1 + ofs, ss); - // mg_flash_erase(partition1 + ofs); - mg_flash_write(partition1 + ofs, partition2 + ofs, ss); - // mg_flash_erase(partition2 + ofs); - mg_flash_write(partition2 + ofs, tmpsector, ss); - } - mg_device_reset(); - } -} -#endif diff --git a/src/device_imxrt.c b/src/ota_imxrt.c similarity index 50% rename from src/device_imxrt.c rename to src/ota_imxrt.c index 9571b1365a..fc6fdbbdb4 100644 --- a/src/device_imxrt.c +++ b/src/ota_imxrt.c @@ -1,7 +1,22 @@ -#include "device.h" +#include "flash.h" #include "log.h" - -#if MG_DEVICE == MG_DEVICE_RT1020 || MG_DEVICE == MG_DEVICE_RT1060 +#include "ota.h" + +#if MG_OTA == MG_OTA_RT1020 || MG_OTA == MG_OTA_RT1060 + +static bool mg_imxrt_write(void *, const void *, size_t); +static bool mg_imxrt_swap(void); + +// TODO(): fill at init, support more devices in a dynamic way +// TODO(): then, check alignment is <= 256, see Wizard's #251 +static struct mg_flash s_mg_flash_imxrt = { + (void *) 0x60000000, // Start, + 8 * 1024 * 1024, // Size, 8mb + 4 * 1024, // Sector size, 4k + 256, // Align, + mg_imxrt_write, + mg_imxrt_swap, +}; struct mg_flexspi_lut_seq { uint8_t seqNum; @@ -71,9 +86,10 @@ struct mg_flexspi_nor_config { #define MG_FLEXSPI_CFG_BLK_TAG (0x42464346UL) // ascii "FCFB" Big Endian #define MG_FLEXSPI_CFG_BLK_VERSION (0x56010400UL) // V1.4.0 -#define MG_FLEXSPI_LUT_SEQ(cmd0, pad0, op0, cmd1, pad1, op1) \ - (MG_FLEXSPI_LUT_OPERAND0(op0) | MG_FLEXSPI_LUT_NUM_PADS0(pad0) | MG_FLEXSPI_LUT_OPCODE0(cmd0) | \ - MG_FLEXSPI_LUT_OPERAND1(op1) | MG_FLEXSPI_LUT_NUM_PADS1(pad1) | MG_FLEXSPI_LUT_OPCODE1(cmd1)) +#define MG_FLEXSPI_LUT_SEQ(cmd0, pad0, op0, cmd1, pad1, op1) \ + (MG_FLEXSPI_LUT_OPERAND0(op0) | MG_FLEXSPI_LUT_NUM_PADS0(pad0) | \ + MG_FLEXSPI_LUT_OPCODE0(cmd0) | MG_FLEXSPI_LUT_OPERAND1(op1) | \ + MG_FLEXSPI_LUT_NUM_PADS1(pad1) | MG_FLEXSPI_LUT_OPCODE1(cmd1)) #define MG_CMD_SDR 0x01 #define MG_CMD_DDR 0x21 @@ -92,101 +108,93 @@ struct mg_flexspi_nor_config { #define MG_FLEXSPI_4PAD 2 #define MG_FLEXSPI_8PAD 3 -#define MG_FLEXSPI_QSPI_LUT \ - { \ - [0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0xEB, MG_RADDR_SDR, MG_FLEXSPI_4PAD, \ - 0x18), \ - [1] = MG_FLEXSPI_LUT_SEQ(MG_DUMMY_SDR, MG_FLEXSPI_4PAD, 0x06, MG_READ_SDR, MG_FLEXSPI_4PAD, \ - 0x04), \ - [4 * 1 + 0] = \ - MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x05, MG_READ_SDR, MG_FLEXSPI_1PAD, 0x04), \ - [4 * 3 + 0] = \ - MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x06, MG_STOP, MG_FLEXSPI_1PAD, 0x0), \ - [4 * 5 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x20, MG_RADDR_SDR, \ - MG_FLEXSPI_1PAD, 0x18), \ - [4 * 8 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0xD8, MG_RADDR_SDR, \ - MG_FLEXSPI_1PAD, 0x18), \ - [4 * 9 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x02, MG_RADDR_SDR, \ - MG_FLEXSPI_1PAD, 0x18), \ - [4 * 9 + 1] = \ - MG_FLEXSPI_LUT_SEQ(MG_WRITE_SDR, MG_FLEXSPI_1PAD, 0x04, MG_STOP, MG_FLEXSPI_1PAD, 0x0), \ - [4 * 11 + 0] = \ - MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x60, MG_STOP, MG_FLEXSPI_1PAD, 0x0), \ +#define MG_FLEXSPI_QSPI_LUT \ + { \ + [0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0xEB, MG_RADDR_SDR, \ + MG_FLEXSPI_4PAD, 0x18), \ + [1] = MG_FLEXSPI_LUT_SEQ(MG_DUMMY_SDR, MG_FLEXSPI_4PAD, 0x06, MG_READ_SDR, \ + MG_FLEXSPI_4PAD, 0x04), \ + [4 * 1 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x05, \ + MG_READ_SDR, MG_FLEXSPI_1PAD, 0x04), \ + [4 * 3 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x06, \ + MG_STOP, MG_FLEXSPI_1PAD, 0x0), \ + [4 * 5 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x20, \ + MG_RADDR_SDR, MG_FLEXSPI_1PAD, 0x18), \ + [4 * 8 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0xD8, \ + MG_RADDR_SDR, MG_FLEXSPI_1PAD, 0x18), \ + [4 * 9 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x02, \ + MG_RADDR_SDR, MG_FLEXSPI_1PAD, 0x18), \ + [4 * 9 + 1] = MG_FLEXSPI_LUT_SEQ(MG_WRITE_SDR, MG_FLEXSPI_1PAD, 0x04, \ + MG_STOP, MG_FLEXSPI_1PAD, 0x0), \ + [4 * 11 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x60, \ + MG_STOP, MG_FLEXSPI_1PAD, 0x0), \ } #define MG_FLEXSPI_LUT_OPERAND0(x) (((uint32_t) (((uint32_t) (x)))) & 0xFFU) -#define MG_FLEXSPI_LUT_NUM_PADS0(x) (((uint32_t) (((uint32_t) (x)) << 8U)) & 0x300U) -#define MG_FLEXSPI_LUT_OPCODE0(x) (((uint32_t) (((uint32_t) (x)) << 10U)) & 0xFC00U) -#define MG_FLEXSPI_LUT_OPERAND1(x) (((uint32_t) (((uint32_t) (x)) << 16U)) & 0xFF0000U) -#define MG_FLEXSPI_LUT_NUM_PADS1(x) (((uint32_t) (((uint32_t) (x)) << 24U)) & 0x3000000U) -#define MG_FLEXSPI_LUT_OPCODE1(x) (((uint32_t) (((uint32_t) (x)) << 26U)) & 0xFC000000U) +#define MG_FLEXSPI_LUT_NUM_PADS0(x) \ + (((uint32_t) (((uint32_t) (x)) << 8U)) & 0x300U) +#define MG_FLEXSPI_LUT_OPCODE0(x) \ + (((uint32_t) (((uint32_t) (x)) << 10U)) & 0xFC00U) +#define MG_FLEXSPI_LUT_OPERAND1(x) \ + (((uint32_t) (((uint32_t) (x)) << 16U)) & 0xFF0000U) +#define MG_FLEXSPI_LUT_NUM_PADS1(x) \ + (((uint32_t) (((uint32_t) (x)) << 24U)) & 0x3000000U) +#define MG_FLEXSPI_LUT_OPCODE1(x) \ + (((uint32_t) (((uint32_t) (x)) << 26U)) & 0xFC000000U) #define FLEXSPI_NOR_INSTANCE 0 -#if MG_DEVICE == MG_DEVICE_RT1020 +#if MG_OTA == MG_OTA_RT1020 struct mg_flexspi_nor_driver_interface { uint32_t version; int (*init)(uint32_t instance, struct mg_flexspi_nor_config *config); - int (*program)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t dst_addr, - const uint32_t *src); + int (*program)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t dst_addr, const uint32_t *src); uint32_t reserved; - int (*erase)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t start, - uint32_t lengthInBytes); + int (*erase)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t start, uint32_t lengthInBytes); uint32_t reserved2; - int (*update_lut)(uint32_t instance, uint32_t seqIndex, const uint32_t *lutBase, - uint32_t seqNumber); + int (*update_lut)(uint32_t instance, uint32_t seqIndex, + const uint32_t *lutBase, uint32_t seqNumber); int (*xfer)(uint32_t instance, char *xfer); void (*clear_cache)(uint32_t instance); }; -#elif MG_DEVICE == MG_DEVICE_RT1060 +#elif MG_OTA == MG_OTA_RT1060 struct mg_flexspi_nor_driver_interface { uint32_t version; int (*init)(uint32_t instance, struct mg_flexspi_nor_config *config); - int (*program)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t dst_addr, - const uint32_t *src); + int (*program)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t dst_addr, const uint32_t *src); int (*erase_all)(uint32_t instance, struct mg_flexspi_nor_config *config); - int (*erase)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t start, - uint32_t lengthInBytes); - int (*read)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t *dst, uint32_t addr, - uint32_t lengthInBytes); + int (*erase)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t start, uint32_t lengthInBytes); + int (*read)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t *dst, uint32_t addr, uint32_t lengthInBytes); void (*clear_cache)(uint32_t instance); int (*xfer)(uint32_t instance, char *xfer); - int (*update_lut)(uint32_t instance, uint32_t seqIndex, const uint32_t *lutBase, - uint32_t seqNumber); - int (*get_config)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t *option); + int (*update_lut)(uint32_t instance, uint32_t seqIndex, + const uint32_t *lutBase, uint32_t seqNumber); + int (*get_config)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t *option); }; #endif -#define flexspi_nor (*((struct mg_flexspi_nor_driver_interface**) \ - (*(uint32_t*)0x0020001c + 16))) +#define flexspi_nor \ + (*((struct mg_flexspi_nor_driver_interface **) (*(uint32_t *) 0x0020001c + \ + 16))) static bool s_flash_irq_disabled; -MG_IRAM void *mg_flash_start(void) { - return (void *) 0x60000000; -} -MG_IRAM size_t mg_flash_size(void) { - return 8 * 1024 * 1024; -} -MG_IRAM size_t mg_flash_sector_size(void) { - return 4 * 1024; // 4k -} -MG_IRAM size_t mg_flash_write_align(void) { - return 256; -} -MG_IRAM int mg_flash_bank(void) { - return 0; -} - MG_IRAM static bool flash_page_start(volatile uint32_t *dst) { - char *base = (char *) mg_flash_start(), *end = base + mg_flash_size(); + char *base = (char *) s_mg_flash_imxrt.start, *end = base + s_mg_flash_imxrt.size; volatile char *p = (char *) dst; - return p >= base && p < end && ((p - base) % mg_flash_sector_size()) == 0; + return p >= base && p < end && ((p - base) % s_mg_flash_imxrt.secsz) == 0; } // Note: the get_config function below works both for RT1020 and 1060 -#if MG_DEVICE == MG_DEVICE_RT1020 -MG_IRAM static int flexspi_nor_get_config(struct mg_flexspi_nor_config *config) { +#if MG_OTA == MG_OTA_RT1020 +MG_IRAM static int flexspi_nor_get_config( + struct mg_flexspi_nor_config *config) { struct mg_flexspi_nor_config default_config = { .memConfig = {.tag = MG_FLEXSPI_CFG_BLK_TAG, .version = MG_FLEXSPI_CFG_BLK_VERSION, @@ -209,7 +217,8 @@ MG_IRAM static int flexspi_nor_get_config(struct mg_flexspi_nor_config *config) return 0; } #else -MG_IRAM static int flexspi_nor_get_config(struct mg_flexspi_nor_config *config) { +MG_IRAM static int flexspi_nor_get_config( + struct mg_flexspi_nor_config *config) { uint32_t options[] = {0xc0000000, 0x00}; MG_ARM_DISABLE_IRQ(); @@ -225,31 +234,36 @@ MG_IRAM static int flexspi_nor_get_config(struct mg_flexspi_nor_config *config) } #endif -MG_IRAM bool mg_flash_erase(void *addr) { - struct mg_flexspi_nor_config config; - if (flexspi_nor_get_config(&config) != 0) { - return false; - } +MG_IRAM static bool flash_erase(struct mg_flexspi_nor_config *config, + void *addr) { if (flash_page_start(addr) == false) { MG_ERROR(("%p is not on a sector boundary", addr)); return false; } - void *dst = (void *)((char *) addr - (char *) mg_flash_start()); + void *dst = (void *) ((char *) addr - (char *) s_mg_flash_imxrt.start); - // Note: Interrupts must be disabled before any call to the ROM API on RT1020 - // and 1060 - MG_ARM_DISABLE_IRQ(); - bool ok = (flexspi_nor->erase(FLEXSPI_NOR_INSTANCE, &config, (uint32_t) dst, - mg_flash_sector_size()) == 0); - if (!s_flash_irq_disabled) { - MG_ARM_ENABLE_IRQ(); // Reenable them after the call - } + bool ok = (flexspi_nor->erase(FLEXSPI_NOR_INSTANCE, config, (uint32_t) dst, + s_mg_flash_imxrt.secsz) == 0); MG_DEBUG(("Sector starting at %p erasure: %s", addr, ok ? "ok" : "fail")); return ok; } -MG_IRAM bool mg_flash_swap_bank(void) { +#if 0 +// standalone erase call +MG_IRAM static bool mg_imxrt_erase(void *addr) { + struct mg_flexspi_nor_config config; + bool ret; + // Interrupts must be disabled before calls to ROM API in RT1020 and 1060 + MG_ARM_DISABLE_IRQ(); + ret = (flexspi_nor_get_config(&config) == 0); + if (ret) ret = flash_erase(&config, addr); + MG_ARM_ENABLE_IRQ(); + return ret; +} +#endif + +MG_IRAM bool mg_imxrt_swap(void) { return true; } @@ -258,82 +272,109 @@ static inline void spin(volatile uint32_t count) { } static inline void flash_wait(void) { - while ((*((volatile uint32_t *)(0x402A8000 + 0xE0)) & MG_BIT(1)) == 0) + while ((*((volatile uint32_t *) (0x402A8000 + 0xE0)) & MG_BIT(1)) == 0) spin(1); } -MG_IRAM static void *flash_code_location(void) { - return (void *) ((char *) mg_flash_start() + 0x2000); -} - -MG_IRAM bool mg_flash_write(void *addr, const void *buf, size_t len) { +MG_IRAM static bool mg_imxrt_write(void *addr, const void *buf, size_t len) { struct mg_flexspi_nor_config config; - if (flexspi_nor_get_config(&config) != 0) { - return false; - } - if ((len % mg_flash_write_align()) != 0) { - MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align())); - return false; + bool ok = false; + // Interrupts must be disabled before calls to ROM API in RT1020 and 1060 + MG_ARM_DISABLE_IRQ(); + if (flexspi_nor_get_config(&config) != 0) goto fwxit; + if ((len % s_mg_flash_imxrt.align) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, s_mg_flash_imxrt.align)); + goto fwxit; } - - if ((char *) addr < (char *) mg_flash_start()) { + if ((char *) addr < (char *) s_mg_flash_imxrt.start) { MG_ERROR(("Invalid flash write address: %p", addr)); - return false; + goto fwxit; } uint32_t *dst = (uint32_t *) addr; uint32_t *src = (uint32_t *) buf; uint32_t *end = (uint32_t *) ((char *) buf + len); - bool ok = true; - - // Note: If we overwrite the flash irq section of the image, we must also - // make sure interrupts are disabled and are not reenabled until we write - // this sector with another irq table. - if ((char *) addr == (char *) flash_code_location()) { - s_flash_irq_disabled = true; - MG_ARM_DISABLE_IRQ(); - } + ok = true; while (ok && src < end) { - if (flash_page_start(dst) && mg_flash_erase(dst) == false) { + if (flash_page_start(dst) && flash_erase(&config, dst) == false) { + ok = false; break; } uint32_t status; - uint32_t dst_ofs = (uint32_t) dst - (uint32_t) mg_flash_start(); - if ((char *) buf >= (char *) mg_flash_start()) { + uint32_t dst_ofs = (uint32_t) dst - (uint32_t) s_mg_flash_imxrt.start; + if ((char *) buf >= (char *) s_mg_flash_imxrt.start) { // If we copy from FLASH to FLASH, then we first need to copy the source // to RAM - size_t tmp_buf_size = mg_flash_write_align() / sizeof(uint32_t); + size_t tmp_buf_size = s_mg_flash_imxrt.align / sizeof(uint32_t); uint32_t tmp[tmp_buf_size]; for (size_t i = 0; i < tmp_buf_size; i++) { flash_wait(); tmp[i] = src[i]; } - MG_ARM_DISABLE_IRQ(); status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, &config, (uint32_t) dst_ofs, tmp); } else { - MG_ARM_DISABLE_IRQ(); status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, &config, (uint32_t) dst_ofs, src); } - if (!s_flash_irq_disabled) { - MG_ARM_ENABLE_IRQ(); - } - src = (uint32_t *) ((char *) src + mg_flash_write_align()); - dst = (uint32_t *) ((char *) dst + mg_flash_write_align()); + src = (uint32_t *) ((char *) src + s_mg_flash_imxrt.align); + dst = (uint32_t *) ((char *) dst + s_mg_flash_imxrt.align); if (status != 0) { ok = false; } } MG_DEBUG(("Flash write %lu bytes @ %p: %s.", len, dst, ok ? "ok" : "fail")); +fwxit: + if (!s_flash_irq_disabled) MG_ARM_ENABLE_IRQ(); return ok; } -MG_IRAM void mg_device_reset(void) { - MG_DEBUG(("Resetting device...")); +// just overwrite instead of swap +MG_IRAM static void single_bank_swap(char *p1, char *p2, size_t s, size_t ss) { + // no stdlib calls here + for (size_t ofs = 0; ofs < s; ofs += ss) { + mg_imxrt_write(p1 + ofs, p2 + ofs, ss); + } *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; } +bool mg_ota_begin(size_t new_firmware_size) { + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_imxrt); +} + +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_imxrt); +} + +bool mg_ota_end(void) { + if (mg_ota_flash_end(&s_mg_flash_imxrt)) { + if (0) { // is_dualbank() + // TODO(): no devices so far + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; + } else { + // Swap partitions. Pray power does not go away + char *tmpsector = malloc(s_mg_flash_imxrt.secsz); + bool ramtmp = (tmpsector != NULL); + if (!ramtmp) { + MG_ERROR(("OOM")); + return false; + } + MG_INFO(("Swapping partitions, size %u (%u sectors)", + s_mg_flash_imxrt.size, + s_mg_flash_imxrt.size / s_mg_flash_imxrt.secsz)); + MG_INFO(("Do NOT power off...")); + mg_log_level = MG_LL_NONE; + s_flash_irq_disabled = true; + // Runs in RAM, will reset when finished + single_bank_swap( + (char *) s_mg_flash_imxrt.start, + (char *) s_mg_flash_imxrt.start + s_mg_flash_imxrt.size / 2, + s_mg_flash_imxrt.size / 2, s_mg_flash_imxrt.secsz); + } + } + return false; +} + #endif diff --git a/src/ota_mcxn.c b/src/ota_mcxn.c new file mode 100644 index 0000000000..44802fa054 --- /dev/null +++ b/src/ota_mcxn.c @@ -0,0 +1,209 @@ +#include "flash.h" +#include "log.h" +#include "ota.h" + +#if MG_OTA == MG_OTA_MCXN + +// - Flash phrase: 16 bytes; smallest portion programmed in one operation. +// - Flash page: 128 bytes; largest portion programmed in one operation. +// - Flash sector: 8 KB; smallest portion that can be erased in one operation. +// - Flash API mg_flash_driver->program: "start" and "len" must be page-size +// aligned; to use 'phrase', FMU register access is needed. Using ROM + +static bool mg_mcxn_write(void *, const void *, size_t); +static bool mg_mcxn_swap(void); + +static struct mg_flash s_mg_flash_mcxn = { + (void *) 0, // Start, filled at init + 0, // Size, filled at init + 0, // Sector size, filled at init + 0, // Align, filled at init + mg_mcxn_write, + mg_mcxn_swap, +}; + +struct mg_flash_config { + uint32_t addr; + uint32_t size; + uint32_t blocks; + uint32_t page_size; + uint32_t sector_size; + uint32_t ffr[6]; + uint32_t reserved0[5]; + uint32_t *bootctx; + bool useahb; +}; + +struct mg_flash_driver_interface { + uint32_t version; + uint32_t (*init)(struct mg_flash_config *); + uint32_t (*erase)(struct mg_flash_config *, uint32_t start, uint32_t len, + uint32_t key); + uint32_t (*program)(struct mg_flash_config *, uint32_t start, uint8_t *src, + uint32_t len); + uint32_t (*verify_erase)(struct mg_flash_config *, uint32_t start, + uint32_t len); + uint32_t (*verify_program)(struct mg_flash_config *, uint32_t start, + uint32_t len, const uint8_t *expected, + uint32_t *addr, uint32_t *failed); + uint32_t reserved1[12]; + uint32_t (*read)(struct mg_flash_config *, uint32_t start, uint8_t *dest, + uint32_t len); + uint32_t reserved2[4]; + uint32_t (*deinit)(struct mg_flash_config *); +}; +#define mg_flash_driver \ + ((struct mg_flash_driver_interface *) (*((uint32_t *) 0x1303fc00 + 4))) +#define MG_MCXN_FLASK_KEY (('k' << 24) | ('e' << 16) | ('f' << 8) | 'l') + +MG_IRAM static bool flash_sector_start(volatile uint32_t *dst) { + char *base = (char *) s_mg_flash_mcxn.start, + *end = base + s_mg_flash_mcxn.size; + volatile char *p = (char *) dst; + return p >= base && p < end && ((p - base) % s_mg_flash_mcxn.secsz) == 0; +} + +MG_IRAM static bool flash_erase(struct mg_flash_config *config, void *addr) { + if (flash_sector_start(addr) == false) { + MG_ERROR(("%p is not on a sector boundary", addr)); + return false; + } + uint32_t dst = + (uint32_t) addr - (uint32_t) s_mg_flash_mcxn.start; // future-proof + uint32_t status = mg_flash_driver->erase(config, dst, s_mg_flash_mcxn.secsz, + MG_MCXN_FLASK_KEY); + bool ok = (status == 0); + if (!ok) MG_ERROR(("Flash write error: %lu", status)); + MG_DEBUG(("Sector starting at %p erasure: %s", addr, ok ? "ok" : "fail")); + return ok; +} + +#if 0 +// read-while-write, no need to disable IRQs for standalone usage +MG_IRAM static bool mg_mcxn_erase(void *addr) { + uint32_t status; + struct mg_flash_config config; + if ((status = mg_flash_driver->init(&config)) != 0) { + MG_ERROR(("Flash driver init error: %lu", status)); + return false; + } + bool ok = flash_erase(&config, addr); + mg_flash_driver->deinit(&config); + return ok; +} +#endif + +MG_IRAM static bool mg_mcxn_swap(void) { + // TODO(): no devices so far + return true; +} + +static bool s_flash_irq_disabled; + +MG_IRAM static bool mg_mcxn_write(void *addr, const void *buf, size_t len) { + bool ok = false; + uint32_t status; + struct mg_flash_config config; + if ((status = mg_flash_driver->init(&config)) != 0) { + MG_ERROR(("Flash driver init error: %lu", status)); + return false; + } + if ((len % s_mg_flash_mcxn.align) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, s_mg_flash_mcxn.align)); + goto fwxit; + } + if ((((size_t) addr - (size_t) s_mg_flash_mcxn.start) % + s_mg_flash_mcxn.align) != 0) { + MG_ERROR(("%p is not on a page boundary", addr)); + goto fwxit; + } + + uint32_t *dst = (uint32_t *) addr; + uint32_t *src = (uint32_t *) buf; + uint32_t *end = (uint32_t *) ((char *) buf + len); + ok = true; + + MG_ARM_DISABLE_IRQ(); + while (ok && src < end) { + if (flash_sector_start(dst) && flash_erase(&config, dst) == false) { + ok = false; + break; + } + uint32_t dst_ofs = (uint32_t) dst - (uint32_t) s_mg_flash_mcxn.start; + // assume source is in RAM or in a different bank or read-while-write + status = mg_flash_driver->program(&config, dst_ofs, (uint8_t *) src, + s_mg_flash_mcxn.align); + src = (uint32_t *) ((char *) src + s_mg_flash_mcxn.align); + dst = (uint32_t *) ((char *) dst + s_mg_flash_mcxn.align); + if (status != 0) { + MG_ERROR(("Flash write error: %lu", status)); + ok = false; + } + } + if (!s_flash_irq_disabled) MG_ARM_ENABLE_IRQ(); + MG_DEBUG(("Flash write %lu bytes @ %p: %s.", len, dst, ok ? "ok" : "fail")); + +fwxit: + mg_flash_driver->deinit(&config); + return ok; +} + +// try to swap (honor dual image), otherwise just overwrite +MG_IRAM static void single_bank_swap(char *p1, char *p2, size_t s, size_t ss) { + char *tmp = malloc(ss); + // no stdlib calls here + for (size_t ofs = 0; ofs < s; ofs += ss) { + if (tmp != NULL) + for (size_t i = 0; i < ss; i++) tmp[i] = p1[ofs + i]; + mg_mcxn_write(p1 + ofs, p2 + ofs, ss); + if (tmp != NULL) mg_mcxn_write(p2 + ofs, tmp, ss); + } + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; +} + +bool mg_ota_begin(size_t new_firmware_size) { + uint32_t status; + struct mg_flash_config config; + if ((status = mg_flash_driver->init(&config)) != 0) { + MG_ERROR(("Flash driver init error: %lu", status)); + return false; + } + s_mg_flash_mcxn.start = (void *) config.addr; + s_mg_flash_mcxn.size = config.size; + s_mg_flash_mcxn.secsz = config.sector_size; + s_mg_flash_mcxn.align = config.page_size; + mg_flash_driver->deinit(&config); + MG_DEBUG( + ("%lu-byte flash @%p, using %lu-byte sectors with %lu-byte-aligned pages", + s_mg_flash_mcxn.size, s_mg_flash_mcxn.start, s_mg_flash_mcxn.secsz, + s_mg_flash_mcxn.align)); + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_mcxn); +} + +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_mcxn); +} + +bool mg_ota_end(void) { + if (mg_ota_flash_end(&s_mg_flash_mcxn)) { + if (0) { // is_dualbank() + // TODO(): no devices so far + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; + } else { + // Swap partitions. Pray power does not go away + MG_INFO(("Swapping partitions, size %u (%u sectors)", + s_mg_flash_mcxn.size, + s_mg_flash_mcxn.size / s_mg_flash_mcxn.secsz)); + MG_INFO(("Do NOT power off...")); + mg_log_level = MG_LL_NONE; + s_flash_irq_disabled = true; + // Runs in RAM, will reset when finished + single_bank_swap( + (char *) s_mg_flash_mcxn.start, + (char *) s_mg_flash_mcxn.start + s_mg_flash_mcxn.size / 2, + s_mg_flash_mcxn.size / 2, s_mg_flash_mcxn.secsz); + } + } + return false; +} +#endif diff --git a/src/device_stm32h5.c b/src/ota_stm32h5.c similarity index 82% rename from src/device_stm32h5.c rename to src/ota_stm32h5.c index 8da8fe45e0..61f68e276c 100644 --- a/src/device_stm32h5.c +++ b/src/ota_stm32h5.c @@ -1,9 +1,21 @@ -// #include "device.h" #include "flash.h" #include "log.h" +#include "ota.h" #if MG_OTA == MG_OTA_STM32H5 +static bool mg_stm32h5_write(void *, const void *, size_t); +static bool mg_stm32h5_swap(void); + +static struct mg_flash s_mg_flash_stm32h5 = { + (void *) 0x08000000, // Start + 2 * 1024 * 1024, // Size, 2Mb + 8 * 1024, // Sector size, 8k + 16, // Align, 128 bit + mg_stm32h5_write, + mg_stm32h5_swap, +}; + #define FLASH_BASE 0x40022000 // Base address of the flash controller #define FLASH_KEYR (FLASH_BASE + 0x4) // See RM0481 7.11 #define FLASH_OPTKEYR (FLASH_BASE + 0xc) @@ -14,24 +26,6 @@ #define FLASH_OPTSR_CUR (FLASH_BASE + 0x50) #define FLASH_OPTSR_PRG (FLASH_BASE + 0x54) -#if 0 -void *mg_flash_start(void) { - return (void *) 0x08000000; -} -size_t mg_flash_size(void) { - return 2 * 1024 * 1024; // 2Mb -} -size_t mg_flash_sector_size(void) { - return 8 * 1024; // 8k -} -size_t mg_flash_write_align(void) { - return 16; // 128 bit -} -int mg_flash_bank(void) { - return MG_REG(FLASH_OPTCR) & MG_BIT(31) ? 2 : 1; -} -#endif - static void flash_unlock(void) { static bool unlocked = false; if (unlocked == false) { @@ -44,9 +38,10 @@ static void flash_unlock(void) { } static int flash_page_start(volatile uint32_t *dst) { - char *base = (char *) mg_flash_start(), *end = base + mg_flash_size(); + char *base = (char *) s_mg_flash_stm32h5.start, + *end = base + s_mg_flash_stm32h5.size; volatile char *p = (char *) dst; - return p >= base && p < end && ((p - base) % mg_flash_sector_size()) == 0; + return p >= base && p < end && ((p - base) % s_mg_flash_stm32h5.secsz) == 0; } static bool flash_is_err(void) { @@ -74,8 +69,8 @@ static bool mg_stm32h5_erase(void *location) { if (flash_page_start(location) == false) { MG_ERROR(("%p is not on a sector boundary")); } else { - uintptr_t diff = (char *) location - (char *) mg_flash_start(); - uint32_t sector = diff / mg_flash_sector_size(); + uintptr_t diff = (char *) location - (char *) s_mg_flash_stm32h5.start; + uint32_t sector = diff / s_mg_flash_stm32h5.secsz; uint32_t saved_cr = MG_REG(FLASH_NSCR); // Save CR value flash_unlock(); flash_clear_err(); @@ -110,21 +105,23 @@ static bool mg_stm32h5_swap(void) { } static bool mg_stm32h5_write(void *addr, const void *buf, size_t len) { - if ((len % mg_flash_write_align()) != 0) { - MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align())); + if ((len % s_mg_flash_stm32h5.align) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, s_mg_flash_stm32h5.align)); return false; } uint32_t *dst = (uint32_t *) addr; uint32_t *src = (uint32_t *) buf; uint32_t *end = (uint32_t *) ((char *) buf + len); bool ok = true; + MG_ARM_DISABLE_IRQ(); flash_unlock(); flash_clear_err(); - MG_ARM_DISABLE_IRQ(); - // MG_DEBUG(("Starting flash write %lu bytes @ %p", len, addr)); MG_REG(FLASH_NSCR) = MG_BIT(1); // Set programming flag while (ok && src < end) { - if (flash_page_start(dst) && mg_stm32h5_erase(dst) == false) break; + if (flash_page_start(dst) && mg_stm32h5_erase(dst) == false) { + ok = false; + break; + } *(volatile uint32_t *) dst++ = *src++; flash_wait(); if (flash_is_err()) ok = false; @@ -137,14 +134,6 @@ static bool mg_stm32h5_write(void *addr, const void *buf, size_t len) { return ok; } -static struct mg_flash s_mg_flash_stm32h5 = { - (void *) 0x08000000, // Start - 2 * 1024 * 1024, // Size, 2Mb - 16, // Align, 128 bit - mg_stm32h5_write, - mg_stm32h5_swap, -}; - bool mg_ota_begin(size_t new_firmware_size) { return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_stm32h5); } @@ -153,7 +142,10 @@ bool mg_ota_write(const void *buf, size_t len) { return mg_ota_flash_write(buf, len, &s_mg_flash_stm32h5); } +// Actual bank swap is deferred until reset, it is safe to execute in flash bool mg_ota_end(void) { - return mg_ota_flash_end(&s_mg_flash_stm32h5); + if(!mg_ota_flash_end(&s_mg_flash_stm32h5)) return false; + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; + return true; } #endif diff --git a/src/device_stm32h7.c b/src/ota_stm32h7.c similarity index 53% rename from src/device_stm32h7.c rename to src/ota_stm32h7.c index e3f7c7773c..a698a9a6e0 100644 --- a/src/device_stm32h7.c +++ b/src/ota_stm32h7.c @@ -1,7 +1,27 @@ -#include "device.h" +#include "flash.h" #include "log.h" +#include "ota.h" -#if MG_DEVICE == MG_DEVICE_STM32H7 +#if MG_OTA == MG_OTA_STM32H7 + +// - H723/735 RM 4.3.3: Note: The application can simultaneously request a read +// and a write operation through the AXI interface. +// - We only need IRAM for partition swapping in the H723, however, all +// related functions must reside in IRAM for this to be possible. +// - Linker files for other devices won't define a .iram section so there's no +// associated penalty + +static bool mg_stm32h7_write(void *, const void *, size_t); +static bool mg_stm32h7_swap(void); + +static struct mg_flash s_mg_flash_stm32h7 = { + (void *) 0x08000000, // Start + 0, // Size, FLASH_SIZE_REG + 128 * 1024, // Sector size, 128k + 32, // Align, 256 bit + mg_stm32h7_write, + mg_stm32h7_swap, +}; #define FLASH_BASE1 0x52002000 // Base address for bank1 #define FLASH_BASE2 0x52002100 // Base address for bank2 @@ -15,21 +35,8 @@ #define FLASH_OPTSR_PRG 0x20 #define FLASH_SIZE_REG 0x1ff1e880 -MG_IRAM void *mg_flash_start(void) { - return (void *) 0x08000000; -} -MG_IRAM size_t mg_flash_size(void) { - return MG_REG(FLASH_SIZE_REG) * 1024; -} -MG_IRAM size_t mg_flash_sector_size(void) { - return 128 * 1024; // 128k -} -MG_IRAM size_t mg_flash_write_align(void) { - return 32; // 256 bit -} -MG_IRAM int mg_flash_bank(void) { - if (mg_flash_size() < 2 * 1024 * 1024) return 0; // No dual bank support - return MG_REG(FLASH_BASE1 + FLASH_OPTCR) & MG_BIT(31) ? 2 : 1; +MG_IRAM static bool is_dualbank(void) { + return (s_mg_flash_stm32h7.size < 2 * 1024 * 1024) ? false : true; } MG_IRAM static void flash_unlock(void) { @@ -37,7 +44,7 @@ MG_IRAM static void flash_unlock(void) { if (unlocked == false) { MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0x45670123; MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0xcdef89ab; - if (mg_flash_bank() > 0) { + if (is_dualbank()) { MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0x45670123; MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0xcdef89ab; } @@ -48,9 +55,10 @@ MG_IRAM static void flash_unlock(void) { } MG_IRAM static bool flash_page_start(volatile uint32_t *dst) { - char *base = (char *) mg_flash_start(), *end = base + mg_flash_size(); + char *base = (char *) s_mg_flash_stm32h7.start, + *end = base + s_mg_flash_stm32h7.size; volatile char *p = (char *) dst; - return p >= base && p < end && ((p - base) % mg_flash_sector_size()) == 0; + return p >= base && p < end && ((p - base) % s_mg_flash_stm32h7.secsz) == 0; } MG_IRAM static bool flash_is_err(uint32_t bank) { @@ -72,18 +80,19 @@ MG_IRAM static bool flash_bank_is_swapped(uint32_t bank) { // Figure out flash bank based on the address MG_IRAM static uint32_t flash_bank(void *addr) { - size_t ofs = (char *) addr - (char *) mg_flash_start(); - if (mg_flash_bank() == 0) return FLASH_BASE1; - return ofs < mg_flash_size() / 2 ? FLASH_BASE1 : FLASH_BASE2; + size_t ofs = (char *) addr - (char *) s_mg_flash_stm32h7.start; + if (!is_dualbank()) return FLASH_BASE1; + return ofs < s_mg_flash_stm32h7.size / 2 ? FLASH_BASE1 : FLASH_BASE2; } -MG_IRAM bool mg_flash_erase(void *addr) { +// read-while-write, no need to disable IRQs for standalone usage +MG_IRAM static bool mg_stm32h7_erase(void *addr) { bool ok = false; if (flash_page_start(addr) == false) { MG_ERROR(("%p is not on a sector boundary", addr)); } else { - uintptr_t diff = (char *) addr - (char *) mg_flash_start(); - uint32_t sector = diff / mg_flash_sector_size(); + uintptr_t diff = (char *) addr - (char *) s_mg_flash_stm32h7.start; + uint32_t sector = diff / s_mg_flash_stm32h7.secsz; uint32_t bank = flash_bank(addr); uint32_t saved_cr = MG_REG(bank + FLASH_CR); // Save CR value @@ -104,8 +113,8 @@ MG_IRAM bool mg_flash_erase(void *addr) { return ok; } -MG_IRAM bool mg_flash_swap_bank(void) { - if (mg_flash_bank() == 0) return true; +MG_IRAM static bool mg_stm32h7_swap(void) { + if (!is_dualbank()) return true; uint32_t bank = FLASH_BASE1; uint32_t desired = flash_bank_is_swapped(bank) ? 0 : MG_BIT(31); flash_unlock(); @@ -118,9 +127,11 @@ MG_IRAM bool mg_flash_swap_bank(void) { return true; } -MG_IRAM bool mg_flash_write(void *addr, const void *buf, size_t len) { - if ((len % mg_flash_write_align()) != 0) { - MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align())); +static bool s_flash_irq_disabled; + +MG_IRAM static bool mg_stm32h7_write(void *addr, const void *buf, size_t len) { + if ((len % s_mg_flash_stm32h7.align) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, s_mg_flash_stm32h7.align)); return false; } uint32_t bank = flash_bank(addr); @@ -128,19 +139,21 @@ MG_IRAM bool mg_flash_write(void *addr, const void *buf, size_t len) { uint32_t *src = (uint32_t *) buf; uint32_t *end = (uint32_t *) ((char *) buf + len); bool ok = true; + MG_ARM_DISABLE_IRQ(); flash_unlock(); flash_clear_err(bank); MG_REG(bank + FLASH_CR) = MG_BIT(1); // Set programming flag MG_REG(bank + FLASH_CR) |= MG_BIT(5); // 32-bit write parallelism - MG_DEBUG(("Writing flash @ %p, %lu bytes", addr, len)); - MG_ARM_DISABLE_IRQ(); while (ok && src < end) { - if (flash_page_start(dst) && mg_flash_erase(dst) == false) break; + if (flash_page_start(dst) && mg_stm32h7_erase(dst) == false) { + ok = false; + break; + } *(volatile uint32_t *) dst++ = *src++; flash_wait(bank); if (flash_is_err(bank)) ok = false; } - MG_ARM_ENABLE_IRQ(); + if (!s_flash_irq_disabled) MG_ARM_ENABLE_IRQ(); MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst, ok ? "ok" : "fail", MG_REG(bank + FLASH_CR), MG_REG(bank + FLASH_SR))); @@ -148,8 +161,44 @@ MG_IRAM bool mg_flash_write(void *addr, const void *buf, size_t len) { return ok; } -MG_IRAM void mg_device_reset(void) { - // SCB->AIRCR = ((0x5fa << SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk); +// just overwrite instead of swap +MG_IRAM static void single_bank_swap(char *p1, char *p2, size_t s, size_t ss) { + // no stdlib calls here + for (size_t ofs = 0; ofs < s; ofs += ss) { + mg_stm32h7_write(p1 + ofs, p2 + ofs, ss); + } *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; } + +bool mg_ota_begin(size_t new_firmware_size) { + s_mg_flash_stm32h7.size = MG_REG(FLASH_SIZE_REG) * 1024; + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_stm32h7); +} + +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_stm32h7); +} + +bool mg_ota_end(void) { + if (mg_ota_flash_end(&s_mg_flash_stm32h7)) { + if (is_dualbank()) { + // Bank swap is deferred until reset, been executing in flash, reset + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; + } else { + // Swap partitions. Pray power does not go away + MG_INFO(("Swapping partitions, size %u (%u sectors)", + s_mg_flash_stm32h7.size, + s_mg_flash_stm32h7.size / s_mg_flash_stm32h7.secsz)); + MG_INFO(("Do NOT power off...")); + mg_log_level = MG_LL_NONE; + s_flash_irq_disabled = true; + // Runs in RAM, will reset when finished + single_bank_swap( + (char *) s_mg_flash_stm32h7.start, + (char *) s_mg_flash_stm32h7.start + s_mg_flash_stm32h7.size / 2, + s_mg_flash_stm32h7.size / 2, s_mg_flash_stm32h7.secsz); + } + } + return false; +} #endif diff --git a/test/Makefile b/test/Makefile index 8d753908f0..78123a9f72 100644 --- a/test/Makefile +++ b/test/Makefile @@ -203,7 +203,7 @@ mongoose.c: Makefile $(wildcard ../src/*.c) $(wildcard ../src/drivers/*.c) cd .. && (export LC_ALL=C ; cat src/license.h; echo; echo '#include "mongoose.h"' ; (for F in src/*.c src/drivers/*.c ; do echo; echo '#ifdef MG_ENABLE_LINES'; echo "#line 1 \"$$F\""; echo '#endif'; cat $$F | sed -e 's,#include ".*,,'; done))> $@ mongoose.h: $(HDRS) Makefile - cd .. && (cat src/license.h; echo; echo '#ifndef MONGOOSE_H'; echo '#define MONGOOSE_H'; echo; cat src/version.h ; echo; echo '#ifdef __cplusplus'; echo 'extern "C" {'; echo '#endif'; cat src/arch.h src/arch_*.h src/net_ft.h src/net_lwip.h src/net_rl.h src/config.h src/str.h src/queue.h src/fmt.h src/printf.h src/log.h src/timer.h src/fs.h src/util.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/sha256.h src/tls_x25519.h src/tls_aes128.h src/tls_uecc.h src/tls_chacha20.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/tls_mbed.h src/tls_openssl.h src/ws.h src/sntp.h src/mqtt.h src/dns.h src/json.h src/rpc.h src/ota.h src/flash.h src/device.h src/net_builtin.h src/profile.h src/drivers/*.h | sed -e '/keep/! s,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@ + cd .. && (cat src/license.h; echo; echo '#ifndef MONGOOSE_H'; echo '#define MONGOOSE_H'; echo; cat src/version.h ; echo; echo '#ifdef __cplusplus'; echo 'extern "C" {'; echo '#endif'; cat src/arch.h src/arch_*.h src/net_ft.h src/net_lwip.h src/net_rl.h src/config.h src/str.h src/queue.h src/fmt.h src/printf.h src/log.h src/timer.h src/fs.h src/util.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/sha256.h src/tls_x25519.h src/tls_aes128.h src/tls_uecc.h src/tls_chacha20.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/tls_mbed.h src/tls_openssl.h src/ws.h src/sntp.h src/mqtt.h src/dns.h src/json.h src/rpc.h src/ota.h src/flash.h src/net_builtin.h src/profile.h src/drivers/*.h | sed -e '/keep/! s,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@ clean: clean_examples clean_refprojs clean_tutorials clean_examples_embedded