From 52144d12f00449d9507dadbfc79c902ccdc851e3 Mon Sep 17 00:00:00 2001 From: robert Date: Wed, 8 Apr 2026 05:27:46 -0400 Subject: [PATCH] Add OTA url check --- mongoose.c | 206 +++++++++++++++++++++++++++++++++++++++++++++++++++++ mongoose.h | 5 ++ src/ota.c | 202 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/ota.h | 5 ++ 4 files changed, 418 insertions(+) create mode 100644 src/ota.c diff --git a/mongoose.c b/mongoose.c index 8f396b41..8ba652d2 100644 --- a/mongoose.c +++ b/mongoose.c @@ -7784,6 +7784,212 @@ void mg_tcpip_mapip(struct mg_connection *c, struct mg_addr *ip) { #endif // MG_ENABLE_TCPIP +#ifdef MG_ENABLE_LINES +#line 1 "src/ota.c" +#endif + + + + +#if MG_OTA != MG_OTA_NONE + +enum { MG_OTA_STATUS_WAITING, MG_OTA_STATUS_SUCCESS, MG_OTA_STATUS_FAIL }; +static int s_version_status; +static int s_ota_status; +static uint64_t s_start_time; + +static struct mg_ota_metadata { + char *version; + char *url; + size_t size; + uint8_t sha256[32]; +} s_ota_metadata; + +static void free_ota_metadata(void) { + if (s_ota_metadata.version) mg_free(s_ota_metadata.version); + if (s_ota_metadata.url) mg_free(s_ota_metadata.url); + memset(&s_ota_metadata, 0, sizeof(s_ota_metadata)); +} + +static int fetch_ota_metadata(struct mg_http_message *response) { + double result; + if (mg_http_status(response) != 200) goto fetch_failed; + if (mg_json_get(response->body, "$", NULL) != 0) goto fetch_failed; + free_ota_metadata(); + s_ota_metadata.version = mg_json_get_str(response->body, "$.version"); + if (s_ota_metadata.version == NULL) goto fetch_failed; + s_ota_metadata.url = mg_json_get_str(response->body, "$.url"); + if (s_ota_metadata.url == NULL) goto fetch_failed; + if (!mg_json_get_num(response->body, "$.size", &result) || result <= 0) + goto fetch_failed; + s_ota_metadata.size = (size_t) result; + // TODO (robertc2000): parse and validate sha256 + MG_DEBUG(("Firmware version: %s, url: %s, size: %ld", s_ota_metadata.version, + s_ota_metadata.url, s_ota_metadata.size)); + return MG_OTA_STATUS_SUCCESS; +fetch_failed: + free_ota_metadata(); + return MG_OTA_STATUS_FAIL; +} + +static void s_version_fn(struct mg_connection *c, int ev, void *ev_data) { + struct mg_str host = mg_url_host((char *) c->fn_data); + struct mg_http_message *hm; + static int fetch_status; + + if (ev == MG_EV_POLL) { + if (s_start_time + 5 * 1000 < mg_millis()) { + mg_error(c, "Connection timeout"); + } + } else if (ev == MG_EV_CONNECT) { + mg_printf(c, + "GET %s HTTP/1.1\r\n" + "Host: %.*s\r\n" + "Connection: close\r\n\r\n", + mg_url_uri((char *) c->fn_data), (int) host.len, host.buf); + fetch_status = MG_OTA_STATUS_WAITING; + } else if (ev == MG_EV_HTTP_MSG) { + hm = (struct mg_http_message *) ev_data; + fetch_status = fetch_ota_metadata(hm); + } else if (ev == MG_EV_ERROR) { + MG_ERROR(("%lu Connection error", c->id)); + s_version_status = MG_OTA_STATUS_FAIL; + } else if (ev == MG_EV_CLOSE) { + MG_DEBUG(("%lu Connection closed %lu", c->id, mg_millis() - s_start_time)); + s_version_status = fetch_status; + } +} + +static void s_firmware_fn(struct mg_connection *c, int ev, void *ev_data) { + struct mg_str host = mg_url_host(s_ota_metadata.url); + struct mg_http_message hm; + static size_t ofs = 0; + static bool ota_begun = false; + size_t alignment = 512, drop, len; + int n, status; + char *buf; + (void) ev_data; + + if (ev == MG_EV_POLL) { + if (s_start_time + 120 * 1000 < mg_millis()) { + mg_error(c, "Connection timeout"); + } + } else if (ev == MG_EV_CONNECT) { + mg_printf(c, + "GET %s HTTP/1.1\r\n" + "Host: %.*s\r\n" + "Connection: close\r\n\r\n", + mg_url_uri(s_ota_metadata.url), (int) host.len, host.buf); + ofs = 0; + ota_begun = false; + } else if (ev == MG_EV_READ) { + if (s_ota_status == MG_OTA_STATUS_FAIL) return; + if (!ota_begun) { // First chunk, parse headers, OTA begin + n = mg_http_parse((char *) c->recv.buf, c->recv.len, &hm); + if (n < 0) { + mg_error(c, "Bad HTTP response"); + return; + } + if (n == 0) return; + status = mg_http_status(&hm); + if (status != 200 || hm.body.len != s_ota_metadata.size) { + mg_error(c, "Bad HTTP response (%d), body length: %lu", status, + hm.body.len); + return; + } + MG_DEBUG(("Beginning OTA (%ld bytes)", s_ota_metadata.size)); + if (!mg_ota_begin(s_ota_metadata.size)) { + s_ota_status = MG_OTA_STATUS_FAIL; + mg_error(c, "mg_ota_begin(%lu) failed", + (unsigned long) s_ota_metadata.size); + return; + } + ota_begun = true; + len = c->recv.len - (size_t) n; + buf = (char *) c->recv.buf + n; + drop = (size_t) n; + } else { // Header parsed, incoming chunks + len = c->recv.len; + buf = (char *) c->recv.buf; + drop = 0; + } + // OTA write + size_t aligned = (ofs + len < s_ota_metadata.size) + ? aligned = MG_ROUND_DOWN(len, alignment) + : len; + if (aligned == 0) return; + if (!mg_ota_write(buf, aligned)) { + mg_error(c, "mg_ota_write(%lu) @%lu failed", (unsigned long) aligned, + (unsigned long) ofs); + return; + } + ofs += aligned; + mg_iobuf_del(&c->recv, 0, aligned + drop); + MG_DEBUG(("Wrote %lu/%lu bytes", (unsigned long) ofs, + (unsigned long) s_ota_metadata.size)); + } else if (ev == MG_EV_ERROR) { + MG_ERROR(("%lu Connection error", c->id)); + s_ota_status = MG_OTA_STATUS_FAIL; + } else if (ev == MG_EV_CLOSE) { // OTA end + MG_DEBUG(("Connection closing, downloaded %ld/%ld bytes", ofs, + s_ota_metadata.size)); + if (s_ota_status != MG_OTA_STATUS_FAIL && ota_begun) { + if (ofs != s_ota_metadata.size) { + s_ota_status = MG_OTA_STATUS_FAIL; + MG_ERROR(("Firmware size mismatch: got %lu expected %lu", + (unsigned long) ofs, (unsigned long) s_ota_metadata.size)); + } else if (!mg_ota_end()) { + s_ota_status = MG_OTA_STATUS_FAIL; + MG_ERROR(("mg_ota_end() failed")); + } else { + MG_DEBUG(("mg_ota_end() successful")); + s_ota_status = MG_OTA_STATUS_SUCCESS; + } + } + ota_begun = false; + ofs = 0; + } +} + +void mg_ota_url_check(struct mg_mgr *mgr, const char *current_version, + const char *metadata_url, + void (*fn)(const char *status)) { + s_version_status = MG_OTA_STATUS_WAITING; + s_ota_status = MG_OTA_STATUS_WAITING; + s_start_time = mg_millis(); + MG_DEBUG(("Connecting to %s to retrieve metadata", metadata_url)); + if (!mg_http_connect(mgr, metadata_url, s_version_fn, + (void *) metadata_url)) { + if (fn) fn("Failed to connect"); + return; + } + while (s_version_status == MG_OTA_STATUS_WAITING) mg_mgr_poll(mgr, 10); + if (s_version_status == MG_OTA_STATUS_SUCCESS) { + if (strcmp(s_ota_metadata.version, current_version) == 0) { + if (fn) fn("Same version"); + free_ota_metadata(); + return; + } + } else { + if (fn) fn("Version retrieving error"); + free_ota_metadata(); + return; + } + if (fn) fn("Pulling firmware"); + s_start_time = mg_millis(); + MG_DEBUG(("Connecting to %s to download firmware", s_ota_metadata.url)); + if (!mg_connect(mgr, s_ota_metadata.url, s_firmware_fn, NULL)) { + if (fn) fn("Failed to connect"); + free_ota_metadata(); + return; + } + while (s_ota_status == MG_OTA_STATUS_WAITING) mg_mgr_poll(mgr, 10); + if (s_ota_status == MG_OTA_STATUS_FAIL && fn) fn("OTA fail"); + free_ota_metadata(); +} + +#endif + #ifdef MG_ENABLE_LINES #line 1 "src/ota_ch32v307.c" #endif diff --git a/mongoose.h b/mongoose.h index 872556b9..0f0074d4 100644 --- a/mongoose.h +++ b/mongoose.h @@ -3105,6 +3105,7 @@ void mg_rpc_list(struct mg_rpc_req *r); + #define MG_OTA_NONE 0 // No OTA support #define MG_OTA_STM32H5 1 // STM32 H5 #define MG_OTA_STM32H7 2 // STM32 H7 @@ -3140,6 +3141,10 @@ void mg_rpc_list(struct mg_rpc_req *r); 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 +void mg_ota_url_check(struct mg_mgr *mgr, + const char *current_version, + const char *metadata_url, + void (*fn)(const char *status)); diff --git a/src/ota.c b/src/ota.c new file mode 100644 index 00000000..979eb53e --- /dev/null +++ b/src/ota.c @@ -0,0 +1,202 @@ +#include "ota.h" +#include "http.h" +#include "log.h" + +#if MG_OTA != MG_OTA_NONE + +enum { MG_OTA_STATUS_WAITING, MG_OTA_STATUS_SUCCESS, MG_OTA_STATUS_FAIL }; +static int s_version_status; +static int s_ota_status; +static uint64_t s_start_time; + +static struct mg_ota_metadata { + char *version; + char *url; + size_t size; + uint8_t sha256[32]; +} s_ota_metadata; + +static void free_ota_metadata(void) { + if (s_ota_metadata.version) mg_free(s_ota_metadata.version); + if (s_ota_metadata.url) mg_free(s_ota_metadata.url); + memset(&s_ota_metadata, 0, sizeof(s_ota_metadata)); +} + +static int fetch_ota_metadata(struct mg_http_message *response) { + double result; + if (mg_http_status(response) != 200) goto fetch_failed; + if (mg_json_get(response->body, "$", NULL) != 0) goto fetch_failed; + free_ota_metadata(); + s_ota_metadata.version = mg_json_get_str(response->body, "$.version"); + if (s_ota_metadata.version == NULL) goto fetch_failed; + s_ota_metadata.url = mg_json_get_str(response->body, "$.url"); + if (s_ota_metadata.url == NULL) goto fetch_failed; + if (!mg_json_get_num(response->body, "$.size", &result) || result <= 0) + goto fetch_failed; + s_ota_metadata.size = (size_t) result; + // TODO (robertc2000): parse and validate sha256 + MG_DEBUG(("Firmware version: %s, url: %s, size: %ld", s_ota_metadata.version, + s_ota_metadata.url, s_ota_metadata.size)); + return MG_OTA_STATUS_SUCCESS; +fetch_failed: + free_ota_metadata(); + return MG_OTA_STATUS_FAIL; +} + +static void s_version_fn(struct mg_connection *c, int ev, void *ev_data) { + struct mg_str host = mg_url_host((char *) c->fn_data); + struct mg_http_message *hm; + static int fetch_status; + + if (ev == MG_EV_POLL) { + if (s_start_time + 5 * 1000 < mg_millis()) { + mg_error(c, "Connection timeout"); + } + } else if (ev == MG_EV_CONNECT) { + mg_printf(c, + "GET %s HTTP/1.1\r\n" + "Host: %.*s\r\n" + "Connection: close\r\n\r\n", + mg_url_uri((char *) c->fn_data), (int) host.len, host.buf); + fetch_status = MG_OTA_STATUS_WAITING; + } else if (ev == MG_EV_HTTP_MSG) { + hm = (struct mg_http_message *) ev_data; + fetch_status = fetch_ota_metadata(hm); + } else if (ev == MG_EV_ERROR) { + MG_ERROR(("%lu Connection error", c->id)); + s_version_status = MG_OTA_STATUS_FAIL; + } else if (ev == MG_EV_CLOSE) { + MG_DEBUG(("%lu Connection closed %lu", c->id, mg_millis() - s_start_time)); + s_version_status = fetch_status; + } +} + +static void s_firmware_fn(struct mg_connection *c, int ev, void *ev_data) { + struct mg_str host = mg_url_host(s_ota_metadata.url); + struct mg_http_message hm; + static size_t ofs = 0; + static bool ota_begun = false; + size_t alignment = 512, drop, len; + int n, status; + char *buf; + (void) ev_data; + + if (ev == MG_EV_POLL) { + if (s_start_time + 120 * 1000 < mg_millis()) { + mg_error(c, "Connection timeout"); + } + } else if (ev == MG_EV_CONNECT) { + mg_printf(c, + "GET %s HTTP/1.1\r\n" + "Host: %.*s\r\n" + "Connection: close\r\n\r\n", + mg_url_uri(s_ota_metadata.url), (int) host.len, host.buf); + ofs = 0; + ota_begun = false; + } else if (ev == MG_EV_READ) { + if (s_ota_status == MG_OTA_STATUS_FAIL) return; + if (!ota_begun) { // First chunk, parse headers, OTA begin + n = mg_http_parse((char *) c->recv.buf, c->recv.len, &hm); + if (n < 0) { + mg_error(c, "Bad HTTP response"); + return; + } + if (n == 0) return; + status = mg_http_status(&hm); + if (status != 200 || hm.body.len != s_ota_metadata.size) { + mg_error(c, "Bad HTTP response (%d), body length: %lu", status, + hm.body.len); + return; + } + MG_DEBUG(("Beginning OTA (%ld bytes)", s_ota_metadata.size)); + if (!mg_ota_begin(s_ota_metadata.size)) { + s_ota_status = MG_OTA_STATUS_FAIL; + mg_error(c, "mg_ota_begin(%lu) failed", + (unsigned long) s_ota_metadata.size); + return; + } + ota_begun = true; + len = c->recv.len - (size_t) n; + buf = (char *) c->recv.buf + n; + drop = (size_t) n; + } else { // Header parsed, incoming chunks + len = c->recv.len; + buf = (char *) c->recv.buf; + drop = 0; + } + // OTA write + size_t aligned = (ofs + len < s_ota_metadata.size) + ? aligned = MG_ROUND_DOWN(len, alignment) + : len; + if (aligned == 0) return; + if (!mg_ota_write(buf, aligned)) { + mg_error(c, "mg_ota_write(%lu) @%lu failed", (unsigned long) aligned, + (unsigned long) ofs); + return; + } + ofs += aligned; + mg_iobuf_del(&c->recv, 0, aligned + drop); + MG_DEBUG(("Wrote %lu/%lu bytes", (unsigned long) ofs, + (unsigned long) s_ota_metadata.size)); + } else if (ev == MG_EV_ERROR) { + MG_ERROR(("%lu Connection error", c->id)); + s_ota_status = MG_OTA_STATUS_FAIL; + } else if (ev == MG_EV_CLOSE) { // OTA end + MG_DEBUG(("Connection closing, downloaded %ld/%ld bytes", ofs, + s_ota_metadata.size)); + if (s_ota_status != MG_OTA_STATUS_FAIL && ota_begun) { + if (ofs != s_ota_metadata.size) { + s_ota_status = MG_OTA_STATUS_FAIL; + MG_ERROR(("Firmware size mismatch: got %lu expected %lu", + (unsigned long) ofs, (unsigned long) s_ota_metadata.size)); + } else if (!mg_ota_end()) { + s_ota_status = MG_OTA_STATUS_FAIL; + MG_ERROR(("mg_ota_end() failed")); + } else { + MG_DEBUG(("mg_ota_end() successful")); + s_ota_status = MG_OTA_STATUS_SUCCESS; + } + } + ota_begun = false; + ofs = 0; + } +} + +void mg_ota_url_check(struct mg_mgr *mgr, const char *current_version, + const char *metadata_url, + void (*fn)(const char *status)) { + s_version_status = MG_OTA_STATUS_WAITING; + s_ota_status = MG_OTA_STATUS_WAITING; + s_start_time = mg_millis(); + MG_DEBUG(("Connecting to %s to retrieve metadata", metadata_url)); + if (!mg_http_connect(mgr, metadata_url, s_version_fn, + (void *) metadata_url)) { + if (fn) fn("Failed to connect"); + return; + } + while (s_version_status == MG_OTA_STATUS_WAITING) mg_mgr_poll(mgr, 10); + if (s_version_status == MG_OTA_STATUS_SUCCESS) { + if (strcmp(s_ota_metadata.version, current_version) == 0) { + if (fn) fn("Same version"); + free_ota_metadata(); + return; + } + } else { + if (fn) fn("Version retrieving error"); + free_ota_metadata(); + return; + } + if (fn) fn("Pulling firmware"); + s_start_time = mg_millis(); + MG_DEBUG(("Connecting to %s to download firmware", s_ota_metadata.url)); + if (!mg_connect(mgr, s_ota_metadata.url, s_firmware_fn, NULL)) { + if (fn) fn("Failed to connect"); + free_ota_metadata(); + return; + } + while (s_ota_status == MG_OTA_STATUS_WAITING) mg_mgr_poll(mgr, 10); + if (s_ota_status == MG_OTA_STATUS_FAIL && fn) fn("OTA fail"); + free_ota_metadata(); +} + +#endif diff --git a/src/ota.h b/src/ota.h index 81417fb6..6344f3c9 100644 --- a/src/ota.h +++ b/src/ota.h @@ -4,6 +4,7 @@ #pragma once #include "arch.h" +#include "net.h" #define MG_OTA_NONE 0 // No OTA support #define MG_OTA_STM32H5 1 // STM32 H5 @@ -40,3 +41,7 @@ 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 +void mg_ota_url_check(struct mg_mgr *mgr, + const char *current_version, + const char *metadata_url, + void (*fn)(const char *status));