feat: add bounded admin WebSocket backend (Phase 8D.5)
- 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.
This commit is contained in:
@@ -10,7 +10,13 @@ typedef int esp_err_t;
|
||||
enum { ESP_OK, ESP_FAIL, ESP_ERR_INVALID_ARG, ESP_ERR_INVALID_STATE,
|
||||
ESP_ERR_NO_MEM, ESP_ERR_TIMEOUT, ESP_ERR_NOT_SUPPORTED, ESP_ERR_NOT_FOUND };
|
||||
enum { USER_ROLE_USER, USER_ROLE_ADMIN };
|
||||
typedef struct { int role; } user_principal_t;
|
||||
#define USER_DATABASE_USERNAME_CAPACITY 16U
|
||||
typedef struct {
|
||||
uint32_t user_id, auth_generation;
|
||||
int role, method;
|
||||
size_t username_length;
|
||||
char username[USER_DATABASE_USERNAME_CAPACITY + 1U];
|
||||
} user_principal_t;
|
||||
typedef unsigned TickType_t;
|
||||
typedef void *TaskHandle_t;
|
||||
typedef int portMUX_TYPE;
|
||||
@@ -38,7 +44,7 @@ static size_t strlcpy(char *d, const char *s, size_t n) {
|
||||
memcpy(d, s, k); d[k] = 0; } return len;
|
||||
}
|
||||
static esp_err_t user_database_principal_is_current(const user_principal_t *p, bool *c)
|
||||
{ (void)p; *c = principal_current; return ESP_OK; }
|
||||
{ (void)p; assert(!lock_depth); *c = principal_current; return ESP_OK; }
|
||||
static const char *esp_err_to_name(int e) { (void)e; return "fake"; }
|
||||
static TaskHandle_t xTaskGetCurrentTaskHandle(void) { return current_task; }
|
||||
static unsigned xTaskGetTickCount(void) { return ticks; }
|
||||
@@ -57,7 +63,8 @@ static int xQueueReceive(QueueHandle_t q, void *p, unsigned t)
|
||||
{ (void)t; if (!q->count) longjmp(loop_done,1); memcpy(p,q->bytes,q->size); q->count=0; return 1; }
|
||||
static SemaphoreHandle_t xSemaphoreCreateBinaryStatic(StaticSemaphore_t *s) { return s; }
|
||||
static int xSemaphoreTake(SemaphoreHandle_t s, unsigned t)
|
||||
{ if (t && prompt_hook) prompt_hook(); int r=*s; *s=0; return r; }
|
||||
{ assert(!lock_depth); if (t && !*s) { ticks+=t; if (prompt_hook) prompt_hook(); }
|
||||
int r=*s; *s=0; return r; }
|
||||
static int xSemaphoreGive(SemaphoreHandle_t s) { *s=1; return 1; }
|
||||
static void linenoiseSetMaxLineLen(unsigned n) { (void)n; }
|
||||
static char *linenoise(const char *p) { (void)p; return NULL; }
|
||||
@@ -67,8 +74,7 @@ static bool console_completion_expand(const char *s, char *d, size_t n)
|
||||
{ (void)s; (void)d; (void)n; if (completion_hook) completion_hook(); return false; }
|
||||
static bool console_completion_format_matches(const char *s, char *d, size_t n, size_t *len)
|
||||
{ (void)s; *len=strlcpy(d,"help\r\n",n); return true; }
|
||||
static size_t esp_console_split_argv(char *s, char **v, size_t n)
|
||||
{ (void)s; (void)v; (void)n; return 0; } /* Real parser tested by admin_ssh_policy. */
|
||||
size_t esp_console_split_argv(char *s, char **v, size_t n);
|
||||
static esp_err_t esp_console_run(const char *s, int *r)
|
||||
{ (void)s; ++runs; if (command_hook) command_hook(); *r=0; return ESP_OK; }
|
||||
typedef struct { const char *command, *help, *hint; int (*func)(int,char **); void *argtable; } esp_console_cmd_t;
|
||||
|
||||
Reference in New Issue
Block a user