Add Bounded Web Admission Diagnostics
This commit is contained in:
@@ -43,6 +43,7 @@ static void esp_restart(void) { ++reboots; }
|
||||
static void vTaskDelay(unsigned delay) { assert(delay==100); ++waits; }
|
||||
#define pdMS_TO_TICKS(ms) (ms)
|
||||
static void print_usage(void) { assert(false); }
|
||||
static int web_diagnostics_command(const char *action) { assert(!strcmp(action, "show")); return 0; }
|
||||
static int show_status(void) { assert(false); return 1; }
|
||||
static int show_counters(void) { assert(false); return 1; }
|
||||
static int show_credentials(void) { assert(false); return 1; }
|
||||
@@ -56,6 +57,8 @@ static bool force_is_present(int argc, char **argv, int expected) {
|
||||
'''
|
||||
tests = r'''
|
||||
int main(void) {
|
||||
char *diagnostics[]={"web", "diagnostics", "show"};
|
||||
assert(command_web(3, diagnostics)==0 && !stops && !scheduled);
|
||||
char *stop[]={"web", "stop"};
|
||||
remote=web=true;
|
||||
assert(command_web(2,stop)==0 && scheduled==1 && !stops && last_action==ADMIN_CONSOLE_DEFER_WEB_STOP);
|
||||
|
||||
@@ -72,6 +72,7 @@ int main(void) {
|
||||
};
|
||||
const char *web_denied[] = {
|
||||
"web", "web help", "web start", "web stop extra", "web counters", "web clear-counters",
|
||||
"web diagnostics enable", "web diagnostics disable", "web diagnostics show", "web diagnostics clear",
|
||||
"web credentials show", "web credentials rotate --force", "web certificate info",
|
||||
"web certificate rotate", "web certificate rotate --force extra",
|
||||
"web certificate rotate --force --force", "web certificate rotate --Force",
|
||||
|
||||
@@ -79,6 +79,7 @@ typedef struct {
|
||||
const uint8_t *servercert, *prvtkey_pem;
|
||||
size_t servercert_len, prvtkey_len;
|
||||
unsigned port_secure, tls_handshake_timeout_ms;
|
||||
void (*user_cb)(void *);
|
||||
} httpd_ssl_config_t;
|
||||
/* Nonproduction defaults deliberately make explicit overrides observable. */
|
||||
#define HTTPD_SSL_CONFIG_DEFAULT() ((httpd_ssl_config_t){.httpd = {.max_open_sockets = 1, .lru_purge_enable = true}})
|
||||
@@ -104,9 +105,10 @@ static void xSemaphoreTake(SemaphoreHandle_t m, int wait) { (void)wait; assert(m
|
||||
static void xSemaphoreGive(SemaphoreHandle_t m) { assert(m && locked); locked = 0; }
|
||||
static void secure_wipe(void *p, size_t n) { assert(!locked); memset(p, 0, n); }
|
||||
#define HANDLER(name) static esp_err_t name(httpd_req_t *r) { (void)r; assert(!"HTTP handler must not run in lifecycle harness"); return ESP_FAIL; }
|
||||
HANDLER(root_handler) HANDLER(status_handler) HANDLER(ticket_handler)
|
||||
HANDLER(websocket_handler) HANDLER(asset_handler) HANDLER(web_cookie_auth_handler)
|
||||
HANDLER(web_admin_transport_ticket_handler) HANDLER(web_admin_transport_upgrade_handler)
|
||||
HANDLER(root_handler) HANDLER(status_handler) HANDLER(traced_ticket_handler)
|
||||
HANDLER(traced_websocket_handler) HANDLER(asset_handler) HANDLER(web_cookie_auth_handler)
|
||||
HANDLER(traced_admin_ticket_handler) HANDLER(traced_admin_upgrade_handler)
|
||||
static void web_diagnostics_tls(void *arg) { (void)arg; assert(false); }
|
||||
HANDLER(serial_settings_handler)
|
||||
HANDLER(web_serial_settings_handler) HANDLER(web_account_settings_handler)
|
||||
HANDLER(web_account_generate_password_handler) HANDLER(web_account_keys_handler)
|
||||
@@ -129,6 +131,7 @@ static esp_err_t httpd_ssl_start(httpd_handle_t *server, const httpd_ssl_config_
|
||||
assert(config->httpd.max_uri_handlers == 24 && config->port_secure == 443);
|
||||
assert(config->httpd.recv_wait_timeout == 1 && config->httpd.send_wait_timeout == 1);
|
||||
assert(config->tls_handshake_timeout_ms == 5000);
|
||||
assert(config->user_cb == web_diagnostics_tls);
|
||||
assert(config->servercert_len == 1 && config->servercert[0] == 1);
|
||||
assert(config->prvtkey_len == 1 && config->prvtkey_pem[0] == 2);
|
||||
if (ssl_start_error != ESP_OK) return ssl_start_error;
|
||||
@@ -297,8 +300,8 @@ int main(void) {
|
||||
assert(route("/api/settings/serial")->handler == serial_settings_handler);
|
||||
assert(keys_calls == 1 && route("/api/settings/accounts/keys")->handler == web_account_keys_handler);
|
||||
const httpd_uri_t *ticket = route("/api/admin/ws-ticket"), *ws = route("/ws/admin");
|
||||
assert(ticket->method == HTTP_POST && ticket->handler == web_admin_transport_ticket_handler && !ticket->is_websocket);
|
||||
assert(ws->method == HTTP_GET && ws->handler == web_admin_transport_upgrade_handler && !ws->is_websocket);
|
||||
assert(ticket->method == HTTP_POST && ticket->handler == traced_admin_ticket_handler && !ticket->is_websocket);
|
||||
assert(ws->method == HTTP_GET && ws->handler == traced_admin_upgrade_handler && !ws->is_websocket);
|
||||
assert(route("/ws/serial")->method == HTTP_GET && !route("/ws/serial")->is_websocket);
|
||||
assert(route("/api/login")->method == HTTP_POST && route("/api/session")->method == HTTP_GET);
|
||||
assert(web_server_start() == ESP_ERR_INVALID_STATE && auth_starts == 1 && ssl_starts == 1);
|
||||
@@ -349,7 +352,7 @@ int main(void) {
|
||||
assert(registered_count == 22 && unregister_calls == failure - 17);
|
||||
for (unsigned i = 0; i < registered_count; ++i)
|
||||
assert(strcmp(registered[i]->uri, "/api/admin/ws-ticket") && strcmp(registered[i]->uri, "/ws/admin"));
|
||||
assert(route("/ws/serial")->handler == websocket_handler);
|
||||
assert(route("/ws/serial")->handler == traced_websocket_handler);
|
||||
assert(route("/api/session")->handler == web_cookie_auth_handler);
|
||||
assert(web_server_start() == ESP_ERR_INVALID_STATE && ssl_starts == 1);
|
||||
clear_events(); assert(web_server_stop() == ESP_OK && !strcmp(events, "ASH"));
|
||||
@@ -366,7 +369,7 @@ int main(void) {
|
||||
assert(!admin_inits && !admin_attaches && !admin_owned && !s_admin_transport_owned);
|
||||
ticket = route("/api/admin/ws-ticket");
|
||||
assert(ticket->method == HTTP_POST && !ticket->is_websocket &&
|
||||
ticket->handler == web_admin_transport_ticket_handler);
|
||||
ticket->handler == traced_admin_ticket_handler);
|
||||
for (unsigned i = 0; i < registered_count; ++i) assert(strcmp(registered[i]->uri, "/ws/admin"));
|
||||
/* Handler identity is checked, not its authentication implementation (doubled). */
|
||||
assert(!auth_stops && !ssl_stops && !s_transitioning && s_last_error == ESP_OK);
|
||||
@@ -451,8 +454,8 @@ int main(void) {
|
||||
assert(route("/api/settings/accounts")->handler == web_account_settings_handler);
|
||||
assert(route("/api/settings/accounts/generate-password")->handler == web_account_generate_password_handler);
|
||||
assert(route("/api/session")->handler == web_cookie_auth_handler);
|
||||
assert(route("/ws/serial")->handler == websocket_handler);
|
||||
assert(route("/ws/admin")->handler == web_admin_transport_upgrade_handler);
|
||||
assert(route("/ws/serial")->handler == traced_websocket_handler);
|
||||
assert(route("/ws/admin")->handler == traced_admin_upgrade_handler);
|
||||
account_mutations = 0;
|
||||
for (unsigned i = 0; i < registered_count; ++i) {
|
||||
assert(strcmp(registered[i]->uri, "/api/settings/accounts/keys"));
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# Web admission diagnostics host tests
|
||||
|
||||
Run from the repository root:
|
||||
|
||||
```sh
|
||||
python3 tests/web_diagnostics/run.py
|
||||
python3 tests/web_admin_transport/server_lifecycle.py
|
||||
python3 tests/admin_console_boundary/lifecycle.py
|
||||
python3 tests/admin_ssh_policy/run.py
|
||||
```
|
||||
|
||||
`run.py` compiles the entire production `web_diagnostics.c` (only includes are
|
||||
substituted) plus all four actual server tracing wrappers, with strict C11
|
||||
warnings. `CC` selects the compiler. No dependencies beyond Python 3 and a C
|
||||
compiler; no device/network/build actions or persistent generated files.
|
||||
|
||||
Twelve runtime groups cover disabled capture, existing live connections,
|
||||
handler return/route preservation, WS classification, heap/stack field mapping,
|
||||
six slots and duplicate/overflow callbacks, stale TLS identity/fd reuse,
|
||||
capture-epoch interleavings, exact ring overwrite accounting, formatted-output
|
||||
secrecy with poisoned URI/header/body fields, bounded concurrent show/clear,
|
||||
stale upgrade completion, partial stop/restart, invalid input, and nonwrapping
|
||||
IDs/saturating counters. One additional source guard group checks production
|
||||
wiring and absence of request/logging/allocation/task/queue APIs. Fakes assert
|
||||
that printing, heap sampling and public HTTPD/TLS calls never run under the
|
||||
metadata lock.
|
||||
|
||||
These are deterministic injected interleavings, not a real FreeRTOS concurrency
|
||||
or TLS/socket simulation. The restart test models synchronous callbacks; the
|
||||
separate lifecycle harness executes real server orchestration with dependency
|
||||
fakes and verifies callback configuration, six sockets/no LRU, unchanged
|
||||
timeouts and all 16 existing failure/restart groups. Canonical CLI routing and
|
||||
SSH/browser policy have separate tests. No target heap, stack margin, admission
|
||||
reliability or secrecy of external SDK logging is proven by these tests.
|
||||
|
||||
Usage, exact SDK cleanup audit, resource accounting, and target checklist:
|
||||
`docs/phase8d11_implementation.md`, “Authorized admission diagnostic slice”.
|
||||
@@ -0,0 +1,69 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-only */
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef int esp_err_t;
|
||||
enum { ESP_OK = 0, ESP_FAIL = -1 };
|
||||
typedef struct { int fd; bool invalid; } esp_tls_t;
|
||||
typedef struct { unsigned user_cb_state; esp_tls_t *tls; } esp_https_server_user_cb_arg_t;
|
||||
enum { HTTPD_SSL_USER_CB_SESS_CREATE, HTTPD_SSL_USER_CB_SESS_CLOSE };
|
||||
typedef struct { void *handle; int fd; const char *uri, *headers, *body; } httpd_req_t;
|
||||
enum { HTTPD_WS_CLIENT_HTTP, HTTPD_WS_CLIENT_WEBSOCKET };
|
||||
#define MALLOC_CAP_INTERNAL 1U
|
||||
#define MALLOC_CAP_8BIT 2U
|
||||
#define MALLOC_CAP_DMA 4U
|
||||
#define MALLOC_CAP_SPIRAM 8U
|
||||
#define portMUX_TYPE int
|
||||
#define portMUX_INITIALIZER_UNLOCKED 0
|
||||
static bool locked, upgraded;
|
||||
static unsigned scans, calls, ws_queries;
|
||||
static int64_t clock_us = 1000;
|
||||
static void (*during_scan)(void), (*during_handler)(void), (*during_print)(void);
|
||||
static void lock(int *m) { (void)m; assert(!locked); locked = true; }
|
||||
static void unlock(int *m) { (void)m; assert(locked); locked = false; }
|
||||
#define portENTER_CRITICAL(m) lock(m)
|
||||
#define portEXIT_CRITICAL(m) unlock(m)
|
||||
static int64_t esp_timer_get_time(void) { assert(!locked); return clock_us; }
|
||||
static uint32_t heap_caps_get_free_size(uint32_t caps) {
|
||||
assert(!locked); ++scans;
|
||||
if (during_scan) { void (*hook)(void) = during_scan; during_scan = NULL; hook(); }
|
||||
return 10000 + caps;
|
||||
}
|
||||
static uint32_t heap_caps_get_largest_free_block(uint32_t caps) { assert(!locked); return 5000 + caps; }
|
||||
static uint32_t uxTaskGetStackHighWaterMark(void *task) { assert(!locked && !task); return 1234; }
|
||||
static esp_err_t esp_tls_get_conn_sockfd(esp_tls_t *tls, int *fd) {
|
||||
assert(!locked); *fd = tls->fd; return tls->invalid ? ESP_FAIL : ESP_OK;
|
||||
}
|
||||
static int httpd_req_to_sockfd(httpd_req_t *r) { assert(!locked); return r->fd; }
|
||||
static int httpd_ws_get_fd_info(void *handle, int fd) {
|
||||
assert(!locked && handle && fd >= 0); ++ws_queries;
|
||||
return upgraded ? HTTPD_WS_CLIENT_WEBSOCKET : HTTPD_WS_CLIENT_HTTP;
|
||||
}
|
||||
static esp_err_t handler_result;
|
||||
static esp_err_t handler(httpd_req_t *r) {
|
||||
assert(!locked && r->handle); ++calls; clock_us += 75;
|
||||
if (during_handler) { void (*hook)(void) = during_handler; during_handler = NULL; hook(); }
|
||||
return handler_result;
|
||||
}
|
||||
static esp_err_t ticket_handler(httpd_req_t *r) { return handler(r); }
|
||||
static esp_err_t websocket_handler(httpd_req_t *r) { return handler(r); }
|
||||
static esp_err_t web_admin_transport_ticket_handler(httpd_req_t *r) { return handler(r); }
|
||||
static esp_err_t web_admin_transport_upgrade_handler(httpd_req_t *r) { return handler(r); }
|
||||
static char output[65536];
|
||||
static size_t output_size;
|
||||
static int capture_printf(const char *fmt, ...) {
|
||||
assert(!locked);
|
||||
va_list ap; va_start(ap, fmt);
|
||||
int count = vsnprintf(output + output_size, sizeof(output) - output_size, fmt, ap);
|
||||
va_end(ap);
|
||||
assert(count >= 0 && (size_t)count < sizeof(output) - output_size);
|
||||
output_size += count;
|
||||
if (during_print) { void (*hook)(void) = during_print; during_print = NULL; hook(); }
|
||||
return count;
|
||||
}
|
||||
#define printf capture_printf
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compile the entire production diagnostic module with deterministic public-API fakes.
|
||||
|
||||
No TLS, network, scheduler, hardware or real heap/stack measurement is simulated.
|
||||
Lock assertions and injected interleavings test the bounded publication contract.
|
||||
"""
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
HERE = Path(__file__).resolve().parent
|
||||
|
||||
def without_includes(path):
|
||||
return '\n'.join(line for line in path.read_text().splitlines()
|
||||
if not line.startswith(('#include', '#pragma once')))
|
||||
|
||||
server = (ROOT / 'src/web_server.c').read_text()
|
||||
wrappers = []
|
||||
for name in ('traced_ticket_handler', 'traced_websocket_handler',
|
||||
'traced_admin_ticket_handler', 'traced_admin_upgrade_handler'):
|
||||
match = re.search(r'static esp_err_t ' + name + r'\(httpd_req_t \*request\)\n\{.*?\n\}', server, re.S)
|
||||
assert match, name
|
||||
wrappers.append(match.group())
|
||||
# This slice must not take over cleanup, add async probes, or enable SDK logging.
|
||||
assert 'config.user_cb = web_diagnostics_tls;' in server
|
||||
assert 'config.httpd.close_fn' not in server and 'config.httpd.open_fn' not in server
|
||||
source = (ROOT / 'src/web_diagnostics.c').read_text()
|
||||
for forbidden in ('httpd_queue_work', 'httpd_get_client_list', 'esp_event_handler_register',
|
||||
'httpd_req_get_', 'request->uri', 'request->user_ctx', 'request->sess_ctx',
|
||||
'malloc(', 'calloc(', 'xTaskCreate', 'ESP_LOG', 'esp_log_level_set'):
|
||||
assert forbidden not in source, forbidden
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix='web-diagnostics-') as directory:
|
||||
directory = Path(directory)
|
||||
unit = directory / 'test.c'
|
||||
unit.write_text((HERE / 'fakes.h').read_text() + '\n' +
|
||||
without_includes(ROOT / 'src/web_diagnostics.h') + '\n' +
|
||||
without_includes(ROOT / 'src/web_diagnostics.c') + '\n' +
|
||||
'\n'.join(wrappers) + '\n' + (HERE / 'test.c').read_text())
|
||||
executable = directory / 'test'
|
||||
subprocess.run([os.environ.get('CC', 'cc'), '-std=c11', '-Wall', '-Wextra', '-Werror',
|
||||
str(unit), '-o', str(executable)], check=True)
|
||||
subprocess.run([str(executable)], check=True)
|
||||
print('PASS: production integration/secrecy source guards (1 group)')
|
||||
@@ -0,0 +1,157 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-only */
|
||||
static void notify(esp_tls_t *tls, unsigned state)
|
||||
{
|
||||
esp_https_server_user_cb_arg_t arg = {.tls = tls, .user_cb_state = state};
|
||||
web_diagnostics_tls(&arg);
|
||||
}
|
||||
static trace_t last_row(void) { return s_events[(s_next + DIAG_EVENTS - 1) % DIAG_EVENTS]; }
|
||||
static void clear_trace(void) { assert(web_diagnostics_command("clear") == 0); }
|
||||
static void toggle(void) {
|
||||
assert(web_diagnostics_command("disable") == 0);
|
||||
assert(web_diagnostics_command("enable") == 0);
|
||||
}
|
||||
static esp_tls_t replacement = {.fd = 10};
|
||||
static esp_tls_t *old_tls;
|
||||
static void reuse_during_handler(void) {
|
||||
notify(old_tls, HTTPD_SSL_USER_CB_SESS_CLOSE);
|
||||
notify(&replacement, HTTPD_SSL_USER_CB_SESS_CREATE);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
esp_tls_t tls[7];
|
||||
for (unsigned i = 0; i < 7; ++i) tls[i] = (esp_tls_t){.fd = 10 + (int)i};
|
||||
httpd_req_t request = {.handle = &tls, .fd = 10,
|
||||
.uri = "/ws/admin?ticket=SECRET_TICKET", .headers = "Cookie: SECRET_COOKIE; Authorization: SECRET_PASSWORD",
|
||||
.body = "SECRET_PRIVATE_KEY SECRET_WIFI SECRET_VERIFIER"};
|
||||
|
||||
/* 1: off by default, always-on identity only, no capability scans. */
|
||||
assert(!s_enabled);
|
||||
notify(&tls[0], HTTPD_SSL_USER_CB_SESS_CREATE);
|
||||
uint64_t first = s_connections[0].seq;
|
||||
assert(first && !scans && !s_count);
|
||||
assert(traced_ticket_handler(&request) == ESP_OK && calls == 1 && !scans);
|
||||
assert(web_diagnostics_command("enable") == 0);
|
||||
assert(s_connections[0].seq == first && !s_count);
|
||||
|
||||
/* 2: all four actual server wrappers, rc unchanged, route tags and WS classification. */
|
||||
assert(traced_ticket_handler(&request) == ESP_OK);
|
||||
assert(last_row().route == WEB_DIAG_SERIAL_TICKET && last_row().elapsed_us == 75);
|
||||
handler_result = ESP_FAIL;
|
||||
assert(traced_admin_ticket_handler(&request) == ESP_FAIL);
|
||||
assert(last_row().route == WEB_DIAG_ADMIN_TICKET && last_row().result == ESP_FAIL);
|
||||
assert(!ws_queries);
|
||||
handler_result = ESP_OK;
|
||||
assert(traced_websocket_handler(&request) == ESP_OK);
|
||||
assert(last_row().ordinary == 1 && last_row().serial == 0); /* ESP_OK is not 101. */
|
||||
upgraded = true;
|
||||
assert(traced_websocket_handler(&request) == ESP_OK);
|
||||
assert(last_row().route == WEB_DIAG_SERIAL_UPGRADE && last_row().serial == 1);
|
||||
notify(&tls[1], HTTPD_SSL_USER_CB_SESS_CREATE);
|
||||
request.fd = 11;
|
||||
assert(traced_admin_upgrade_handler(&request) == ESP_OK);
|
||||
assert(last_row().route == WEB_DIAG_ADMIN_UPGRADE && last_row().admin == 1);
|
||||
assert(last_row().stack_bytes == 1234 && last_row().free_bytes[0] == 10003 &&
|
||||
last_row().largest[1] == 5005 && last_row().free_bytes[2] == 10010);
|
||||
|
||||
/* 3: six post-TLS slots; overflow cannot affect admission or evict observations. */
|
||||
for (unsigned i = 2; i < 6; ++i) notify(&tls[i], HTTPD_SSL_USER_CB_SESS_CREATE);
|
||||
trace_t row = last_row();
|
||||
assert(row.ordinary == 4 && row.serial == 1 && row.admin == 1);
|
||||
notify(&tls[6], HTTPD_SSL_USER_CB_SESS_CREATE);
|
||||
assert(s_lost == 1 && s_connections[0].seq == first);
|
||||
notify(&tls[0], HTTPD_SSL_USER_CB_SESS_CREATE);
|
||||
assert(s_unmatched == 1 && s_connections[0].seq == first);
|
||||
|
||||
/* 4: close identifies TLS instance, not fd; later reuse gets a new sequence. */
|
||||
clock_us += 100;
|
||||
notify(&tls[0], HTTPD_SSL_USER_CB_SESS_CLOSE);
|
||||
assert(last_row().event == TLS_CLOSE && last_row().seq == first && !last_row().serial);
|
||||
notify(&replacement, HTTPD_SSL_USER_CB_SESS_CREATE);
|
||||
uint64_t reused = s_connections[0].seq;
|
||||
assert(reused > first && s_connections[0].fd == 10);
|
||||
notify(&tls[0], HTTPD_SSL_USER_CB_SESS_CLOSE); /* stale instance with same fd */
|
||||
assert(s_connections[0].seq == reused && s_unmatched == 2);
|
||||
|
||||
/* 5: clear and toggles never reset live IDs; no sample may cross an epoch. */
|
||||
uint64_t event_before = s_event_seq;
|
||||
clear_trace();
|
||||
assert(s_connections[0].seq == reused && !s_count && !s_overwritten);
|
||||
request.fd = 10;
|
||||
during_scan = clear_trace;
|
||||
assert(traced_ticket_handler(&request) == ESP_OK && !s_count);
|
||||
during_handler = toggle;
|
||||
assert(traced_ticket_handler(&request) == ESP_OK && s_count == 1); /* enter only */
|
||||
assert(last_row().event == ENTER && last_row().id > event_before);
|
||||
clear_trace();
|
||||
during_scan = toggle;
|
||||
assert(traced_ticket_handler(&request) == ESP_OK && !s_count);
|
||||
unsigned prior_scans = scans;
|
||||
assert(web_diagnostics_command("disable") == 0);
|
||||
assert(traced_ticket_handler(&request) == ESP_OK && scans == prior_scans);
|
||||
assert(web_diagnostics_command("enable") == 0);
|
||||
|
||||
/* 6: full ring bounded with exact overwrite count; sequence survives clear. */
|
||||
clear_trace();
|
||||
for (unsigned i = 0; i < 25; ++i) assert(traced_ticket_handler(&request) == ESP_OK);
|
||||
assert(s_count == DIAG_EVENTS && s_overwritten == 18);
|
||||
uint64_t cutoff = s_event_seq;
|
||||
output_size = 0; output[0] = 0;
|
||||
assert(web_diagnostics_command("show") == 0);
|
||||
assert(s_event_seq == cutoff && s_count == DIAG_EVENTS);
|
||||
assert(strstr(output, "post-TLS occupancy=6/6 ordinary=5 serial=0 admin=1"));
|
||||
assert(strstr(output, "overwritten=18") && strstr(output, "NOT HTTP status"));
|
||||
|
||||
/* 7: actual formatted output contains no request secrets or pointer identity. */
|
||||
for (const char *const *p = (const char *const[]){"SECRET_", "Cookie:", "Authorization:",
|
||||
"ticket=", "/ws/admin?", "tls=", "0x", NULL}; *p; ++p) assert(!strstr(output, *p));
|
||||
assert(strstr(output, "conn=") && strstr(output, "stack=1234"));
|
||||
|
||||
/* 8: show/clear interleaving is bounded and explicitly marks missing rows. */
|
||||
output_size = 0; output[0] = 0;
|
||||
during_print = clear_trace;
|
||||
assert(web_diagnostics_command("show") == 0);
|
||||
assert(!s_count && strstr(output, "no longer retained"));
|
||||
|
||||
/* 9: synthetic close/reuse inside handler cannot classify replacement using stale fd. */
|
||||
notify(&replacement, HTTPD_SSL_USER_CB_SESS_CLOSE);
|
||||
notify(&tls[0], HTTPD_SSL_USER_CB_SESS_CREATE);
|
||||
old_tls = &tls[0];
|
||||
during_handler = reuse_during_handler;
|
||||
assert(traced_websocket_handler(&request) == ESP_OK);
|
||||
assert(s_connections[0].tls == &replacement && s_connections[0].kind == 0);
|
||||
|
||||
/* 10: synchronous stop closes all; failed stop with live sockets keeps metadata;
|
||||
* restart with reused TLS addresses and fds never resets connection sequence. */
|
||||
uint64_t before_restart = s_connection_seq;
|
||||
notify(&replacement, HTTPD_SSL_USER_CB_SESS_CLOSE);
|
||||
assert(s_connections[1].seq); /* partial/failed stop still owned */
|
||||
for (unsigned i = 1; i < 6; ++i) notify(&tls[i], HTTPD_SSL_USER_CB_SESS_CLOSE);
|
||||
for (unsigned i = 0; i < 6; ++i) assert(!s_connections[i].seq);
|
||||
notify(&tls[0], HTTPD_SSL_USER_CB_SESS_CREATE);
|
||||
assert(s_connections[0].seq > before_restart);
|
||||
notify(&tls[0], HTTPD_SSL_USER_CB_SESS_CLOSE);
|
||||
|
||||
/* 11: invalid callback/getter and unknown command have no side effects. */
|
||||
uint64_t before = s_connection_seq;
|
||||
web_diagnostics_tls(NULL);
|
||||
tls[0].invalid = true;
|
||||
notify(&tls[0], HTTPD_SSL_USER_CB_SESS_CREATE);
|
||||
assert(s_connection_seq == before);
|
||||
assert(web_diagnostics_command("show SECRET_PASSWORD") == 1);
|
||||
|
||||
/* 12: nonwrapping identities/epochs and saturating counters fail observation only. */
|
||||
s_connection_seq = UINT64_MAX;
|
||||
s_lost = UINT32_MAX;
|
||||
notify(&tls[1], HTTPD_SSL_USER_CB_SESS_CREATE);
|
||||
assert(s_connection_seq == UINT64_MAX && s_lost == UINT32_MAX && !s_connections[0].seq);
|
||||
s_event_seq = UINT64_MAX;
|
||||
unsigned retained = s_count;
|
||||
record((connection_t){.fd = 10}, ENTER, 0, 0, 0, s_epoch);
|
||||
assert(s_event_seq == UINT64_MAX && s_count == retained);
|
||||
s_epoch = UINT64_MAX;
|
||||
assert(web_diagnostics_command("enable") == 0 && !s_enabled);
|
||||
assert(!locked);
|
||||
puts("PASS: web diagnostics 12 lifecycle/ring/secrecy groups");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user