/* SPDX-License-Identifier: GPL-3.0-only */ /* Authenticated, bounded WebSocket transport for the serial session broker. */ #pragma once #include #include #include #include "esp_err.h" #include "esp_http_server.h" #include "session_broker.h" #include "user_database.h" #include "web_session_store.h" #ifdef __cplusplus extern "C" { #endif #define WEB_SERIAL_TRANSPORT_MAX_SESSIONS 2U #define WEB_SERIAL_TRANSPORT_MAX_TICKETS 4U #define WEB_SERIAL_TRANSPORT_TICKET_LENGTH 32U #define WEB_SERIAL_TRANSPORT_TICKET_CAPACITY \ (WEB_SERIAL_TRANSPORT_TICKET_LENGTH + 1U) #define WEB_SERIAL_TRANSPORT_TICKET_LIFETIME_SECONDS 30U #define WEB_SERIAL_TRANSPORT_MAX_RX_PAYLOAD 1024U #define WEB_SERIAL_TRANSPORT_TX_PAYLOAD_SIZE 512U #define WEB_SERIAL_TRANSPORT_TICKET_URI "/api/ws-ticket" #define WEB_SERIAL_TRANSPORT_WS_URI "/ws/serial" #define WEB_SERIAL_TRANSPORT_TICKET_QUERY_KEY "ticket" typedef struct { uint64_t tickets_issued; uint64_t tickets_consumed; uint64_t tickets_rejected; uint64_t tickets_expired; uint64_t connections; uint64_t connection_failures; uint64_t disconnections; uint64_t service_start_failures; uint64_t broker_failures; uint64_t writer_requests; uint64_t writer_grants; uint64_t writer_denials; uint64_t writer_releases; uint64_t writer_revocations; uint64_t rx_ws_frames_accepted; uint64_t rx_ws_frames_rejected; uint64_t rx_ws_bytes_accepted; uint64_t rx_ws_bytes_rejected; uint64_t tx_binary_frames; uint64_t tx_binary_bytes; uint64_t tx_control_frames; uint64_t tx_control_bytes; uint64_t send_failures; uint64_t queue_failures; uint64_t protocol_errors; uint64_t close_requests; } web_serial_transport_counters_t; typedef struct { bool active; bool principal_valid; bool writer; bool tx_pending; bool close_requested; int socket_fd; uint32_t generation; session_broker_client_id_t broker_client_id; user_role_t user_role; user_auth_method_t auth_method; char username[USER_DATABASE_USERNAME_CAPACITY + 1U]; } web_serial_transport_session_snapshot_t; typedef struct { bool initialized; bool server_attached; uint32_t active_sessions; uint32_t active_tickets; web_serial_transport_session_snapshot_t sessions[WEB_SERIAL_TRANSPORT_MAX_SESSIONS]; web_serial_transport_counters_t counters; } web_serial_transport_snapshot_t; /* * Allocate no per-session heap objects and start the permanent transport task. * CONFIG_HTTPD_WS_SUPPORT must be enabled. CONFIG_HTTPD_QUEUE_WORK_BLOCKING must * be disabled because that IDF mode can wait forever inside httpd_queue_work(). */ esp_err_t web_serial_transport_init(void); /* Attach after httpd start; detach as part of stopping that same server. */ esp_err_t web_serial_transport_attach_server(httpd_handle_t server); esp_err_t web_serial_transport_detach_server(httpd_handle_t server); /* * Mint a one-time bearer ticket bound to a current authenticated principal and * nonzero originating web-session ID. The * principal is copied; the output is exactly 32 Base64URL characters plus a * terminator and expires after 30 monotonic seconds. Never log or persist it. */ esp_err_t web_serial_transport_mint_ticket(const user_principal_t *principal, web_session_id_t web_session_id, char *ticket, size_t capacity); /* * Convenience POST response helper for /api/ws-ticket. Authentication is * intentionally outside this module: pass the principal returned by successful * authentication, and its session ID. Callers must also * enforce CSRF/Origin policy. Register as HTTP_POST, not as a public handler. */ esp_err_t web_serial_transport_handle_authenticated_ticket_request( httpd_req_t *request, const user_principal_t *principal, web_session_id_t web_session_id); /* * Frame callback installed by the HTTPD adapter after authorized admission. * Do not register directly: the initial HTTP GET must pass cookie/Origin policy * and call the session handler below before any 101 response. */ esp_err_t web_serial_transport_ws_handler(httpd_req_t *request); /* Trusted cookie-authorized upgrade caller; validate cookie/Origin first. * Zero is invalid for initial admission; no Basic fallback exists. */ esp_err_t web_serial_transport_session_ws_handler(httpd_req_t *request, web_session_id_t web_session_id); /* Invalidates the store first, then marks only matching tickets/slots for owner * cleanup. Safe to repeat after either store or transport slot reuse. */ esp_err_t web_serial_transport_revoke_web_session(web_session_id_t id); esp_err_t web_serial_transport_get_snapshot( web_serial_transport_snapshot_t *snapshot); typedef struct { uint64_t count, sum_us, max_us; } web_serial_performance_timing_t; typedef struct { bool active, pending, measured_pending, executing, saturated; int socket_fd; uint32_t generation; session_broker_client_id_t broker_client_id; uint64_t pending_age_us; uint64_t queued_frames, queued_bytes, queue_errors; uint64_t sent_frames, sent_bytes, send_errors, retired; web_serial_performance_timing_t queue_wait, send_call; web_serial_performance_timing_t completion_attempt, completion_nonempty; web_serial_performance_timing_t completion_first_nonempty; } web_serial_performance_session_t; typedef struct { bool enabled, epoch_exhausted; uint32_t epoch; web_serial_performance_session_t sessions[WEB_SERIAL_TRANSPORT_MAX_SESSIONS]; } web_serial_performance_snapshot_t; /* Binary TX only; no broker/HTTPD queries, heap scans, or sensitive identities. * queued_* counts transport reservations, including reported queue failures; * sent_* counts successful send-call returns. send_errors includes owner-context * rejection; send_call timings count only actual API calls. queue_wait includes * owned callbacks retired without sending. Pending age is since reservation-path * entry, including an executing send; unavailable epochs are explicitly marked. * All timing endpoints are local monotonic estimates, never peer acknowledgments. * Disable freezes aggregates; enable resumes them. Every toggle/clear fences * in-flight samples. Clear preserves enabled state. Epoch exhaustion fails closed. * Nonempty completion intervals include idle; first_nonempty requires the first * subsequent read to return data (not proof of backlog at send completion). */ esp_err_t web_serial_performance_enable(bool enabled); esp_err_t web_serial_performance_clear(void); esp_err_t web_serial_performance_snapshot(web_serial_performance_snapshot_t *out); /* Clearing counters does not alter tickets, sessions, ownership, or queued data. */ esp_err_t web_serial_transport_clear_counters(void); /* Invalidate cookie records and tickets/sockets for one username (also after * deletion), or all accounts. Store invalidation occurs even if serial init * failed. Authoritative store/principal checks supplement notifications. */ esp_err_t web_serial_transport_revoke_user(const uint8_t *username, size_t username_length); esp_err_t web_serial_transport_revoke_sessions(void); #ifdef __cplusplus } #endif