From daf9d8965a97b0acec330c6955db4edc42f2a628 Mon Sep 17 00:00:00 2001 From: bloqaudio Date: Sun, 30 Aug 2026 14:04:03 -0500 Subject: [PATCH 1/3] httpd: scope the multipart parse cursors to their functions cfg_pos, cfg_hdr, cfg_body, cfg_end, cfg_last and cfg_bl are used only by config_take(), so move them out of file scope and into the function. The five cursors stay static: SDCC register-caches automatic xdata locals and spills them through DSEG slots, which costs 6 bytes of direct RAM this build has no room for; static keeps the access pattern of the old globals and the memory layout is unchanged. cfg_bl produces no spill slot at the current register pressure, so it is a plain local. The mechanism is recorded in issue #386. handle_post() reused cfg_pos and cfg_end as scratch for unrelated values; those uses get their own named locals, frag_len and payload_start. --- httpd/httpd.c | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/httpd/httpd.c b/httpd/httpd.c index 23fef5d..168ee20 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -48,10 +48,8 @@ __xdata uint8_t boundary[72]; #define CONFIG_UPLOAD_BUF (CONFIG_LEN + 384) __xdata uint8_t config_upload; __xdata uint8_t config_buf[CONFIG_UPLOAD_BUF]; -__xdata uint16_t cfg_pos, cfg_hdr, cfg_body, cfg_end, cfg_last; // part-header bytes buffered so far; accumulates across TCP segments __xdata uint16_t pre_acc; -__xdata uint8_t cfg_bl; __xdata uint8_t * __xdata content_type = 0; __xdata uint8_t * __xdata session = 0; @@ -325,6 +323,10 @@ void gen_random_hex_chars(__xdata uint8_t * b, __xdata uint8_t bytes) /* 0: body incomplete, 1: configuration stored, 2: malformed */ static uint8_t config_take(void) { + // #386: needs static, otherwise it still lands in SRAM/DSEG + static __xdata uint16_t cfg_pos, cfg_hdr, cfg_body, cfg_end, cfg_last; + __xdata uint8_t cfg_bl; + cfg_bl = strlen_x(boundary); // the body is complete once the closing boundary has arrived @@ -505,6 +507,7 @@ void handle_post(void) __xdata struct httpd_state * __xdata s = &(uip_conn->appstate); __xdata uint8_t *p = uip_appdata; __xdata uint8_t *request_path = p + 6; + __xdata uint16_t frag_len, payload_start; // Was the multipart header sent in multiple packets? if (s->tstate != TSTATE_MULTIPART) { @@ -600,16 +603,16 @@ void handle_post(void) return; } if (config_upload) { - cfg_pos = uip_len - (p - uip_appdata); - if (write_len + cfg_pos >= CONFIG_UPLOAD_BUF) { + frag_len = uip_len - (p - uip_appdata); + if (write_len + frag_len >= CONFIG_UPLOAD_BUF) { print_string("Configuration too large, aborting.\n"); config_upload = 0; s->tstate = TSTATE_NONE; send_bad_request(); return; } - memcpy(config_buf + write_len, p, cfg_pos); - write_len += cfg_pos; + memcpy(config_buf + write_len, p, frag_len); + write_len += frag_len; uint8_t taken = config_take(); if (!taken) { @@ -625,17 +628,17 @@ void handle_post(void) slen = strtox(outbuf, "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n"); return; } - cfg_pos = uip_len - (p - uip_appdata); - if (pre_acc + cfg_pos >= CONFIG_UPLOAD_BUF) { + frag_len = uip_len - (p - uip_appdata); + if (pre_acc + frag_len >= CONFIG_UPLOAD_BUF) { print_string("Firmware upload header too large, aborting.\n"); s->tstate = TSTATE_NONE; send_bad_request(); return; } - memcpy(config_buf + pre_acc, p, cfg_pos); - pre_acc += cfg_pos; - cfg_end = preamble_payload_start(pre_acc); - if (!cfg_end) { + memcpy(config_buf + pre_acc, p, frag_len); + pre_acc += frag_len; + payload_start = preamble_payload_start(pre_acc); + if (!payload_start) { s->tstate = TSTATE_MULTIPART; return; } @@ -652,7 +655,7 @@ void handle_post(void) // appcall POST branch cannot send leftovers slen = 0; upload_settings.p = config_buf; - upload_settings.bptr = cfg_end; + upload_settings.bptr = payload_start; upload_settings.plen = pre_acc; stream_upload(); From f0f142fbe0846f0afa3326bc64d0ba54da795b6d Mon Sep 17 00:00:00 2001 From: bloqaudio Date: Sun, 30 Aug 2026 04:16:27 -0500 Subject: [PATCH 2/3] httpd: track buffered upload bytes in one accumulator --- httpd/httpd.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/httpd/httpd.c b/httpd/httpd.c index 168ee20..5ebe6e3 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -48,7 +48,8 @@ __xdata uint8_t boundary[72]; #define CONFIG_UPLOAD_BUF (CONFIG_LEN + 384) __xdata uint8_t config_upload; __xdata uint8_t config_buf[CONFIG_UPLOAD_BUF]; -// part-header bytes buffered so far; accumulates across TCP segments +// bytes buffered in config_buf so far (config body, or a firmware part +// header); accumulates across TCP segments __xdata uint16_t pre_acc; __xdata uint8_t * __xdata content_type = 0; __xdata uint8_t * __xdata session = 0; @@ -332,7 +333,7 @@ static uint8_t config_take(void) // the body is complete once the closing boundary has arrived cfg_last = 0; while (1) { - if (cfg_last + cfg_bl + 1 >= write_len) + if (cfg_last + cfg_bl + 1 >= pre_acc) return 0; if (strstart_x(&config_buf[cfg_last], boundary) && strstart(&config_buf[cfg_last + cfg_bl], "--")) @@ -548,7 +549,7 @@ void handle_post(void) dbg_string("Configuration upload\n"); verify_crc = 0; config_upload = 1; - write_len = 0; + pre_acc = 0; } // Check for other POST requests, which are not multipart, below } else { @@ -604,15 +605,15 @@ void handle_post(void) } if (config_upload) { frag_len = uip_len - (p - uip_appdata); - if (write_len + frag_len >= CONFIG_UPLOAD_BUF) { + if (pre_acc + frag_len >= CONFIG_UPLOAD_BUF) { print_string("Configuration too large, aborting.\n"); config_upload = 0; s->tstate = TSTATE_NONE; send_bad_request(); return; } - memcpy(config_buf + write_len, p, frag_len); - write_len += frag_len; + memcpy(config_buf + pre_acc, p, frag_len); + pre_acc += frag_len; uint8_t taken = config_take(); if (!taken) { From 005209f97aec1aec3e41199c31fc3e04a5d56a5e Mon Sep 17 00:00:00 2001 From: bloqaudio Date: Sun, 30 Aug 2026 14:04:17 -0500 Subject: [PATCH 3/3] httpd: split the upload fragment handlers out of handle_post() The tail of handle_post() interleaved the config and firmware upload paths behind one config_upload conditional. Give each path its own function, handle_config_fragment() and handle_firmware_fragment(), and reduce handle_post() to routing. Both handlers keep their own copy of the buffer-and-bounds-check prologue: with SDCC a shared helper costs 2 bytes of direct RAM for the fragment pointer (a spill slot when the parameter lives in xdata, a DSEG home when it does not), and direct RAM is fully allocated. The memory layout is unchanged from the pre-split code. --- httpd/httpd.c | 136 ++++++++++++++++++++++++++++---------------------- 1 file changed, 76 insertions(+), 60 deletions(-) diff --git a/httpd/httpd.c b/httpd/httpd.c index 5ebe6e3..d871226 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -503,12 +503,83 @@ uint8_t stream_upload(void) } +static void handle_config_fragment(__xdata uint8_t *p) +{ + __xdata struct httpd_state * __xdata s = &(uip_conn->appstate); + __xdata uint16_t frag_len; + uint8_t taken; + + frag_len = uip_len - (p - uip_appdata); + if (pre_acc + frag_len >= CONFIG_UPLOAD_BUF) { + print_string("Configuration too large, aborting.\n"); + config_upload = 0; + s->tstate = TSTATE_NONE; + send_bad_request(); + return; + } + memcpy(config_buf + pre_acc, p, frag_len); + pre_acc += frag_len; + taken = config_take(); + if (!taken) { + s->tstate = TSTATE_MULTIPART; + return; + } + config_upload = 0; + s->tstate = TSTATE_NONE; + if (taken == 2) { + send_bad_request(); + return; + } + slen = strtox(outbuf, "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n"); +} + + +static void handle_firmware_fragment(__xdata uint8_t *p) +{ + __xdata struct httpd_state * __xdata s = &(uip_conn->appstate); + __xdata uint16_t frag_len, payload_start; + + frag_len = uip_len - (p - uip_appdata); + if (pre_acc + frag_len >= CONFIG_UPLOAD_BUF) { + print_string("Firmware upload header too large, aborting.\n"); + config_upload = 0; + s->tstate = TSTATE_NONE; + send_bad_request(); + return; + } + memcpy(config_buf + pre_acc, p, frag_len); + pre_acc += frag_len; + payload_start = preamble_payload_start(pre_acc); + if (!payload_start) { + s->tstate = TSTATE_MULTIPART; + return; + } + dbg_string("Have content octets\n"); + + flash_init(0); // Re-initialize flash for non-DIO operation, otherwise flashing fails + set_sys_led_state(SYS_LED_FAST); + + crc_value = 0; + bindex = 0; + write_len = 0; + // A verdict is only built once the upload part completes; + // clear any stale response so the completion check in the + // appcall POST branch cannot send leftovers + slen = 0; + upload_settings.p = config_buf; + upload_settings.bptr = payload_start; + upload_settings.plen = pre_acc; + stream_upload(); + + dbg_string("Done reading first fragment\n"); +} + + void handle_post(void) { __xdata struct httpd_state * __xdata s = &(uip_conn->appstate); __xdata uint8_t *p = uip_appdata; __xdata uint8_t *request_path = p + 6; - __xdata uint16_t frag_len, payload_start; // Was the multipart header sent in multiple packets? if (s->tstate != TSTATE_MULTIPART) { @@ -603,66 +674,11 @@ void handle_post(void) send_bad_request(); return; } - if (config_upload) { - frag_len = uip_len - (p - uip_appdata); - if (pre_acc + frag_len >= CONFIG_UPLOAD_BUF) { - print_string("Configuration too large, aborting.\n"); - config_upload = 0; - s->tstate = TSTATE_NONE; - send_bad_request(); - return; - } - memcpy(config_buf + pre_acc, p, frag_len); - pre_acc += frag_len; - uint8_t taken = config_take(); - - if (!taken) { - s->tstate = TSTATE_MULTIPART; - return; - } - config_upload = 0; - s->tstate = TSTATE_NONE; - if (taken == 2) { - send_bad_request(); - return; - } - slen = strtox(outbuf, "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n"); - return; - } - frag_len = uip_len - (p - uip_appdata); - if (pre_acc + frag_len >= CONFIG_UPLOAD_BUF) { - print_string("Firmware upload header too large, aborting.\n"); - s->tstate = TSTATE_NONE; - send_bad_request(); - return; - } - memcpy(config_buf + pre_acc, p, frag_len); - pre_acc += frag_len; - payload_start = preamble_payload_start(pre_acc); - if (!payload_start) { - s->tstate = TSTATE_MULTIPART; - return; - } - dbg_string("Have content octets\n"); - - flash_init(0); // Re-initialize flash for non-DIO operation, otherwise flashing fails - set_sys_led_state(SYS_LED_FAST); - - crc_value = 0; - bindex = 0; - write_len = 0; - // A verdict is only built once the upload part completes; - // clear any stale response so the completion check in the - // appcall POST branch cannot send leftovers - slen = 0; - upload_settings.p = config_buf; - upload_settings.bptr = payload_start; - upload_settings.plen = pre_acc; - stream_upload(); - - dbg_string("Done reading first fragment\n"); + if (config_upload) + handle_config_fragment(p); + else + handle_firmware_fragment(p); return; - } else { send_not_found(); return;