static admin_ssh_console_token_t token={.session_id=7,.slot_generation=1}; static user_principal_t admin={.role=USER_ROLE_ADMIN,.username="admin",.username_length=5}; static bool live=true, disconnect_input, revoke_input, password_mode, visible; static esp_err_t expected; static uint8_t answer[65]; static size_t answer_length; static unsigned calls; static bool owner_current(const admin_ssh_console_token_t *t, const user_principal_t *p) { (void)t; (void)p; assert(!lock_depth); return live; } static bool drained(const admin_ssh_console_token_t *t) { (void)t; assert(!lock_depth); 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) { (void)t; (void)action; (void)arg; ++actions; return ESP_ERR_NOT_SUPPORTED; } static const admin_console_owner_t owner={.is_current=owner_current,.drained=drained,.perform=perform}; static void zeroed(const void *data, size_t n) { const uint8_t *p=data; for(size_t i=0;i=130 ? 2U : 1U)); } int main(void) { assert(owner_drained && actions==0); assert(admin_ssh_console_init()==ESP_OK); assert(admin_ssh_console_start_uart_frontend()==ESP_OK); uint8_t bytes[140]; for(int route=0;route<3;++route) { memset(bytes,'Q',64); bytes[64]='\r'; run_case(route,bytes,65,ESP_OK,false,false,false,false); bytes[64]='\n'; run_case(route,bytes,65,ESP_OK,false,false,false,false); /* Confirmation accepts the exact maximum, not a truncated prefix. */ memcpy(bytes+65,bytes,65); run_case(route,bytes,130,ESP_OK,true,false,false,false); if(route) { bytes[64]='\r'; bytes[65]='\n'; memset(bytes+66,'Q',64); bytes[130]='\r'; run_case(route,bytes,131,ESP_OK,true,false,false,false); } for(int suffix='X';suffix<='Y';++suffix) { memset(bytes,'Q',64); bytes[64]=(uint8_t)suffix; bytes[65]='\r'; run_case(route,bytes,66,ESP_ERR_INVALID_SIZE,true,false,false,false); bytes[65]=8; bytes[66]=127; bytes[67]='Q'; bytes[68]='\r'; run_case(route,bytes,69,ESP_ERR_INVALID_SIZE,false,false,false,false); } /* Unsupported controls/high bytes cannot silently disappear, even if erased. */ for(unsigned byte=0;byte<256;++byte) { if((byte>=32 && byte<=126) || byte==3 || byte==8 || byte==127 || byte==10 || byte==13) continue; memset(bytes,'Q',63); bytes[63]=(uint8_t)byte; bytes[64]=8; bytes[65]='Q'; bytes[66]='Q'; bytes[67]='\r'; run_case(route,bytes,68,ESP_ERR_INVALID_SIZE,false,false,false,false); } memset(bytes,'Q',64); bytes[64]=8; bytes[65]='Q'; bytes[66]=127; bytes[67]='Q'; bytes[68]='\r'; run_case(route,bytes,69,ESP_OK,false,false,false,false); memset(bytes,'Q',65); bytes[65]=3; run_case(route,bytes,66,ESP_ERR_INVALID_STATE,false,false,false,false); run_case(route,bytes,65,route ? ESP_ERR_NOT_FOUND : ESP_FAIL,false,route!=0,false,false); if(route) run_case(route,bytes,65,ESP_ERR_NOT_FOUND,false,false,true,false); /* Visible prompts retain their existing truncation/edit behavior. */ memset(bytes,'Q',65); bytes[65]=8; bytes[66]='Q'; bytes[67]='\r'; run_case(route,bytes,68,ESP_OK,false,false,false,true); /* A failed confirmation also wipes the first full password. */ memset(bytes,'Q',64); bytes[64]='\r'; memset(bytes+65,'Q',65); bytes[130]='\r'; run_case(route,bytes,131,ESP_ERR_INVALID_SIZE,true,false,false,false); } puts("PASS: UART0/SSH/web exact capacity, sticky overflow/suffix/backspace, all unsupported bytes, cancellation/IO failure/disconnect/revocation, no echo, confirmation and visible editing"); }