127 lines
6.5 KiB
C
127 lines
6.5 KiB
C
|
|
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<n;++i) assert(p[i]==0); }
|
|
static void reply(void)
|
|
{
|
|
++publications;
|
|
/* Feed bytewise to exercise persistent state across transport packets. */
|
|
while(input_offset<input_size) {
|
|
uint8_t byte=input[input_offset++]; size_t consumed=0;
|
|
assert(admin_ssh_console_feed_input(&token,&byte,1,&consumed) && consumed==1);
|
|
if(s_sessions[0].prompt_state!=ADMIN_PROMPT_WAITING) break;
|
|
}
|
|
if(disconnect_input) admin_ssh_console_close(&token);
|
|
if(revoke_input) live=false;
|
|
}
|
|
static void command(void)
|
|
{
|
|
++calls;
|
|
memset(answer,0xa5,sizeof(answer)); answer_length=999;
|
|
esp_err_t error=password_mode ? read_password(answer,&answer_length) :
|
|
visible ? console_input_read_line("Input: ",answer,sizeof(answer),&answer_length) :
|
|
console_input_read_hidden("Password: ",answer,sizeof(answer),12,64,&answer_length);
|
|
assert(error==expected);
|
|
if(error==ESP_OK) {
|
|
assert(answer_length==64 && answer[64]==0);
|
|
for(size_t i=0;i<64;++i) assert(answer[i]=='Q');
|
|
} else {
|
|
assert(!answer_length); zeroed(answer,sizeof(answer));
|
|
}
|
|
if(s_dispatch_remote) {
|
|
zeroed(s_sessions[0].prompt_input,sizeof(s_sessions[0].prompt_input));
|
|
assert(!s_sessions[0].prompt_rejected && !s_sessions[0].prompt_length);
|
|
}
|
|
}
|
|
static void run_case(int route, const uint8_t *bytes, size_t n, esp_err_t result,
|
|
bool passwords, bool disconnect, bool revoke, bool show)
|
|
{
|
|
input=bytes; input_size=n; input_offset=0; expected=result;
|
|
password_mode=passwords; disconnect_input=disconnect; revoke_input=revoke; visible=show;
|
|
publications=0; calls=0; live=true; uart_output_length=0; uart_output[0]=0;
|
|
s_dispatch_remote=false;
|
|
if(route==0) command();
|
|
else {
|
|
++token.slot_generation; token.transport=route==1 ? ADMIN_CONSOLE_TRANSPORT_SSH : ADMIN_CONSOLE_TRANSPORT_WEB;
|
|
assert(admin_ssh_console_open_owned(&token,&admin,&owner)==ESP_OK);
|
|
uint8_t out[4096]; size_t count;
|
|
assert(admin_ssh_console_read_output(&token,out,sizeof(out),&count)==ESP_OK);
|
|
size_t consumed;
|
|
assert(admin_ssh_console_feed_input(&token,(const uint8_t *)"user password target\r",21,&consumed));
|
|
assert(consumed==21);
|
|
prompt_hook=reply; command_hook=command;
|
|
if(!setjmp(loop_done)) worker_task(NULL);
|
|
prompt_hook=NULL; command_hook=NULL;
|
|
if(s_sessions[0].active) {
|
|
assert(admin_ssh_console_read_output(&token,out,sizeof(out)-1,&count)==ESP_OK);
|
|
out[count]=0;
|
|
if(!show) assert(!strstr((char *)out,"QQQ"));
|
|
admin_ssh_console_close(&token);
|
|
}
|
|
zeroed(&s_sessions[0],sizeof(s_sessions[0]));
|
|
}
|
|
assert(calls==1 && !lock_depth);
|
|
assert(input_offset==n);
|
|
if(!show) assert(!strstr(uart_output,"QQQ"));
|
|
assert(publications==(passwords && n>=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");
|
|
}
|