page_impl.c and rtl837x_port.c are linked together over the simulated
engine, so /vlan.json, /vlanlist and /l2.json are checked against what
vlan_create, pvid and the L2 table hold, through the same bytes the switch
would. The L2 walk is driven the way the web page drives it, page after
page until an index repeats, which is also where the repeat of the first
entry is pinned down as the wrap marker the page relies on. The counter
test fails on the code before the previous commit.
send_status wrote the high and the low word of a packet counter one after
the other, each without its leading zeros. That reads fine until a port
passes four billion packets; from then on a low word of 5 prints as "0x15"
next to a high word of 1, and the rates the page derives from the
difference between two readings jump around. send_counters already padded
the low word, so both now share one helper that pads it only when the high
word is set, which also drops the leading "0" the counters page used to
print in front of every small value.
The switch is reached through four register functions and the SFR_DATA
registers, so a flat register file behind them is enough to run the table
code on the host. A few addresses are live: writing TBL_CTRL with EXECUTE
performs the VLAN or L2 operation at once and clears the bit, STAT_GET
fetches a counter, L2_TBL_FLUSH_CTRL drops entries. Nothing ever reports
busy, so a polling loop runs once and the tests say nothing about what
happens when hardware stalls; that question was settled elsewhere.
The header states the VLAN and L2 entry layouts on its own, so a test can
hold what rtl837x_port.c writes against a second reading of the same bits
rather than against itself. The first suite does that for the VLAN entry,
the PVID register two ports share, the static multicast and management
entries, the per-port flush and the trunk registers. Two stub headers stand
in for the ones a firmware build generates, so the harness needs no SDCC
build first.
Two things kept every register-driven module out of the host harness. The
SFR keywords were unknown, so rtl837x_sfr.h did not compile at all, and once
it did, each translation unit would have defined its own copy of the
registers. They map to extern declarations now; a mock emits the storage
once by re-including the header with the keywords redefined.
The other was memset. rtl837x_common.h hides the firmware's own prototypes
under RTLP_HOST_TEST so host builds use the C library, but nothing declared
the library versions, and the modules do call them. The shim pulls in
string.h ahead of everything, where nothing can clash with it.
The messages said Error: themselves, or usage:, or nothing at all.
cmd_error() prints the prefix now, so every rejected command answers the
same way and the strings only carry what is specific to them.
Two messages that started with Error or refusing are reworded so the
prefix does not read twice. The syslog usage text becomes one string
instead of three calls.
The session cookie carried neither Expires nor Max-Age, so browsers that
clean up session cookies aggressively (LibreWolf, for one) may drop it
while the session is still valid. Send Max-Age=session_timeout so the
declared lifetime matches the timeout already enforced by the firmware.
itoa16_html() only converted four digits, so extend it to five to print
timeouts up to 65535. That also fixes information.json, which used the
same helper and reported wrong values from 10000 s up.
Adds a numeric input next to the syslog settings to read and set the
session timeout without knowing the command. The current value is
reported in information.json, so the field also shows the default the
firmware was built with.
Costs 86 bytes of code (BANK1) and nothing in RAM.
run_cmd_body() answers POST /cmd with 400 when err_status is set, but
only the tokenizer set it. Every "Error:", "Invalid ..." and "Unknown
command" from the parsers went back as 200, so the web page reported the
command as applied, and save_cmd put the rejected line into the command
history, from where Save to flash wrote it into the startup config.
Set ERR_INVALID_ARGUMENT on every path that rejects a command: the err:
labels of the parsers, the parsers that print their own error, the
stp parser and the unknown command fallback. Warnings that still apply
the command, such as a full VLAN name table, are left alone.
Record a command in the history only when it was accepted, and let
execute_commands() report the first failure of a multi-line body, since
cmd_tokenize() clears err_status again for every following line.
Save to flash ran the stored config through the same filter as the
command log, so stored lines the page does not know were dropped. The
stored lines are now carried over as they are and only the log is
filtered.
ingress only changes the ports it lists, but the merge replaced every
stored ingress line. It now merges per port; "ingress t|u|a" still
replaces everything, and per-port settings an all-ports line already
overrode are not revived.
Write from the config editor no longer clears the command log or the
unsaved marker, since the edited text does not hold the changes made on
other pages; only Save, which merges them, clears the log.
The command log grows from 1 kB to 2 kB, which still fits the 2500 byte
output buffer, and send_cmd_log() stops at the end of that buffer.
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.
handle_post() takes the request path from byte 6 of the segment it is
given. For the first segment of a POST that is the path, but for a later
segment of a multipart upload it is whatever part of the file happens to
sit there. The cmd and login handlers were matched before the multipart
state was checked, so a segment of a config or firmware upload with
"cmd " at that offset ran the rest of the segment as CLI commands, and
one with "login " there aborted the upload with 400.
Check for an upload in progress, and for new upload and config requests,
before looking at cmd and login. A new POST cannot match both sets of
words, so nothing changes for requests that are not uploads.
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.
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.
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.
The session timeout was a compile-time constant (200 s), so changing it
meant rebuilding the firmware. Make it a variable that keeps the same
default and add a "session <seconds>" command, so it can be set from
the console or from the startup configuration like the other settings.
Measured on KP_9000_9XHML_X_V3_1: ROM +180 bytes (BANK1 +17, BANK2
+161) and xdata +2 bytes; the internal RAM map is unchanged.
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.
The firmware counts every authenticated request as session activity, and
the page polls the status every 2.5 s, so an open tab never let the
session expire. Track the last mouse, key, touch or wheel event and treat
the page as hidden once ten minutes have passed without one: the pollers
keep ticking but send nothing, the session times out in the firmware as
it did before, and the next input resumes polling, which meets the 401
and returns to the login page.
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.
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
BANK2 outgrew its 48 KB window on PCB_SWTG018AS_V2_1_0 (49182 bytes, 30
over) once the spanning tree code learnt about link aggregation, and the
image builder refuses to link. Only KP_9000_6XHML_X2 is built in CI, so
the overflow was not caught there.
dhcp.c is self-contained: its three exports are __banked, the rest of
the module is only reached from inside it, it holds no __code pointers,
and its uip calls already crossed into BANK1. Moving the pragma is the
whole change. BANK2 49182 -> 43691 on PCB_SWTG018AS_V2_1_0, BANK3 5709
-> 11206, BANK1 +6 for the trampolines; the generated assembly shows no
plain lcall leaving the module's bank.
The firmware lists a LAG as its own entity in /stp.json (`lag` and `mbr`,
`p` 0) and drops the member ports from the list; its settings take
`stp lag <n>`. Give the group its own row in both spanning tree tables,
named with its members, key the cells by entity rather than port number,
and rebuild the tables whenever the entity set or a membership changes so
a group created on the LAG tab appears without a reload. The `stp lag`
commands are recognised for the unsaved-changes badge and the startup
config merge alongside `stp port`.
The rename of the entity index turned `stp port <n>` into `stp ent <n>`
in the parser only. doc/stp.md, the web UI and every saved startup
config still spell it `stp port`, so per-port settings were rejected on
the console and silently dropped when the startup config was replayed at
boot. Restore the keyword; `stp lag <n>` is unchanged.
A dark receiver reports 0 mW, which printed as -Infinity dBm. The DOM
value has a resolution of 0.1 uW, so -40 dBm is the lowest reading the
module can express; clamp there.