/* Typed deferred work exercises the production dispatcher/control state machine. */ static admin_ssh_console_token_t token={.session_id=7, .slot_generation=1, .transport=ADMIN_CONSOLE_TRANSPORT_WEB}; static user_principal_t principal={.role=USER_ROLE_ADMIN, .auth_generation=1}; static bool live=true, close_in_action; static unsigned validations, invalidate_at; static esp_err_t action_result; static bool current(const admin_ssh_console_token_t *t, const user_principal_t *p) { assert(!lock_depth && t->session_id==7 && p->auth_generation==1); if (++validations==invalidate_at) live=false; return live; } static bool drained(const admin_ssh_console_token_t *t) { assert(!lock_depth && current_task==s_control_task && t->session_id==7); return owner_drained; } static esp_err_t perform(const admin_ssh_console_token_t *t, admin_ssh_deferred_action_type_t action, uint32_t arg); static const admin_console_owner_t owner={ .supported_actions=1U << ADMIN_CONSOLE_DEFER_WEB_CERTIFICATE_ROTATE, .dispatcher_actions=1U << ADMIN_CONSOLE_DEFER_WEB_CERTIFICATE_ROTATE, .is_current=current, .drained=drained, .perform=perform, }; static esp_err_t perform(const admin_ssh_console_token_t *t, admin_ssh_deferred_action_type_t action, uint32_t arg) { assert(!lock_depth && current_task==s_task && current_task!=s_control_task); assert(action==ADMIN_CONSOLE_DEFER_WEB_CERTIFICATE_ROTATE && arg==0); assert(s_sessions[0].executing && s_sessions[0].deferred_action_pending); assert(!admin_ssh_console_accepts_input(t)); size_t consumed=99; assert(!admin_ssh_console_feed_input(t,(const uint8_t *)"ignored",7,&consumed) && !consumed); ++actions; if (close_in_action) { admin_ssh_console_close(t); admin_ssh_console_token_t replacement=*t; ++replacement.slot_generation; assert(admin_ssh_console_open_owned(&replacement,&principal,&owner)==ESP_ERR_INVALID_STATE); } return action_result; } static void pump(void (*task)(void *)) { current_task=task==control_task ? s_control_task : s_task; if (!setjmp(loop_done)) task(NULL); } static void clear_output(void) { uint8_t data[4096]; size_t n; assert(admin_ssh_console_read_output(&token,data,sizeof(data),&n)==ESP_OK); } static void reopen_certificate_session(void) { admin_ssh_console_close(&token); ++token.slot_generation; live=principal_current=owner_drained=true; validations=invalidate_at=0; close_in_action=false; action_result=ESP_OK; ticks=0; assert(admin_ssh_console_open_owned(&token,&principal,&owner)==ESP_OK); clear_output(); } static esp_err_t schedule(void) { current_task=s_task; s_dispatch_remote=true; s_dispatch_token=token; s_dispatch_principal=principal; s_sessions[0].executing=s_sessions[0].command_pending=true; esp_err_t result=admin_ssh_console_dispatch_defer(ADMIN_CONSOLE_DEFER_WEB_CERTIFICATE_ROTATE,0); s_sessions[0].executing=s_sessions[0].command_pending=false; s_dispatch_remote=false; return result; } static void revoke_delay(void) { if (ticks>=200) live=false; } static void reuse_delay(void) { if (ticks>=200) { delay_hook=NULL; reopen_certificate_session(); } } static void assert_pending(void) { admin_ssh_console_session_snapshot_t snapshot; assert(admin_ssh_console_get_session_snapshot(&token,&snapshot)==ESP_OK); assert(snapshot.deferred_action_pending && !admin_ssh_console_accepts_input(&token)); } static void uart_observes_pending(void) { assert(current_task==s_task && actions==0); assert_pending(); } int main(void) { assert(admin_ssh_console_init()==ESP_OK); assert(admin_ssh_console_start_uart_frontend()==ESP_OK); s_task=(void *)1; s_control_task=(void *)2; /* Union overlay preserves the old queue item allocation on this ABI. */ struct old_request { admin_request_origin_t origin; admin_ssh_console_token_t token; user_principal_t principal; TaskHandle_t completion_task; uint8_t line[ADMIN_SSH_CONSOLE_COMMAND_LINE_CAPACITY+1U]; }; assert(sizeof(admin_request_t)==sizeof(struct old_request)); assert(s_request_queue->capacity==4 && s_control_queue->capacity==2); reopen_certificate_session(); queue_full=true; assert(schedule()==ESP_ERR_TIMEOUT && !s_sessions[0].deferred_action_pending && !actions); queue_full=false; assert(schedule()==ESP_OK); assert_pending(); /* Drain waits for acknowledgement, then times out without enqueue/mutation. */ s_sessions[0].output_length=1; pump(control_task); assert(ticks==10000 && !actions && !s_request_queue->count && !s_sessions[0].deferred_action_pending); clear_output(); ticks=0; assert(schedule()==ESP_OK); admin_request_t uart={.origin=ADMIN_REQUEST_UART0, .line="memory"}; for (unsigned i=0;i<4;++i) assert(xQueueSend(s_request_queue,&uart,0)); pump(control_task); assert(!actions && !s_sessions[0].deferred_action_pending && s_request_queue->count==4); assert(s_sessions[0].output_length); pump(worker_task); clear_output(); puts("PASS: unchanged queue item/depths, admission and handoff queue failure before mutation, ack drain cancellation"); assert(schedule()==ESP_OK); assert(xQueueSend(s_request_queue,&uart,0)); pump(control_task); assert_pending(); assert(!actions && s_request_queue->count==2); command_hook=uart_observes_pending; pump(worker_task); command_hook=NULL; assert(actions==1 && !s_sessions[0].deferred_action_pending && !s_sessions[0].executing); assert(runs==5); /* Typed work never calls esp_console_run. */ puts("PASS: control only hands off, queued UART first, crypto callback exclusively serialized on dispatcher, input gated through callback"); for (unsigned cancellation=0;cancellation<7;++cancellation) { reopen_certificate_session(); assert(schedule()==ESP_OK); if (cancellation==0) delay_hook=revoke_delay; if (cancellation==1) delay_hook=reuse_delay; pump(control_task); delay_hook=NULL; if (cancellation==2) live=false; if (cancellation==3) principal_current=false; if (cancellation==4) reopen_certificate_session(); if (cancellation==5) admin_ssh_console_close(&token); if (cancellation==6) invalidate_at=2; /* Last check after executing reservation. */ pump(worker_task); assert(actions==1 && !s_sessions[0].executing); if (cancellation==1 || cancellation==4) { assert(s_sessions[0].active && !s_sessions[0].deferred_action_pending && !s_sessions[0].output_length); } } puts("PASS: delay/queued revoke, account revoke, close/reuse, final execution check; no output into replacements"); reopen_certificate_session(); action_result=ESP_ERR_NO_MEM; assert(schedule()==ESP_OK); pump(control_task); pump(worker_task); assert(actions==2 && !s_sessions[0].deferred_action_pending && admin_ssh_console_accepts_input(&token)); uint8_t out[512]={0}; size_t n; assert(admin_ssh_console_read_output(&token,out,sizeof(out)-1,&n)==ESP_OK); assert(strstr((char *)out,"Deferred remote action failed: fake")); reopen_certificate_session(); close_in_action=true; assert(schedule()==ESP_OK); pump(control_task); pump(worker_task); admin_session_t empty={0}; assert(!memcmp(&empty,&s_sessions[0],sizeof(empty)) && actions==3); puts("PASS: action error reaches deferred result, input resumes on failure, self-detach reserves slot until return and wipes state"); }