Files
ESP32_Serial_Swiss_Army_Knife/tests/web_diagnostics/test.c
T

158 lines
7.7 KiB
C

/* 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;
}