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