- Require current admin cookie sessions, Origin checks and single-use tickets - Reuse the shared console with session-aware authorization and slot allocation - Add HTTPD-owned I/O, bounded buffering and revocation cleanup - Prevent LRU eviction of serial clients and stale admin socket closure - Reject unsupported web-shell mutations before side effects - Add host regressions, a smoke client and resource accounting Validated by user sign-off after a 15-minute full-client soak at 230400 baud, with a few broker drops under heavy output. Browser UI remains for Phase 8D.6; numeric memory reserves remain open.
5.2 KiB
Admin ticket store host checks
Run from the repository root:
python3 tests/web_admin_tickets/run.py
python3 tests/web_admin_tickets/run.py --sanitize
Requires a C11 cc, Python 3, OpenSSL development headers/libcrypto, and (for
--sanitize) ASan/UBSan runtimes. No firmware build, network, generated assets,
or persistent build output. The runner reuses the session-store harness's tiny
platform header fakes. test.c includes the unmodified production C, using
real project principal/session declarations and OpenSSL SHA-256. Inclusion gives
white-box access for wipe, saturation and exhaustion assertions without adding
production test hooks. RNG, time and session validation are deterministic fakes;
all external calls assert that the ticket critical section is not held.
Exact test groups
- Stopped/start/idempotent-start lifecycle; 64 hex output; SHA-256 digest-only storage; success, replay denial and full record wipe.
- Two-ticket capacity and no live eviction; exact counters; nested competing issuance takes the last slot and the losing output is wiped.
- Issue rejects user role, public-key method, mismatched generation, zero ID, NULL principal/output, stale sessions and session-check errors.
- Consume burns matches before denying wrong session, user role, public-key method, generation, stale/check-error, zero ID or NULL principal; also rejects a different session with the same account principal.
- Empty/NULL/short/long/nonhex input; uppercase hex consumes the same secret.
- Success one microsecond before expiry; rejection at expiry; stale reclaim on issue/snapshot; snapshot expiry cleanup; signed deadline overflow rejection.
- Revocation ID precedence, exact username length/name and global scope; revocation never invalidates the fake sessions.
- RNG/SHA failures, failed output wipe, consume SHA failure leaves the unidentifiable ticket intact, duplicate live RNG/digest rejection.
- Issuance RNG/SHA hooks exercise stop/restart, global and nonmatching revoke, and session invalidation; currentness hook exercises stop/restart.
- Consume SHA/postcheck hooks exercise stop/restart, global/nonmatching revoke, stale sessions and expiry; nested competing consumes admit exactly once.
- Prune check races replacement with the same ID, digest and deadline; the non-reused record generation protects the replacement from stale cleanup.
- Nonwrapping epoch and record generation exhaustion, permanent lifecycle failure at exhaustion, saturated counters, NULL/count-only snapshots and host structure sizes.
Contract and limits
The public API is in src/web_admin_tickets.h. This module is inert until wired
by a later integration increment. It adds no routes, session invalidation,
transport, task, socket, queue, timer or heap allocation. Callers must authorize
HTTP cookie/Origin/CSRF, invalidate the authoritative session store before
calling revoke, wipe successful token outputs and recheck currentness at later
sensitive boundaries. A successful consume is not an authorization lease.
Two tickets, 32 RNG bytes each, 64 hex characters plus NUL, absolute 30-second lifetime. Only SHA-256 of decoded secret bytes is retained with copied principal, session ID, deadline and unique generation. Both hex cases are accepted. Live digest collisions fail rather than creating ambiguous tickets. No retry loop or live eviction. Pruning checks at most two copied records per invocation. Every revoke advances the epoch even if no record matches, conservatively cancelling unrelated in-flight issue/consume work. Start is idempotent while ready. Stop/start never resets counters, epoch or record generation.
issued counts published tickets, consumed counts burned matches (including
subsequently denied admissions), rejected counts failed issue/consume calls;
capacity_rejections is a subset of rejected. All counters saturate at UINT32_MAX.
Snapshot prunes expired/stale records and exports counts, readiness and storage
size only. A capacity failure is ESP_ERR_NO_MEM; malformed input INVALID_ARG;
unauthorized/stale/lifecycle-raced work INVALID_STATE; no live consume match
NOT_FOUND; SHA failure ESP_FAIL; RNG errors propagate. Failed issue wipes all 65
output bytes when output is non-NULL. Output must not alias inputs.
Host measured sizes: ticket 104 B, two-ticket state 248 B, fake lock 4 B, snapshot
40 B; snapshot storage_bytes = 252 B. Estimated 32-bit target sizes: ticket
96 B, state 232 B, plus the target portMUX (typically 8 B), roughly 240 B static
RAM. These are estimates, not target linker measurements. Issue plus nested
prune has 240 B of explicit ticket/random local payload on this host (about
224 B on a 32-bit target), excluding scalar/compiler frames and session/RNG/SHA
call stacks; caller also owns a 65 B token. No measured target stack/flash delta.
Hooks test deterministic interleavings, not true multicore scheduling or IDF portMUX semantics. They do not validate the real DRBG, mbedTLS, session database, HTTP admission, hardware, or full Phase 8D.5 integration. Post-check account changes without notification are subject to the same no-lease boundary as the session API. Combined hardware validation remains pending; no firmware build or device operation is part of this increment.