Provide admin-only SSH status plus generation-safe start, stop, and single-session disconnect operations through the bounded dispatcher. Include Settings UI coverage, lifecycle safeguards, and host-side regression tests.
36 KiB
Code map
This is a semantic map, not a complete file inventory. Start here, then read the listed headers and only the implementation paths relevant to the task.
Bootstrap and system composition
Responsibility: establish startup order, recovery behavior, configuration loading, service dependencies, and command registration.
- Files:
src/main.c,src/CMakeLists.txt, rootCMakeLists.txt,platformio.ini,partitions.csv,src/idf_component.yml; inspect targeted settings insdkconfig.defaultswhen crypto, PSRAM, HTTPS/HTTPD, USB, or socket capacity matters - Entry point:
app_main() - Called by: ESP-IDF runtime
- Dependencies: every subsystem initializer
- Lifecycle constraint: optional display/network failures should not remove UART0 administrative recovery or USB UART1 access; the custom administration frontend starts only after command registration.
Secure randomness
Responsibility: provide the sole project-owned, mutex-serialized application DRBG, seeded before Wi-Fi/radio use.
- Files:
src/secure_random.{h,c} - Interfaces:
secure_random_init(), random-byte helpers,secure_wipe() - Called by: HTTPS material, SSH keys, users, Wi-Fi defaults, tickets, the HTTPS authentication cache, and the wolfCrypt seed callback
- Constraint: initialization order is security-significant; the DRBG deliberately avoids post-radio reseeding and fails closed at its generation limit. Do not add independent weak RNGs or radio-dependent early entropy paths.
Physical RS-232 and serial service
Responsibility: protect the MAX3243/UART resource, own UART1 while running, buffer binary RX/TX, apply serial configuration, and expose status/counters.
- Files:
src/rs232_port_owner.{h,c},src/serial_service.{h,c},src/serial_config.{h,c},src/serial_console.{h,c} - Interfaces: owner claim/release/fault; serial init/start/stop/read/write/configuration/snapshots; versioned NVS load/save
- Normal data caller:
session_broker; USB, WebSocket, role-userSSH, console, and local UI also call serial lifecycle/configuration APIs as appropriate - Dependencies: ESP-IDF UART driver,
board_pins.h, NVS - Ownership: the cooperative owner arbitrates active diagnostics (
PHASE0) against the service (SERVICE); boot-time static-safe GPIO initialization and service-owned static-mode restoration are explicit exceptions. Unsafe cleanup marksFAULTuntil reboot. - Lifecycle: stop/reconfiguration discards serial-service RX/TX and task-local pending bytes, but leaves broker clients, writer ownership, events, and already-fanned output intact. The 16 KiB RX and 8 KiB TX payloads prefer PSRAM; their FreeRTOS controls and UART driver storage remain internal.
Session broker
- 8D.16 management:
session_broker_get_management_snapshot()copies compact eight-client rows plus writer/lease generation atomically with zero wait;session_broker_assign_writer_current()compares generation and target under the force-writer lock. 29-bit client generations retire slots instead of wrapping; saturating lease generation fences ABA, survives counter clear, and leaves ordinary recovery available. Actual canonical regressions extendtests/session_broker_diagnostics/run.py. Full contracts/wrap analysis:docs/phase8d16_implementation.md.
Responsibility: mediate all transport access to the serial service; provide one writer lease and multiple isolated observers.
- Files:
src/session_broker.{h,c},src/session_console.{h,c} - Interfaces: connect/disconnect, request/release/force writer, nonblocking read/write/event APIs, snapshots and counters
- Called by: USB, web serial, role-
userSSH, console tests, local UI snapshots/actions - Dependencies:
serial_service - Data path:
transport -> broker -> serial service -> UART1; reverse data is fanned out per client. - Ownership: client IDs are slot/generation-safe; events are advisory and can drop, so use snapshots as authority.
- Lifecycle: one permanent task and eight preallocated client slots; slow output drops only for the affected client.
- Diagnostics:
broker countersadds active-client ID/type/pending/HWM/UART/queued/read/dropped rows; clear seeds HWM from pending, disconnect removes rows but retains global discard accounting.tests/session_broker_diagnostics/run.py; capture before disconnect, never use consumingbroker readas a probe. Semantics/recipe:docs/web_throughput_diagnostics.md.
Native USB CDC
Responsibility: adapt TinyUSB CDC host state/data to one broker client.
- Files:
src/usb_cdc_transport.{h,c},src/usb_console.{h,c} - Interfaces:
usb_cdc_transport_init(), snapshots/counters, queued writer request/release - Called by: startup, TinyUSB callbacks, console/local UI
- Dependencies: TinyUSB, broker, serial service
- Flow:
USB host <-> USB task <-> broker - Lifecycle: permanent owner task; broker client exists only while attached with host DTR asserted.
- Constraint: callbacks retain the latest host line coding only for diagnostics; it never reconfigures UART1. UART1 framing and speed remain controlled by the serial configuration and its explicit persistence commands.
Web and WebSocket serial
-
8D.18 client/writer contexts (2026-09-13):
web_ui.cextends8D.17's sole quick host with admin-only Broker clients/Active writer entrances to the existing Broker controller/native confirmation. One5-second-after-success live timer and5-second whole-read deadline; explicit identity/lease token retained across refresh, sticky stale/absence latches prevent rebasing/resurrection. Full-page drafts untouched by new triggers; focused controls retain focus with guarded aria-disabled state. No backend/policy/assets/CPU/transport changes.tests/web_ui_session/{broker.cjs,layout.py}:135 UI groups+renderer/HTML/CSP; broad broker/auth/lifecycle/transport regressions PASS. Baseline100,300/1,788,661 → final100,300/1,793,301 RAM/flash (+0/+4,640 B), CPU160 verified. Independent parent review and target sign-off pending. Exact contracts/tests/limits/checklist:docs/phase8d18_implementation.md. -
8D.17 quick settings (2026-09-13):
web_ui.cowns Serial/Wi-Fi status-trigger nonmodal popovers using the single existing settings DOM/controller, no parallel drafts/endpoints. Secret-free Network quick mode excludes password controls; full-page promotion preserves controller/nonsecret draft, dismissal fences reads/polling without replay. Hover/focus/click/tap, Escape/outside dismiss; full-page drafts protected from hover. Teststests/web_ui_session/{browser,network}.cjs,layout.py:126 UI groups + renderer/HTML/CSP PASS; optional Chromium geometry blocked by sandbox, target/independent parent review pending. Baseline100,300/1,782,613 → final100,300/1,788,629 RAM/flash (+0/+6,016 B). CPU160/combined WS send/Broker/Display unchanged. Contract, exact tests and checklist:docs/phase8d17_implementation.md. -
8D.16 Broker (2026-09-13):
web_broker_settings.{c,h}owns optional admin-only GET/api/settings/broker, GET/POST/api/settings/broker-operation; existing dispatcher queues only IDs.web_ui.cadds Serial/Display-style full-page rows and explicit confirmed assignment, no mutation on view/selection. 256-byte/four-receive request,2048-byte snapshot,96-byte result, one login-bound slot/no timer;33 handlers/six sockets, unchanged tasks/stacks/queue/assets/CPU160/combined WS send. Tests: cookie--broker6+shared, broker management/wrap, dispatcher, lifecycle25, UI119+HTML/CSP and broad regressions. Baseline100,196/1,765,233 B → final100,300/1,782,613 RAM/flash. Independent parent review and target sign-off pending. Contracts/resources/checklist:docs/phase8d16_implementation.md. -
8D.14 Display (2026-09-09):
web_display_settings.{c,h}owns optional admin-only GET/api/settings/display, GET/POST/api/settings/display-operation;web_ui.csupplies Serial-style dim/off settings and bounded completion checks.local_status_uiowns generation-safe config/storage reservation shared with CLI; buttons do not edit timeouts. No I2C changes. 256-byte/four-receive request,128-byte snapshot,96-byte result, one slot/no timer;30 handlers/six sockets, unchanged tasks/stacks/queue/schema. Tests: cookie--display(7+shared), UI111, lifecycle23, dispatcher and broad regressions. Actual baseline100,100/1,748,513 B → final100,196 RAM/1,765,233 flash at160MHz. Target pending; exact API, reset ordering, deadlines, resource/validation limits:docs/phase8d14_implementation.md. -
Current 8D.12/8D.13 — user functional sign-off 2026-09-08, including Settings presentation:
web_network_settings.{c,h}owns optional admin-only GET/api/settings/networkand GET/POST/api/settings/network-operation;web_ui.csupplies Network, UTF-8/hex SSID editing and explicit transient-secret/connection controls.wifi_managerowns generation-checked secret-free snapshots/patch/save/stored-only load and radio transitions;mdns_serviceowns independent conditional hostname persistence, with manager reannouncement. Existing dispatcher receives IDs only. 768-byte request/2,048-byte snapshot/128-byte result, one slot/one-second timer with 30-second queued expiry plus scheduling latency; no hard cancellation. 27 handlers/six sockets, no task/stack/queue/schema growth. Parent integrated tests/build PASS; latest styling UI100 + renderer/CSP/Chromium checks, 99,548 B RAM / 1,744,325 B flash. User full-mix evidence accepted; loaded internal/DMA minima2,276/156 B remain resource follow-ups, not reserve approval. Full contract/exclusions/checklist:docs/phase8d12_13_implementation.md. Both phases user-authorized together; no 8D.14/M3 claim. Older next-phase statements below are historical. -
8D.11:
web_account_settings.{c,h}extends Accounts with fingerprint-only POST/api/settings/accounts/keysand key-add/key-delete/key-clear on the existing operation endpoint/dispatcher.user_database.{c,h}owns zero-wait target-checked snapshots and canonical conditional key mutations.web_ui.chandles confirmations, sparse stable indices and self-revocation uncertainty. 24 handlers, six sockets; no new task/stack/queue depth. Host-tested/build-verified, target pending. Contracts/tests/checklist:docs/phase8d11_implementation.md.
Responsibility: serve authenticated HTTPS UI/API, issue WebSocket tickets, and adapt browser serial sessions to broker clients.
-
Files:
src/web_server.{h,c},src/web_serial_transport.{h,c},src/web_ui.{h,c},src/web_console.{h,c} -
Ordinary HTTPS idle cleanup:
src/web_httpd_idle.{c,h}, owner sweep inweb_httpd_adapter.{c,h}, lifecycle/TLS composition inweb_server.c;tests/web_httpd_idle/run.py. Independent of diagnostics/optional transports: 15-second observed idle, one-second timer/one queued probe, six rows, actual WS/async/pending exemptions, safe current-owner shutdown and stop/restart fencing. No LRU/socket/timeout/stack increase. SDK queue/owner-delay limits and target checklist:docs/https_idle_cleanup.md. -
Independent throughput diagnostics:
web_serial_transport.{c,h}owns two fixed per-slot binary-TX aggregates and epoch fences;web_console.cexposes default-disabledweb performance enable|disable|show|clear. Queue-entry/callback-entry, synchronous-send and completion/drain-return estimates, not peer receipt or scheduler-only latency.tests/web_serial_performance/run.py; resource/evidence limits and UART0 paired capture:docs/web_throughput_diagnostics.md. -
Opt-in admission diagnostics:
src/web_diagnostics.{c,h},tests/web_diagnostics/run.py. Public synchronous HTTPS create/close callbacks publish six post-TLS connection records; four ticket/upgrade wrappers feed a 32-entry numeric ring. UART0/admin SSHweb diagnostics enable|disable|show|clear; no queue/task/cleanup override or capacity change. Full bounds, SDK semantics and preaccept/TLS blind spots:docs/phase8d11_implementation.md. -
Legacy removal user-signed-off 2026-09-08 (unchanged certificate fingerprint, preexisting users usable, full-mix evidence):
user_databasepersists missing storage empty and preserves valid v1 user bytes; private derivedv1_admin_marker, no public bootstrap/migration/sync APIs.web_securityprivately migrates v1 1392-byte material to TLS-only v2 1340-byte material, exact identity/generation retained, commit before publish, fail closed without fallback overwrite. Credential commands removed; user generated passwords and TLS rotation remain. Contracts, downgrade and evidence limits:docs/legacy_credential_removal.md. -
Security files:
src/web_security.{h,c},src/web_cookie_auth.{h,c},src/web_session_store.{h,c},src/web_auth_parse.{h,c}. Private IDF boundary:src/web_httpd_adapter.{h,c}. -
Asset files: authored/generated boundary in
src/web_assets_data.{h,c},web_assets/SOURCES.md,web_assets/generate_embedded_assets.py -
Interfaces: web init/start/stop/snapshots; HTTP handlers; ticket mint/consume; attach/detach; targeted session revocation
-
Called by: startup, ESP-IDF HTTPS server, user administration revocation, console/local UI
-
Dependencies: user database, secure random, broker, successful Wi-Fi manager initialization at boot, mbedTLS/HTTPS server; actual network reachability is an operational prerequisite, not an initializer invariant
-
Flow:
browser -> HTTPS login/cookie session -> CSRF-protected ticket -> cookie/Origin/ticket admission -> WebSocket -> web transport -> broker -
Ownership: HTTPD owns socket send/close work; transport task owns broker mediation; two fixed WebSocket slots and four outstanding tickets.
-
Security constraints: Basic/cache removed; four absolute one-hour cookie sessions revalidate principal currentness. Four pre-login challenges (120 s), five credential attempts/60 s globally, no live session/challenge/ticket eviction. Origin/CSRF required for mutations; Origin/cookie/ticket before upgrade. Disconnect pauses reconnect but retains login; Sign out invalidates its session. Authored loader changes must update their hard-coded CSP hashes atomically.
-
Session-store boundary: admitted HTTPS start initializes records; auth-init failure gates HTTPS. Failed start/accepted stop disables and wipes state. Tickets/slots require nonzero non-reused session IDs; session/account/global revocation invalidates store records before socket cleanup. RNG/SHA/database calls run outside short portMUX sections; ID/expiry/epoch checks reject stale work. Run
python3 tests/web_session_store/run.pyand its--serialintegration mode. -
8D.3 HTTP policy:
web_cookie_authowns public login/challenge/login POST/session/logout routes and protected-route checks;web_auth_parsehandles bounded values/JSON.web_httpd_adapteralone reads private IDF 5.5.0 header scratch, rejects duplicate fields, defers 101 until transport admission and wipes consumed scratch while preserving right-aligned pending bytes. No SDK patch.src/CMakeLists.txtsupplies private includes and compiles HTTPD warning/debug logs out. Test withpython3 tests/web_cookie_auth/run.pyandpython3 tests/web_auth_parse/run.py. -
8D.3 UI:
src/web_login_ui.{c,h}serves standalone/login;web_ui.cvalidates session before serial connect/restore and handles logout/401 safely. Both scripts hash-bound, auth documents/app no-store. Tests:python3 tests/web_login_ui/run.pyandpython3 tests/web_ui_session/run.py. Live cutover host-tested/build-verified, M1 validated by user sign-off (numeric reserves open):docs/phase8d3_implementation.md. -
Asset constraint:
web_assets_data.cis checked-in generated input to the build; do not hand-edit or regenerate casually. -
8D.6 UI:
web_ui.cadds admin-only Serial/Admin selection and explicit admin open/close through existing endpoints. Serial socket/client/lease survives mode switches; hidden output drains into independent 5,000-line/64 KiB-pending terminals with visible browser-drop counts. Selected keyboard only; logout/expiry/pagehide closes both with handler cleanup. Session identity changes require a clean document before adopting the view; same-session restore retains hidden-until-validated buffers. Fit readiness retries are bounded to three and cache only success. Focusedtests/web_ui_session/run.pyhas 17 groups plus toolbar-order/CSP checks. 8D.6 is user-validated; telemetry, evidence limits and 8D.7 handoff are indocs/phase8d6_implementation.md. Numeric reserves remain open; no 8D.7 restriction change. -
8D.8–8D.10 target sign-off (2026-09-08): User reports thorough implemented Serial/account settings tests, supplies settled boot/full-mix telemetry and signs implemented work off. Covers both 8D.10 slices and 8D.9 UX. Supersedes target-pending/exclusion status in historical summaries below; exact scope/evidence/counters/limits:
docs/phase8d10_implementation.md. No unreported checklist passes, reserve approval or M3 completion. Next 8D.11 only on separate request; no source change from sign-off. -
8D.8:
web_ui.cadds admin-only Settings/Serial without socket/lease changes.web_server.cexposes optional admin-only bodylessGET /api/settings/serial, eight working serial values, 256-byte response, no writes/NVS.serial_service_get_snapshot()is a zero-wait consistent config/running copy.web_httpd_register_optional_get()stages both new-route allocations before table publication (installed IDF public registration leaves a dangling descriptor on name-allocation failure); only Settings uses this startup/exact-GET adapter. 17 URI slots, six sockets/no LRU, no new task. Tests: cookie auth--settings(5 groups), UI (21 groups), lifecycle (12 groups). Implemented/build-verified, target/signoff pending; exact accounting and inherited registration-audit followup:docs/phase8d8_implementation.md. M2 remains signed off; no 8D.9. -
8D.9:
web_serial_settings.{c,h}owns strict 256-byte typed mutation admission and one session-bound pending/result slot. Existingadmin_ssh_consoledispatcher consumes only an ID, revalidates currentness/dequeue deadline and calls canonical serial APIs.web_server.cadds optional GET/POST/api/settings/serial-operation(19 handlers total);web_cookie_auth_require_json()retains Origin/CSRF/admin policy, private optional registration supports exact GET/POST. UI adds explicit framing/lifecycle/persistence with automatic completion checks (1 s, at most 10 GETs/15 s overall), refresh on known terminal results and manual uncertainty recovery without socket/lease changes. Settings stay visible/stale while pending; only Reset confirms NVS overwrite; selecting the current view is a no-op./api/statususes a consistent zero-wait serial snapshot (running:nullwhen unavailable). Tests: cookie--serial-settings(10 groups),--settings(6), UI (35 after UX refinement), console boundary and lifecycle (13). Build verified, target/signoff pending; bounds and failure contracts:docs/phase8d9_implementation.md. Supersedes 8D.8's no-8D.9 status above. -
8D.10 first slice:
web_account_settings.{c,h}owns compact admin account list and one session-bound other-account role/delete operation slot.user_database_get_accounts()is a zero-wait key/secret-free projection;*_current()role/delete wrappers compare target ID/auth generation under the canonical mutation lock. Existing dispatcher routes IDs; successful calls target-revoke web/SSH. Optional GET/api/settings/accounts, GET/POST/api/settings/account-operationraise handlers to 22, sockets/tasks/stacks/queue depth unchanged. UI Accounts subview preserves terminal/lease semantics, confirms mutations and auto-checks/refreshes with manual uncertainty recovery. Tests: cookie--accounts(5), canonical accounts, dispatcher, lifecycle (14), UI (41 + CSP). Target pending; create/password/generated-secret/self changes remain next slice, 8D.10 incomplete. Record:docs/phase8d10_implementation.md. -
Current 8D.10 slice 2 (supersedes first-slice exclusions above):
web_account_settings.{c,h}adds create/password/self and separate bodyless POST/api/settings/accounts/generate-password;user_database_set_password_current()shares mutation-lock target checks and canonical commit logic,user_database_generate_password_value()generates without mutation. 768-byte/four-receive admission, 96-byte secret-free results; one-second periodic timer cancels/wipes queued non-executing credentials after 30 seconds plus scheduling latency, while dispatcher wipes executing locals on return. Generation has no retained retrieval; UI uses 60-second context-bound acknowledgement before separate submission. Self revocation can deny result retrieval; 401/disconnect is uncertain. Browser-shell restrictions unchanged. Missing generated-route registration found in review is fixed: independent optional endpoint, 23 handlers, failure isolation/restart coverage. Implementation complete, host-tested/build-verified; target/signoff pending. Parent PASS canonical accounts/boundary, parser 294, cookie accounts 9/shared and serial-settings 10, transport 25/tickets 12, store/serial and diff check; UI agent PASS 57/CSP, route agent lifecycle 15. Parent build 25.61 s, 95,908 B RAM / 1,694,237 B flash (+80/+9,880 vs slice 1; +200/+25,400 vs final 8D.9 UX). Timer runtime costs/stack margins remain unmeasured; no 8D.11. Exact evidence attribution:docs/phase8d10_implementation.md.
Browser admin backend (8D.5)
-
8D.7 current status (2026-09-07): implemented scope validated; M2 explicitly signed off by the user ("Jupp, sign M2 off"). Supersedes M2-open/target-pending/continuation statements in the historical slices below; accepted M2 does not require revalidation. User verified certificate rotation and web start/stop via UART0/SSH admin/web admin, restarting after browser stop via another route; full mix without broker drops up to 230400 baud after external adapter correction is user-reported. Intermittent supported two serial + one admin admission failures, recently not recurring, are accepted nonblocking, not fixed. Browser self/generated/key/legacy-credential and other owner command restrictions remain deferred; bootstrap/recovery remain permanently UART0-only. Numeric memory reserves/stack margins remain unapproved; no full parity or individual unreported checklist passes. Next: separately requested 8D.8 read-only settings entry and Serial page; sign-off alone authorizes no implementation. Evidence:
docs/phase8d7_implementation.md. -
8D.7 third account slice:
admin_ssh_consoleshares parsed browser other-account policy withuser_console; interactive add/password and forced delete/role now allowed, self/generated/key/bootstrap/recovery still blocked. Post-prompt/pre-DB-API currentness is operation admission, not cancellation of admitted derivation/commit. Existing target-only notifications follow success. Review has no actionable findings;python3 tests/admin_console_boundary/accounts.pyadds deterministic handler/database failure and stale-next-operation regressions. Target/M2 pending; seedocs/phase8d7_implementation.md. -
8D.7 second slice: exact parsed browser
web certificate rotate --force;admin_ssh_console.{c,h}supplies the typed request union/ownerdispatcher_actionsmask, bounded drain/200 ms handoff to the existing 12 KiB dispatcher, persistent pending gate and revalidated executing-slot reservation.web_console.cschedules;web_admin_transport.crevalidates then calls transactionalweb_security_rotate_certificate()→web_server_stop()→web_server_start(), short-circuiting errors and retaining ownership on failed stop. SSH/UART0 unchanged. No tasks/depth/routes/assets/stacks added; target stack margins unknown. Boundaryrun.pyincludescertificate.c; lifecycle/policy and transport 25/tickets 12 host groups pass as reported. Credential/account then other owner slices remain; user authorized stacking, not target/M2 sign-off. Seedocs/phase8d7_implementation.md. -
8D.7 first-slice history: browser
reboot/web stopdefer viaadmin_ssh_consolecontrol task; WEB owner revalidates cookie/principal/token before lifecycle calls. Pending console input is discarded (incoming-frame disposition latched before receive).web_console.cdefers stop only for browser origin; other restrictions remain. Tests additionally includepython3 tests/admin_console_boundary/lifecycle.py; handoff:docs/phase8d7_implementation.md. No 8D.7/M2 acceptance yet. -
Files:
src/web_admin_transport.{c,h},src/web_admin_tickets.{c,h}, protected registration/lifecycle inweb_server.c, revocation throughweb_serial_transport_revoke_*, diagnostics inweb_console.c. -
Routes: CSRF-protected admin-only
POST /api/admin/ws-ticket; ordinaryGET /ws/adminwith cookie/Origin/ticket/shared-console admission before explicit 101. No UI entry or broker client. One socket, two tickets, existing two shared console slots; six total HTTPD sockets, LRU disabled, 16 URI handlers. -
Ownership: 20 ms ESP timer queues at most one HTTPD poll, no new task; HTTPD owns 1,552 B PSRAM-only payload and IO. Closure uses HTTPD-owned
shutdown, not IDF's reusable-pointer queued close. Detach fences submitters; only successful HTTPD stop retires queued state before restart. Session/principal currentness and generation checks protect all sensitive boundaries. -
Tests:
python3 tests/web_admin_transport/run.py --tickets,python3 tests/web_admin_transport/server_lifecycle.py,python3 tests/web_cookie_auth/run.py --admin; manual smoke client/procedure intests/web_admin_transport/README.mdanddocs/phase8d5_implementation.md. Final shutdown fix is host-tested and build-verified by the parent's sequential finalpio run; target validation remains pending.
SSH
- 8D.19 first service slice:
web_ssh_settings.{c,h}adds optional admin-only GET/api/settings/ssh, GET/POST/api/settings/ssh-operation; existing dispatcher queues only IDs to one login-bound slot.ssh_transport_get_management_snapshot()copies published state without owner wait/stack scan;ssh_transport_manage_current()checks saturated service generation under existing command mutex and exact session ID under SSH lock before canonical lifecycle/external-close admission. Exhausted session slots retire instead of wrapping.web_ui.cadds confirmed SSH-only Settings, sticky stale selection,15-second requests/manual Check Result/Refresh.36 handlers/six sockets/no new tasks/timers/depth/stacks/assets; CPU160 and8D.18 preserved. Teststests/ssh_management/run.py, cookie--ssh, dispatcher, lifecycle27 and UI143. Contracts/resources/remaining8D.19 service audit/target checks:docs/phase8d19_implementation.md. SSH slice implemented/host/build verified; parent review/target sign-off pending, not full8D.19.
Responsibility: authenticate SSH, route users to serial and administrators to the command dispatcher, and own wolfSSH lifecycle.
- Files:
src/ssh_transport.{h,c},src/ssh_security.{h,c},src/ssh_console.{h,c} - Interfaces: init/start/stop, session snapshots/disconnect/revocation, host-key replacement, counters
- Called by: startup, network clients, user revocation, console/local UI
- Dependencies: user database, broker, admin SSH console, secure random, wolfSSH/wolfSSL; boot start gate requires Wi-Fi and SSH security/runtime readiness, independently of HTTPS identity readiness (verified in
main.cafter accepted legacy cleanup). - Flow: role
user-> broker; roleadmin->admin_ssh_console - Ownership: after caller-side library initialization, one task pinned to core 1 owns runtime wolfSSH contexts/sessions; two fixed generation-tagged slots.
- Security constraint: an interactive shell request is required; exec and subsystems are rejected, and no project file-transfer or forwarding route exists. PTY is not explicitly required.
Users, authentication, and authorization
Responsibility: persist bounded accounts, verify passwords/SSH keys, issue secret-free principals, and enforce account invariants.
- Files:
src/user_database.{h,c},src/user_console.{h,c};src/admin_command_gate.{h,c}is currently a narrow recursive wrapper used only by theusercommand handler, not the global command serializer - Interfaces: credential-independent init/empty recovery, authenticate, principal-currentness, account/password/role/key mutations, snapshots
- Called by: web and SSH authentication/currentness checks and console administration
- Dependencies: NVS, secure random, mbedTLS cryptography; after a committed command-layer mutation, best-effort web/SSH revocation calls supplement authoritative transport currentness checks
- Ownership: database mutex protects the internal live record and PSRAM-preferred transactional candidate; password authentication runs PBKDF2 outside the mutex and revalidates afterward, while mutation locking must be checked per operation.
- Authorization: UART0 establishes the first administrator through normal
user addand exclusively owns unavailable-database recovery to empty (healthy database refused); current admins may use admin SSH for other commands unless handler policy denies them. HTTPS serial/status permits both roles; administration requiresadmin. - Constraint: final administrator cannot be deleted or demoted; transport principals must be rechecked after mutations.
Administration console infrastructure
Responsibility: provide one canonical command registry and serialized execution for UART0 and admin SSH.
- Files:
src/admin_ssh_console.{h,c},src/console_input.{h,c},src/console_completion.{h,c},src/system_console.{h,c},src/network_console.{h,c}and all*_console.{h,c}modules - Entry points:
admin_ssh_console_init(),admin_ssh_console_start_uart_frontend(), command registration functions - Called by: startup, UART0 frontend, role-
adminSSH transport - Dependencies: ESP-IDF console/linenoise, all command handlers, user-principal currentness
- Flow:
UART0/admin SSH -> bounded request queue -> one dispatcher -> esp_console_run() - Ownership: dispatcher is sole
esp_console_run()caller; the SSH owner exclusively performs post-initialization wolfSSH runtime calls. - Lifecycle: remote session tokens include slot generation; fixed output/history/prompt state is wiped immediately on idle close or after an executing handler returns. Admin SSH
exitand Ctrl+D on an empty command line request bounded deferred self-disconnect after best-effort output draining. - Constraint: one slow command or prompt serializes all administration. Admin SSH is unavailable until command registration and UART frontend creation complete; supported deferred actions wait only for a bounded application-buffer drain heuristic.
- 8D.4/8D.5 boundary:
admin_ssh_console_open_owned()retains explicit-index admission; runtime SSH and browser owners useadmin_ssh_console_open_available()for the same two slots. Copied transport-qualified identity and immutable firmware-lifetime currentness/drain/lifecycle adapters; SSH publishes its allocated console index separately from its physical SSH slot. Owners handle liveness/output; dispatcher and prompt waits additionally require owner currentness (250 ms polling plus check/scheduling latency). SSH publishes locked principal copies; consumed console output is wiped. Completion scratch is nonblockingly serialized. Browser unsupported lifecycle/account mutations are rejected before execution. Focused host command:python3 tests/admin_console_boundary/run.py.
Wi-Fi
Responsibility: persist station/AP policy and own asynchronous ESP-NETIF/Wi-Fi state transitions.
- Files:
src/wifi_config.{h,c},src/wifi_manager.{h,c},src/wifi_console.{h,c},src/mdns_config.{h,c},src/mdns_service.{h,c},src/mdns_console.{h,c},src/network_console.{h,c} - Interfaces: config defaults/validate/load/save; manager init/start/stop/apply/reconnect/next-profile/snapshot
- Called by: startup, console, local UI, ESP event callbacks; typed Network settings uses secret-free zero-wait projections and dispatcher-owned canonical conditional mutations (8D.12/8D.13).
- Dependencies: secure random for default AP password, NVS, ESP-NETIF/Wi-Fi/events, Espressif mDNS, lwIP diagnostics
- Lifecycle: permanent manager task and bounded queue; callbacks enqueue compact events only.
- Constraint: application NVS is authoritative (
WIFI_STORAGE_RAM); working edits are not persisted until save. Start/stop, including local controls, intentionally update the RAMenabled_at_bootfield. Working-config copies contain PSKs and must be tightly scoped and wiped; routine status/local UI must use secret-free snapshots.
Local display and controls
Responsibility: own OLED I2C/framebuffer operations and present status plus constrained button actions.
- Files:
src/local_display.{h,c},src/local_status_ui.{h,c},src/local_boot_animation.{h,c},src/local_ui_config.{h,c},src/local_ui_console.{h,c} - Interfaces: display init/frame/draw/commit/snapshot; UI start/activity/config; generation-checked settings projection/update and explicit persistence reservation; versioned NVS settings
- Called by: startup, local UI task, diagnostics, display console
- Dependencies: copied snapshots/public APIs from serial, broker, USB, Wi-Fi, web, SSH
- Ownership:
local_displaysolely owns I2C0 and framebuffer mutex; a frame belongs to its initiating task. - Lifecycle: the low-priority task is firmware-lifetime only if button GPIO initialization succeeds; it still runs with an absent panel so a press can reprobe after successful I2C bus setup. Failed bus creation is not recoverable by that reprobe, and
displayconfiguration commands depend on the UI task. - Constraint: collect service snapshots before I2C; local UI never joins broker or handles secrets. All configuration writers honor the UI owner's zero-wait reservation; NVS runs outside timing critical sections. Reset commits defaults before RAM publication, including CLI; buttons/diagnostic holds update activity, not configuration generation.
Hardware and diagnostics
Responsibility: centralize board wiring and provide bounded electrical tests with safe cleanup.
- Files:
src/board_pins.h,src/rs232_hw_test.{h,c},src/local_ui_hw_test.{h,c},src/status_led.{h,c} - Documentation:
docs/wiring.md,docs/electrical_tests.md - Called by: startup and
debugcommands - Dependencies: physical RS-232 owner, serial/display services, ESP-IDF GPIO/UART/I2C/LED drivers
- Ownership: RS-232 diagnostics refuse to run while the service owns the port; display diagnostics reuse
local_display. - Constraint: wiring and voltage assumptions are safety-relevant; verify target hardware before running diagnostics. RGB LED initialization is currently boot-fatal, and its colors report diagnostic state rather than aggregate firmware health.
Where should I look?
| Task | Start here |
|---|---|
| Change boot order or failure behavior | src/main.c, then affected subsystem init/start contracts |
| Change serial framing, flow control, or persistence | serial_config.*, serial_service.*, serial_console.* |
| Change writer/observer policy | session_broker.*, then all three transports |
| Debug missing or duplicated serial bytes | serial_service.c -> session_broker.c -> relevant transport task |
| Change USB open/DTR or line coding | usb_cdc_transport.* |
| Change browser terminal protocol | web_serial_transport.*, web_ui.c, web_server.c |
| Change HTTPS endpoints/authentication | web_server.*, web_security.*, user_database.* |
| Change SSH login or role routing | ssh_transport.*, ssh_security.*, user_database.* |
| Add or change a command | relevant *_console.c, console_completion.c, admin_ssh_console.c policy/deferred handling |
| Change account roles/passwords/keys | user_database.*, user_console.c, transport revocation APIs |
| Change Wi-Fi policy or profile persistence | wifi_manager.*, wifi_config.*, wifi_console.c |
| Change station mDNS hostname or persistence | mdns_service.*, mdns_config.*, mdns_console.c, then wifi_manager.c |
| Change OLED rendering or buttons | local_status_ui.c, local_display.*, local_ui_config.* |
| Change board GPIO or electrical tests | board_pins.h, hardware test module, docs/wiring.md |
| Change embedded browser assets | web_assets/SOURCES.md, generator, then generated data only as an explicit regeneration task |
| Investigate memory/watchdog regressions | broker/web/SSH bounded loops, allocation placement, root CMakeLists.txt, relevant roadmap Phase 6 history |