From dcd3cd5a986831fb3214ee6609eada351e91e600 Mon Sep 17 00:00:00 2001 From: bloqaudio Date: Tue, 11 Aug 2026 10:27:11 -0500 Subject: [PATCH 01/10] test: add host-side unit-test harness, covering cmd_editor Compile selected firmware translation units unmodified under host gcc with AddressSanitizer and UBSan, force-including a shim header that erases the SDCC 8051 keywords, and link them against a mocked serial ring and console. A first suite covers the CLI line editor: line entry, backspace editing, and both long-input overflow cases (length cap and ring-pointer stall). The only firmware-source change is one #ifndef RTLP_HOST_TEST block in rtl837x_common.h grouping the libc-named prototypes (sleep/memcpy/memset/ strlen/strcpy/strcmp) so host builds take them from the C library instead; on-target builds are unaffected. Run with: make -C test --- rtl837x_common.h | 14 ++++-- test/Makefile | 45 ++++++++++++++++++ test/README.md | 49 ++++++++++++++++++++ test/sdcc_shim.h | 40 ++++++++++++++++ test/support.c | 103 +++++++++++++++++++++++++++++++++++++++++ test/support.h | 53 +++++++++++++++++++++ test/test_cmd_editor.c | 76 ++++++++++++++++++++++++++++++ 7 files changed, 376 insertions(+), 4 deletions(-) create mode 100644 test/Makefile create mode 100644 test/README.md create mode 100644 test/sdcc_shim.h create mode 100644 test/support.c create mode 100644 test/support.h create mode 100644 test/test_cmd_editor.c diff --git a/rtl837x_common.h b/rtl837x_common.h index ee1c61bb..0ea0e74b 100644 --- a/rtl837x_common.h +++ b/rtl837x_common.h @@ -158,7 +158,6 @@ void reg_write_m(uint16_t reg_addr); void sds_read(uint8_t sds_id, uint8_t page, uint8_t reg); void sds_write_v(uint8_t sds_id, uint8_t page, uint8_t reg, uint16_t v); void delay(uint16_t t); -void sleep(uint16_t t); void write_char_no_syslog(char c); void write_char(char c); void print_reg(uint16_t reg); @@ -170,15 +169,22 @@ uint8_t reg_bit_test(uint16_t reg_addr, char bit); void sfr_mask_data(uint8_t n, uint8_t mask, uint8_t set); void sfr_set_zero(void); void reset_chip(void); +/* Firmware implementations that shadow libc names. Host unit-test builds + * (RTLP_HOST_TEST) hide these prototypes so they don't clash with glibc; + * argument order matches libc, so on-host callers transparently use the + * C library. See test/. */ +#ifndef RTLP_HOST_TEST +void sleep(uint16_t t); void memcpy(__xdata void * __xdata dst, __xdata const void * __xdata src, uint16_t len); -void memcpyc(__xdata uint8_t *dst, __code uint8_t *src, uint16_t len); void memset(__xdata uint8_t *dst, __xdata uint8_t v, uint8_t len); int memcmp(__xdata const void *a, __xdata const void *b, uint16_t len); uint16_t strlen(__code const char *s); -uint16_t strlen_x(__xdata const char *s); -uint16_t strtox(__xdata uint8_t *dst, __code const char *s); uint16_t strcpy(__xdata uint8_t *dst, const char *s); char strcmp(__xdata const uint8_t *a, __code const uint8_t *b); +#endif +void memcpyc(__xdata uint8_t *dst, __code uint8_t *src, uint16_t len); +uint16_t strlen_x(__xdata const char *s); +uint16_t strtox(__xdata uint8_t *dst, __code const char *s); bool strstart(__xdata const uint8_t *a, __code const uint8_t *b); bool strstart_x(__xdata const uint8_t *a, __xdata const uint8_t *b); void tcpip_output(void); diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 00000000..66702b70 --- /dev/null +++ b/test/Makefile @@ -0,0 +1,45 @@ +# Host unit-test harness for RTLPlayground firmware logic. +# +# Compiles selected firmware translation units with host gcc + AddressSanitizer, +# force-including sdcc_shim.h to erase 8051 keywords, and links them against +# mocked hardware edges (support.c). No SDCC, no flashing, no hardware. +# +# make build + run all tests +# make run run tests +# make clean + +CC = gcc +SRCROOT = .. +# -include shim first so __xdata/__code/__banked vanish before any firmware header. +CFLAGS = -std=gnu11 -g -O1 -Wall -Wextra -Wno-unused-parameter -Wno-unknown-pragmas \ + -Wno-builtin-declaration-mismatch -DRTLP_HOST_TEST \ + -fsanitize=address,undefined -fno-omit-frame-pointer \ + -include sdcc_shim.h -I. -I$(SRCROOT) -I$(SRCROOT)/httpd -I$(SRCROOT)/uip +LDFLAGS = -fsanitize=address,undefined + +BUILDDIR = build + +# One binary per subsystem under test. +TESTS = $(BUILDDIR)/test_cmd_editor + +all: run + +$(BUILDDIR): + mkdir -p $(BUILDDIR) + +# cmd_editor.c is compiled UNMODIFIED from the firmware tree. +$(BUILDDIR)/test_cmd_editor: test_cmd_editor.c support.c $(SRCROOT)/cmd_editor.c | $(BUILDDIR) + $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) + +run: $(TESTS) + @fail=0; for t in $(TESTS); do \ + echo "=== $$t ==="; \ + $$t || fail=1; \ + echo; \ + done; \ + if [ $$fail -ne 0 ]; then echo "TESTS FAILED"; exit 1; else echo "ALL TESTS PASSED"; fi + +clean: + rm -rf $(BUILDDIR) + +.PHONY: all run clean diff --git a/test/README.md b/test/README.md new file mode 100644 index 00000000..8c6df84e --- /dev/null +++ b/test/README.md @@ -0,0 +1,49 @@ +# Host unit-test harness + +Compile and test individual firmware translation units on the build host with +**gcc + AddressSanitizer + UBSan** — no SDCC, no flashing, no hardware. This is +the fast inner loop for the logic-level bugs in `../../NOTES/08-findings.md` +(parser, line editor, HTTP header, DHCP): edit → `make` → see red/green in +seconds instead of a minutes-long flash-and-reboot cycle. + +## Run +``` +cd test +make # build + run all tests (ASan/UBSan on) +make clean +``` +Exit status is non-zero if any check fails, a sanitizer trips, or the watchdog +fires — so it drops straight into CI. + +## How it works +- **`sdcc_shim.h`** (force-included) erases SDCC 8051 keywords (`__xdata`, + `__code`, `__banked`, …) so firmware sources compile under host gcc. It changes + no logic — the code under test is byte-for-byte the firmware source. +- **`support.c` / `support.h`** mock the hardware edges: the 16-byte serial ring + (`sbuf`), the command/history buffers, and the character-output sink + (`write_char` etc.). Buffers are sized **exactly** as on target, so ASan + redzones catch the same off-by-one overflows the 8051 hits. +- Builds define **`RTLP_HOST_TEST`**, which hides the firmware's libc-named + prototypes (`memset`/`strlen`/…) in `rtl837x_common.h` so they don't clash with + glibc. Argument order matches libc, so on-host callers transparently use the C + library. This guard is compiled out of normal firmware builds — zero on-target + effect. +- A **SIGALRM watchdog** (`watchdog_arm`) turns an infinite-loop bug into a fast + test failure instead of a hung runner. + +## Current coverage +| Test binary | TU under test | Findings exercised | +|-------------|---------------|--------------------| +| `test_cmd_editor` | `cmd_editor.c` | **C4** — full-line hang + `cmd_buffer` 1-byte overflow; basic entry & backspace regressions | + +## Adding a test for another module +1. Write `test_.c` with `main()` driving the module's entry points and + `CHECK(cond, msg)` assertions. +2. Add any missing mocked symbols to `support.c` (only what the linker asks for). +3. Add a target in the `Makefile` listing `test_.c support.c ../.c`. +4. If the module calls a firmware libc-name (`memcpy`/`strcpy`/…), it will use the + C library on host (compatible arg order); provide a `fw_*`-style mock only if + the semantics differ in a way the test cares about. + +Good next targets (pure logic, high finding density): `cmd_parser.c` (C6/C8), +the HTTP header parser in `httpd/httpd.c` (S1), `dhcp.c` (S5). diff --git a/test/sdcc_shim.h b/test/sdcc_shim.h new file mode 100644 index 00000000..c080ff44 --- /dev/null +++ b/test/sdcc_shim.h @@ -0,0 +1,40 @@ +/* + * sdcc_shim.h — neutralize SDCC 8051 keywords so firmware translation units + * compile under host gcc/clang for off-target unit testing. + * + * Force-included (gcc -include) ahead of every firmware source under test. + * It only erases storage/qualifier keywords and the banked-call attribute; + * it does NOT change any logic, so the code under test is byte-for-byte the + * firmware source. Hardware/register access is provided by mocks (support.c), + * never by this file. + */ +#ifndef SDCC_SHIM_H +#define SDCC_SHIM_H + +#include + +/* Memory-space qualifiers → nothing (host has one flat address space). */ +#define __xdata +#define __code +#define __pdata +#define __idata +#define __data +/* bool, not unsigned char: SDCC's bit types normalize any assigned value + * to 0/1, which _Bool reproduces exactly. */ +#define __bit bool +#define __sbit bool + +/* NOTE: the firmware ships its own 8051 memcpy/memset/strlen/strcpy/sleep with + * incompatible signatures (rtl837x_common.h). Host TUs must therefore build with + * -fno-builtin (so gcc doesn't auto-declare the builtins) and must NOT include + * /. Test code uses the t_* helpers in support.h instead. */ + +/* Function attributes → nothing (no banking / no 8051 calling conventions). */ +#define __banked +#define __reentrant +#define __naked +#define __using(n) +#define __interrupt(n) +#define __at(addr) + +#endif /* SDCC_SHIM_H */ diff --git a/test/support.c b/test/support.c new file mode 100644 index 00000000..58e64ad5 --- /dev/null +++ b/test/support.c @@ -0,0 +1,103 @@ +/* + * support.c — mock hardware edges + test environment for the host harness. + * + * Everything the firmware expects the platform/other-TUs to provide, but which + * has no business touching real hardware in a unit test, lives here: the serial + * ring, the command/history buffers, the character-output sink, and stubs for + * the print helpers. The buffers are sized EXACTLY as on target so that + * AddressSanitizer redzones catch the same off-by-one overflows the 8051 hits. + */ +#include "sdcc_shim.h" +#include +#include +#include +#include +/* Declared by hand instead of , whose sleep() prototype clashes with + * the firmware's own sleep() declaration in rtl837x_common.h. */ +extern unsigned int alarm(unsigned int); +extern void _exit(int); +#include "rtl837x_common.h" /* SBUF_SIZE, CMD_BUF_SIZE, CMD_HISTORY_SIZE, decls */ +#include "support.h" + +/* --- symbols the editor/parser reference (extern in the firmware headers) --- */ +__xdata volatile uint8_t sbuf_ptr; +__xdata uint8_t sbuf[SBUF_SIZE]; +uint8_t cmd_buffer[CMD_BUF_SIZE]; /* ASan-instrumented: OOB caught */ +uint8_t cmd_available; +uint8_t err_status; +uint8_t cmd_history[CMD_HISTORY_SIZE]; +uint16_t cmd_history_ptr; + +/* editor-internal read pointer (defined in cmd_editor.c, external linkage) */ +extern __xdata uint8_t l; + +/* the editor entry points under test */ +void cmd_editor_init(void); +void cmd_edit(void); + +/* ------------------------------- output sink ------------------------------- */ +char out_buf[8192]; +size_t out_len; + +void out_reset(void) { out_len = 0; out_buf[0] = 0; } + +static void out_putc(char c) { + if (out_len < sizeof(out_buf) - 1) { out_buf[out_len++] = c; out_buf[out_len] = 0; } +} + +/* --- mocked print helpers (signatures match rtl837x_common.h post-shim) ---- */ +void write_char(char c) { out_putc(c); } +void print_string(char *p) { while (*p) out_putc(*p++); } +void print_string_x(char *p) { while (*p) out_putc(*p++); } +void print_cmd_prompt(void) { print_string((char *)"> "); } +void itoa(uint8_t v) { /* firmware prints a decimal number */ + char t[4]; int n = 0; + if (!v) { out_putc('0'); return; } + while (v) { t[n++] = '0' + v % 10; v /= 10; } + while (n) out_putc(t[--n]); +} + +/* ------------------------------ environment -------------------------------- */ +void env_reset(void) { + sbuf_ptr = 0; + t_memset(sbuf, 0, sizeof(sbuf)); + t_memset(cmd_buffer, 0, sizeof(cmd_buffer)); + t_memset(cmd_history, 0, sizeof(cmd_history)); + cmd_history_ptr = 0; + cmd_available = 0; + err_status = 0; + out_reset(); + cmd_editor_init(); /* sets l = sbuf_ptr, clears cursor/len */ +} + +/* Push one byte into the ring, draining first if it is full (leave 1 slot). */ +static void ring_push(uint8_t b) { + while (((sbuf_ptr + 1) & SBUF_MASK) == l) + cmd_edit(); /* drain; under the C4 bug this spins */ + sbuf[sbuf_ptr] = b; + sbuf_ptr = (sbuf_ptr + 1) & SBUF_MASK; +} + +void feed_n(const char *s, size_t n) { + for (size_t i = 0; i < n; i++) ring_push((uint8_t)s[i]); + cmd_edit(); +} +void feed(const char *s) { feed_n(s, t_strlen(s)); } + +/* -------------------------------- watchdog --------------------------------- */ +static const char *wd_what; +static void wd_fire(int sig) { + (void)sig; + /* async-signal-unsafe write is acceptable: we abort immediately after. */ + fprintf(stderr, "\n HANG DETECTED (watchdog): %s\n", wd_what ? wd_what : "?"); + _exit(2); +} +void watchdog_arm(unsigned seconds, const char *what) { + wd_what = what; + signal(SIGALRM, wd_fire); + alarm(seconds); +} +void watchdog_disarm(void) { alarm(0); } + +/* -------------------------------- framework -------------------------------- */ +int tests_run, tests_failed; diff --git a/test/support.h b/test/support.h new file mode 100644 index 00000000..069691f1 --- /dev/null +++ b/test/support.h @@ -0,0 +1,53 @@ +/* + * support.h — test harness API for off-target firmware unit tests. + * + * Provides the mock environment (serial ring, command buffers, output capture) + * plus a tiny assertion framework. The firmware sources under test are compiled + * unmodified against this; only the hardware edges are mocked here. + */ +#ifndef TEST_SUPPORT_H +#define TEST_SUPPORT_H + +#include +#include + +/* Distinct-named string helpers so host code needn't include , which + * would clash with the firmware's own memset/strlen/strcpy prototypes in + * rtl837x_common.h (different 8051 signatures). */ +static inline void t_memset(void *d, int c, size_t n) { + unsigned char *p = d; while (n--) *p++ = (unsigned char)c; +} +static inline size_t t_strlen(const char *s) { const char *p = s; while (*p) p++; return (size_t)(p - s); } +static inline int t_strcmp(const char *a, const char *b) { + while (*a && *a == *b) { a++; b++; } return (unsigned char)*a - (unsigned char)*b; +} + +/* ---- captured terminal output (what the firmware would send over serial) ---- */ +extern char out_buf[8192]; +extern size_t out_len; +void out_reset(void); + +/* ---- editor/parser environment reset ---- */ +void env_reset(void); /* clears ring, buffers, editor state */ + +/* ---- drive the serial input ring exactly like the RX ISR would ---- + * Pushes bytes into the 16-byte sbuf ring, draining via cmd_edit() whenever the + * ring would overflow, then a final drain. Mirrors real ISR→editor handoff. + */ +void feed(const char *s); /* NUL-terminated */ +void feed_n(const char *s, size_t n); /* explicit length (for embedded NULs) */ + +/* ---- watchdog: arm before calling into code that could spin (C4 hang) ---- */ +void watchdog_arm(unsigned seconds, const char *what); +void watchdog_disarm(void); + +/* ---- minimal test framework ---- */ +extern int tests_run, tests_failed; +#define CHECK(cond, msg) do { \ + tests_run++; \ + if (!(cond)) { tests_failed++; \ + printf(" FAIL: %s (%s:%d)\n", (msg), __FILE__, __LINE__); \ + } else { printf(" ok: %s\n", (msg)); } \ + } while (0) + +#endif /* TEST_SUPPORT_H */ diff --git a/test/test_cmd_editor.c b/test/test_cmd_editor.c new file mode 100644 index 00000000..54cc61bf --- /dev/null +++ b/test/test_cmd_editor.c @@ -0,0 +1,76 @@ +/* + * test_cmd_editor.c — host unit tests for cmd_editor.c (the serial line editor). + * + * Focus: finding C4 (08-findings.md) — a full input line both HANGS the editor + * (continue without advancing the ring read pointer) and OVERFLOWS cmd_buffer by + * one byte (guard off-by-one lets cmd_line_len reach CMD_BUF_SIZE). + * + * These tests are RED against the current source and GREEN after the fix, with + * no hardware and no flash cycle. The hang is caught by a SIGALRM watchdog; the + * overflow is caught by AddressSanitizer redzones on cmd_buffer[]. + */ +#include "sdcc_shim.h" +#include +#include +#include +#include "rtl837x_common.h" /* CMD_BUF_SIZE */ +#include "support.h" + +/* editor state (globals in cmd_editor.c, external linkage) */ +extern __xdata uint8_t cmd_line_len; +extern uint8_t cmd_buffer[]; +extern uint8_t cmd_available; + +static void test_basic_line(void) { + printf("[test] basic line entry + Enter\n"); + env_reset(); + feed("vlan 199\n"); + CHECK(cmd_available == 1, "Enter marks a command available"); + CHECK(t_strcmp((char *)cmd_buffer, "vlan 199") == 0, "cmd_buffer holds the typed line"); +} + +static void test_backspace(void) { + printf("[test] backspace edits the line\n"); + env_reset(); + feed("helo"); + feed("\b"); /* delete the 'o' -> wait, cursor at end: removes last char */ + feed("lo\n"); + CHECK(t_strcmp((char *)cmd_buffer, "hello") == 0, "backspace then retype yields 'hello'"); +} + +/* C4 part 1: a line that fills the buffer must not spin forever. */ +static void test_full_line_no_hang(void) { + char big[200]; + printf("[test] C4: overlong line does not hang the editor\n"); + env_reset(); + t_memset(big, 'a', sizeof(big)); + watchdog_arm(5, "C4 full-line hang (editor spun without advancing ring ptr)"); + feed_n(big, sizeof(big)); /* 200 printable chars, no newline */ + watchdog_disarm(); + /* Reaching here means no hang. With the fix the line caps one short of full. */ + CHECK(cmd_line_len == CMD_BUF_SIZE - 1, + "line length caps at CMD_BUF_SIZE-1 (room reserved for NUL)"); +} + +/* C4 part 2: filling the buffer then Enter must not write cmd_buffer[CMD_BUF_SIZE]. */ +static void test_full_line_no_overflow(void) { + char full[CMD_BUF_SIZE]; /* 128 printable chars, exactly buffer size */ + printf("[test] C4: full line + Enter does not overflow cmd_buffer\n"); + env_reset(); + t_memset(full, 'b', sizeof(full)); + watchdog_arm(5, "C4 overflow-path hang"); + feed_n(full, sizeof(full)); + feed("\n"); /* under the bug, writes cmd_buffer[128] -> ASan trap */ + watchdog_disarm(); + CHECK(1, "128-char line + Enter completes without ASan overflow"); +} + +int main(void) { + printf("== cmd_editor host tests ==\n"); + test_basic_line(); + test_backspace(); + test_full_line_no_hang(); + test_full_line_no_overflow(); + printf("\n%d checks, %d failed\n", tests_run, tests_failed); + return tests_failed ? 1 : 0; +} From c05d83132b566a1cbdcd7aea6e418bc6028117fa Mon Sep 17 00:00:00 2001 From: bloqaudio Date: Mon, 14 Sep 2026 14:05:33 -0500 Subject: [PATCH 02/10] test: drop the unused typedef keyword and make the gcc-only warning switch conditional Building the firmware headers on the host showed two warnings from machine.h: `typedef struct machine { ... };` and the same for machine_runtime declare no alias, so the keyword only draws "useless storage class specifier in empty declaration". Every user says `struct machine`, so drop the keyword. -Wno-builtin-declaration-mismatch is a gcc switch and clang reports it as an unknown option on every build; add it only when the compiler is not clang. --- machine.h | 4 ++-- test/Makefile | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/machine.h b/machine.h index fd882229..b6d4a825 100644 --- a/machine.h +++ b/machine.h @@ -74,7 +74,7 @@ struct sfp_port i2c_bus_t i2c; }; -typedef struct machine { +struct machine { char machine_name[30]; uint8_t isRTL8373; // Lowest logical port number @@ -102,7 +102,7 @@ typedef struct machine { uint32_t mac_flash_offset; }; -typedef struct machine_runtime +struct machine_runtime { uint8_t isRTL8373 : 1; uint8_t isN : 1; diff --git a/test/Makefile b/test/Makefile index 66702b70..550cea3b 100644 --- a/test/Makefile +++ b/test/Makefile @@ -12,9 +12,12 @@ CC = gcc SRCROOT = .. # -include shim first so __xdata/__code/__banked vanish before any firmware header. CFLAGS = -std=gnu11 -g -O1 -Wall -Wextra -Wno-unused-parameter -Wno-unknown-pragmas \ - -Wno-builtin-declaration-mismatch -DRTLP_HOST_TEST \ + -DRTLP_HOST_TEST \ -fsanitize=address,undefined -fno-omit-frame-pointer \ -include sdcc_shim.h -I. -I$(SRCROOT) -I$(SRCROOT)/httpd -I$(SRCROOT)/uip +ifeq ($(findstring clang,$(shell $(CC) --version 2>/dev/null)),) +CFLAGS += -Wno-builtin-declaration-mismatch +endif LDFLAGS = -fsanitize=address,undefined BUILDDIR = build From f65416f0d13702e73c9faf9461f70b85561c23c3 Mon Sep 17 00:00:00 2001 From: bloqaudio Date: Mon, 14 Sep 2026 15:04:19 -0500 Subject: [PATCH 03/10] ci: run the host-side unit tests on every push The harness builds with the gcc the workflow already installs and runs in under a second, so it goes before the SDCC steps and fails the build first when a host-visible regression lands. --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f5f0150b..1f859285 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,6 +17,8 @@ jobs: run: | apt update apt install make gcc sdcc xxd python-is-python3 libjson-c-dev zlib1g-dev -y + - name: Run the host-side unit tests + run: make -C test - name: Check if machine.c can be compiled for all machines run: make machine_check - name: Make project From c04a5fc3a41660700e215fce59eaa5781c207917 Mon Sep 17 00:00:00 2001 From: DrDoof <8052722+DrDoof@users.noreply.github.com> Date: Sat, 12 Sep 2026 16:24:15 +0200 Subject: [PATCH 04/10] flash: bail out of a bulk read of zero length flash_read_bulk() ends its copy loop only through the comparisons len == 1|2|3|4, so a length of zero matches none of them. The following len -= 4 wraps on uint16_t to 65532 and reaches 4 only after 16384 iterations, writing 65536 bytes to the destination and overwriting the whole XDATA space. send_config() gets there with valid_len 0 whenever the stored configuration begins with a NUL byte, which is the state left by uploading an empty configuration file. Every later request for the configuration page then clears the management VLAN and the STP flag and replaces the host address, while the switch keeps running and keeps forwarding traffic. --- rtl837x_flash.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rtl837x_flash.c b/rtl837x_flash.c index 5f04c88a..f18d21fb 100644 --- a/rtl837x_flash.c +++ b/rtl837x_flash.c @@ -222,6 +222,10 @@ void flash_read_jedecid(void) void flash_read_bulk(__xdata uint8_t *dst) { short status; + + if (!flash_region.len) + return; + flash_configure_sio(); while (flash_read_status() & STATUS_REG_BUSY_MASK); flash_configure_mmio(); From e0df37f359e6b2be8a7df8d9846f9cd03130511e Mon Sep 17 00:00:00 2001 From: DrDoof <8052722+DrDoof@users.noreply.github.com> Date: Sat, 12 Sep 2026 18:34:59 +0200 Subject: [PATCH 05/10] flash: bail out of a security-register read of zero length flash_read_security() ends its loop the same way flash_read_bulk() did before the previous commit: only through comparisons with 1, 2, 3 and 4. A length of zero matches none of them, the following subtraction wraps on uint16_t and the loop keeps printing until the counter comes back down to 4. No caller passes zero today, since the only one is the CLI with a constant length of 40, but the bound belongs in the function rather than in whoever calls it. --- rtl837x_flash.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rtl837x_flash.c b/rtl837x_flash.c index f18d21fb..3805d730 100644 --- a/rtl837x_flash.c +++ b/rtl837x_flash.c @@ -262,6 +262,9 @@ void flash_read_bulk(__xdata uint8_t *dst) void flash_read_security(void) { + if (!flash_region.len) + return; + flash_configure_sio(); while (flash_read_status() & STATUS_REG_BUSY_MASK); From 6ea9503ab24573d26147c60740277deae11f37b3 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Tue, 15 Sep 2026 09:37:11 +0200 Subject: [PATCH 06/10] httpd: do not accept a session cookie before the first login session_id is cleared at boot and only a successful login fills it in, while the uptime counter and last_session_use both start at 0. For the first SESSION_TIMEOUT seconds after a start, a request carrying an empty "Cookie: session=" therefore passes is_word_x(session, session_id) and is treated as logged in, which is enough for /cmd, /config and /upload. Only look at the cookie once a session has been handed out. --- httpd/httpd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httpd/httpd.c b/httpd/httpd.c index f87de897..74c11148 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -340,7 +340,7 @@ __xdata uint8_t *scan_header(__xdata uint8_t * __xdata p) read_reg_timer(&now); - if (session) { + if (session && session_id[0]) { if (now - last_session_use > SESSION_TIMEOUT) { dbg_string("Session expired\n"); } else { From 3b3f36256c94c31125e31922eb064ed0ed1b95e5 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Tue, 15 Sep 2026 15:28:36 +0200 Subject: [PATCH 07/10] flash: drop the unused status variable from flash_read_bulk() Since #427 the busy wait calls flash_read_status() directly, so the local it used to go through is no longer read anywhere. --- rtl837x_flash.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/rtl837x_flash.c b/rtl837x_flash.c index 3805d730..2ec5bc3f 100644 --- a/rtl837x_flash.c +++ b/rtl837x_flash.c @@ -221,8 +221,6 @@ void flash_read_jedecid(void) */ void flash_read_bulk(__xdata uint8_t *dst) { - short status; - if (!flash_region.len) return; From 0cd56b11963b39839dd331e7d2bd9d3167392756 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Tue, 15 Sep 2026 19:27:50 +0200 Subject: [PATCH 08/10] ci: build pull requests too The workflow only ran on push, so a pull request opened from a fork never got a build, and neither did the host-side unit tests that run in the same job. --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1f859285..8fc36371 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,6 +3,7 @@ name: Build firmware on: push: branches: ['**'] + pull_request: jobs: build: From 8e78d1e629659ba7598d6373100d5f009c207ff4 Mon Sep 17 00:00:00 2001 From: bennydiamond Date: Mon, 14 Sep 2026 21:30:24 -0400 Subject: [PATCH 09/10] Missing closing bracket --- machine.c | 1 + 1 file changed, 1 insertion(+) diff --git a/machine.c b/machine.c index aeb8d5bc..1c2fee14 100644 --- a/machine.c +++ b/machine.c @@ -1182,6 +1182,7 @@ __code const struct machine machine = { 0 }, }, +}; #elif defined MACHINE_POE_2G080110GS __code const struct machine machine = { .machine_name = "MokerLink POE-2G080110GS", From 0d115e0e0bfaf26b4d50480f1883a3ff2c48e4f7 Mon Sep 17 00:00:00 2001 From: bennydiamond Date: Tue, 15 Sep 2026 15:34:34 -0400 Subject: [PATCH 10/10] Include HYWS-SGT0108S in makefile recipe 'machine_check' --- machine.h | 1 + 1 file changed, 1 insertion(+) diff --git a/machine.h b/machine.h index b6d4a825..14fc5c13 100644 --- a/machine.h +++ b/machine.h @@ -44,6 +44,7 @@ // #define MACHINE_SWTG024AS_V2_0 // #define MACHINE_FG_4GT_2SX_V2_0 // #define MACHINE_FG_8GT_1SX +// #define MACHINE_LIANGUO_HYWS_SGT0108S // #define MACHINE_POE_2G080110GS typedef struct {