Files
ESP32_Serial_Swiss_Army_Knife/tests/web_admin_transport
Commander1024 d9ec3c08de Add Typed Display Settings Administration
Implements admin-only Display settings with generation-checked
Apply, Save, Load, Defaults, and Reset operations across the web UI,
CLI, SSH dispatcher, and local UI owner. Adds bounded HTTP handling,
session-isolated operation results, browser lifecycle support, and
comprehensive host tests and documentation.
2026-09-09 10:15:23 +02:00
..
2026-09-07 09:36:38 +02:00
2026-09-07 09:36:38 +02:00

Admin WebSocket transport host harness

Run from the repository root:

python3 tests/web_admin_transport/run.py
python3 tests/web_admin_transport/run.py --strict
python3 tests/web_admin_transport/run.py --tickets
python3 tests/web_admin_transport/run.py --sanitize

CC selects the compiler. The runner compiles the current production src/web_admin_transport.c and production public headers into a temporary C11 translation unit with -Wall -Wextra -Werror. Only include directives are removed; transport functions are not copied or reimplemented. Temporary output is removed. platform.h supplies host types; fakes.h doubles dependencies; test.c exercises production entry points and inspects private state for lifecycle/wipe assertions. No firmware build, network access or device operation is performed.

Latest reported results — 8D.7 second certificate slice

Implementer reports run.py --tickets PASS 25 transport / 12 ticket groups, including certificate owner routing, currentness rejection and commit → stop → start short-circuit/error behavior. The owner's dispatcher_actions mask selects the existing 12 KiB dispatcher, not the 4 KiB control task. tests/admin_console_boundary/run.py (including certificate.c) separately covers typed deferred handoff/pending gate/executing reservation; lifecycle.py covers canonical handlers and unchanged SSH/UART0 behavior. Policy, server lifecycle 11, cookie --admin and store --serial also pass as reported. Independent review has no actionable findings; sanitizer validation is unavailable due to missing libasan/libubsan. No hardware validation is claimed or performed by this documentation update.

Current WEB policy allows exact parsed web status, web stop, web certificate rotate --force, and reboot/self-close; other web forms, account mutations, network mutations and restricted SSH lifecycle/key mutations remain blocked. Certificate drain/acknowledgement bounds do not bound queued execution or prove browser receipt. See docs/phase8d7_implementation.md for final parent build/resources, trust/relogin/failure checklist and authorized next bounded slice; M2 acceptance remains pending.

Earlier results recorded 2026-09-06

8D.7 first slice: run.py --tickets passes 23 transport / 12 ticket groups. Adds WEB stop/reboot owner routing, stale/revoked action rejection and stop-error propagation, pending-input discard before poll and cancellation-during-receive with/without an occupied RX buffer. python3 tests/admin_console_boundary/lifecycle.py separately checks the production canonical handlers and unchanged SSH/UART0 behavior. Dependencies remain doubled; no target stop/reboot is executed. See docs/phase8d7_implementation.md for scope and pending validation.

Final continuation: run.py --tickets passes 19 transport / 12 ticket groups, including the HTTPD-owned shutdown retry/reuse regression. server_lifecycle.py passes 11 groups against extracted production server lifecycle/URI tables. python3 tests/web_cookie_auth/run.py --admin now links the real cookie policy, session store, tickets, private adapter and admin transport for endpoint admission, pre-101 rejection and logout/expiry/currentness cleanup checks; console and runtime IO remain doubled. These supersede the older counts/integration-pending notes below. Final admin closure uses direct HTTPD-owned shutdown, not queued IDF session-close work. Parent reports the sequential final firmware build after this fix passed in 23.55 s, at 95,580 B RAM / 1,637,273 B flash, and the final independent security integration review found no actionable findings. See docs/phase8d5_implementation.md for build history and the pending target procedure.

After the production empty-frame, input-deadline and timer-generation fixes:

  • python3 tests/web_admin_transport/run.py: 18 groups passed.
  • python3 tests/web_admin_transport/run.py --strict: 18 groups passed.
  • All assertions are mandatory by default. --strict is retained as a compatibility flag with identical behavior; there are no expected-defect probes or failure exemptions.
  • Earlier, before these regression additions, --tickets also ran the separate production ticket suite: 12 groups passed. It was not rerun in this update. This is a separate suite, not transport plus real-ticket integration.
  • The earlier --sanitize --tickets attempt was blocked at linking by missing /usr/lib64/libasan.so.8.0.0 and /usr/lib64/libubsan.so.1.0.0. Sanitizers were not rerun in this update; no sanitizer pass is claimed.

Meaningful coverage

  • PSRAM-only allocation flags, allocation/timer-create failure cleanup, retry, idempotent initialization and duplicate attachment rejection.
  • Authentication-helper delegation, role rejection, ticket response/capacity, exact upgrade URI shape, ticket failure and ticket consumption before capacity rejection. Shared console admission precedes 101; fake console assigns index 1 to verify that the returned shared-slot token is retained.
  • One admin socket without replacement; failed upgrade and revocation during console admission release reservations and console state.
  • At most one outstanding transport poll; byte-preserving input, partial input consumption/retry, consumed-input wiping, output delivery and TX wiping.
  • Nonfinal/text/oversized frames and another frame while RX is occupied fail closed outside deferral; during observed deferral bounded input is discarded without cancelling the scheduled action. Stalled input closes after the five-second deadline. Pending bytes are not fed at or after the deadline even if the console can now consume them.
  • Session/account notification isolation, idle currentness failure, invalidation during currentness checking and between output consumption and send. Notifier paths close the console and flag the slot without socket operations.
  • Send/queue failure paths, close-trigger suppression after success, deferred action support checks and the output-send drain guard. A queue-submission hook frees and re-admits the HTTPD slot before returning failure: the replacement generation remains unflagged/live and its next poll delivers output.
  • Detach disables acceptance and new timer submissions. A deterministic hook enters detach during submission, exercises its timeout, then verifies retry. Queued work after detach does no IO. Successful-stop simulation discards pending work and frees HTTPD context before stopped retires the queue marker/re-attach.
  • Disconnect wipes payload and retires console state; replacement generations reject a previous owner token. Dependency fakes assert external calls occur outside the transport critical section and socket/input/output operations occur in the simulated HTTPD owner context.

Empty-frame regression

IDF 5.5's httpd_ws_recv_frame uses frame->len == 0 as its header-parsing sentinel. Calling it twice on an empty frame would parse a second header. The production transport now skips the payload receive for zero-length frames. The mandatory regression asserts one header parse, no input/close side effect, and successful feeding of a following nonempty frame.

The fake models the sentinel checked in the installed IDF 5.5 components/esp_http_server/src/httpd_ws.c; it counts parses rather than emulating socket timeout or wire desynchronization. No production source was edited for this regression update.

Limits / remaining integration and target work

This is deterministic single-threaded execution, not a concurrency proof. Locks are assertions and races are selected reentrant hooks; FreeRTOS scheduling, esp_timer scheduling, stack bounds, allocation placement on hardware, and memory floors are not measured. Payload byte counts use host ABI metadata sizes; 512-byte RX plus 1024-byte TX are not the entire allocated struct size.

Cookie/Origin/CSRF parsing, real session expiry/principal storage, ticket crypto, shared-console allocator/dispatcher/prompts/policy and SSH are doubled here. Their implementation correctness is not established by this harness. In particular it does not prove simultaneous use of both real shared console slots. The independent ticket suite is optional via --tickets.

HTTPD request/context/upgrade/send/close and queue operations are fakes. Close triggering is recorded, not queued as IDF's real session-close work. Queue delivery loss, socket-slot reuse, TLS partial reads/writes, ping/pong/control-frame handling, actual HTTPD stop completion and on-wire pre-101 responses require real-IDF or target validation. The empty-frame sentinel is modeled from source, not linked from IDF. Failed httpd_ssl_stop orchestration is the integrating server's duty; this harness only calls stopped after simulated successful shutdown.

Server route registration, six-socket non-eviction policy, revocation hook order, status aggregation, full-client coexistence, serial writer/USB isolation and whole-8D.5 target acceptance remain main integration/target work. No production source is changed by this harness; passing normal mode does not close Phase 8D.5.

Temporary authenticated device smoke client

client.py is a local Python-standard-library-only tool, not shipped firmware, UI, or a new endpoint. Running it contacts the specified device and consumes a login attempt/session and, for an administrator, an admin console slot. Only run against a device you are authorized to test. It never uploads, builds or erases.

# System TLS trust; certificate hostname must match the explicit HTTPS origin.
python3 tests/web_admin_transport/client.py --url https://device.local

# Trust a locally obtained PEM CA/device certificate; hostname is still verified.
python3 tests/web_admin_transport/client.py --url https://device.local --cafile device-cert.pem --smoke

# Explicit isolated/local-test opt-in ONLY: warns and disables TLS verification.
python3 tests/web_admin_transport/client.py --url https://device.local --insecure --max-runtime 60 --timeout 10

Replace the example hostname with your device's certificate-matching hostname. Only HTTPS origins on port 443 are accepted: no URL credentials, application paths, queries or fragments. Host and Origin are derived from that validated origin; redirects and environment proxies are not followed. --insecure does not enforce private-address routing: it is an explicit operator opt-in, not proof that the destination is local. Prefer --cafile, with its certificate obtained through a trusted channel; insecure mode exposes credentials to active network interception.

Username is requested with input, password with non-echoing getpass. Password entry fails rather than falling back to echoed input. No credential arguments, cookie files or HTTP debug logs are used. Cookies (including HttpOnly) are kept in an in-memory CookieJar and copied into the WebSocket request header. Routine results never print cookies, CSRF, passwords, tickets, ticket URLs or exception representations. Python immutable strings cannot be reliably erased from memory; this is not protection against process inspection, swap or core dumps.

The default and only mode is bounded smoke (--smoke is optional):

  1. GET /api/login-challenge with X-Login-Bootstrap: 1, then JSON username/password POST /api/login with challenge CSRF, then GET /api/session for session CSRF. All requests include the matching Origin.
  2. For role user, require HTTP 403 from the CSRF-protected admin ticket POST.
  3. For role admin, mint a ticket, authenticate /ws/admin with the cookie and ticket, validate the 101 handshake, then require 403 when replaying that ticket with the same live cookie. Run binary help, an empty binary frame, empty Enter, and exit, waiting for prompts/closure rather than sending commands in a burst.
  4. Close the client socket and attempt CSRF-protected logout in finally; require a subsequent session request to return 401. Cleanup failure is reported and makes the command fail. If connectivity or authentication-response delivery fails, server-side cleanup cannot be guaranteed; a session may remain until its absolute expiry. There are no automatic login retries.

Console bytes are deliberately printed directly to stdout, including terminal control sequences. Use a trusted device and do not capture output into routine logs if console commands may disclose sensitive information. Authentication metadata and rejected-response bodies are not printed. Smoke uses no mutating administration command other than closing its own console/login session.

This version intentionally has no interactive/raw-terminal mode, so it does not change terminal settings or exercise completion/hidden prompts. It requires POSIX interval timers for a hard runtime guard: default 60 seconds, configurable up to 300, starting after credential entry, plus up to 10 seconds for cleanup. Individual transport timeout defaults to 10 seconds (maximum 30). HTTP/upgrade headers or response bodies, frames, and per-command output have bounded sizes. The WS parser accepts only final, unmasked, bounded binary/control frames and masks all client frames; it is not a general-purpose WebSocket implementation.

Local validation (2026-09-06): syntax compiled in memory, and offline in-memory checks passed for valid/rejected URLs, masked client frame encoding, bounded server frame rejection, both-role smoke/replay/command sequencing, HttpOnly CookieJar header forwarding and logout cleanup sequencing. These checks were run without adding test files or opening sockets. No device/network command, TLS handshake, browser test, interactive test or hardware validation was performed. The client is temporary test tooling; its implementation and these local checks do not establish whole-8D.5 acceptance.