Files
ESP32_Serial_Swiss_Army_Knife/tests/hidden_input/test.c
T

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");
}