120 lines
7.4 KiB
C
120 lines
7.4 KiB
C
|
|
#define SSH_TRANSPORT_MAX_SESSIONS 2U
|
|
enum { SSH_TRANSPORT_SESSION_FREE=0, SSH_TRANSPORT_SESSION_ACTIVE=2,
|
|
SSH_TRANSPORT_ROUTE_ADMIN_CONSOLE=2 };
|
|
enum { USER_AUTH_METHOD_PASSWORD=0 };
|
|
typedef struct {
|
|
uint32_t session_id, generation, broker_client_id;
|
|
int state, route, socket_fd;
|
|
uint8_t console_slot_index;
|
|
bool authenticated, principal_valid, writer, close_requested;
|
|
size_t rx_length, rx_offset, tx_length, tx_offset;
|
|
user_principal_t principal;
|
|
char peer[48];
|
|
} ssh_slot_t;
|
|
typedef struct {
|
|
bool active, tx_pending, rx_pending, authenticated, principal_valid, close_requested;
|
|
bool writer, admin_command_pending;
|
|
uint32_t session_id, generation, broker_client_id, admin_output_pending;
|
|
int state, route, socket_fd, user_role, auth_method;
|
|
char username[USER_DATABASE_USERNAME_CAPACITY+1U], peer[48];
|
|
} ssh_transport_session_snapshot_t;
|
|
static ssh_transport_session_snapshot_t s_session_snapshots[2];
|
|
static user_principal_t s_console_principals[2];
|
|
static uint8_t s_console_slot_indices[2];
|
|
static uint32_t s_external_close_id[2];
|
|
static unsigned stopped, disconnected, rotated, reset, restarted;
|
|
static esp_err_t ssh_transport_stop(void) { ++stopped; return ESP_OK; }
|
|
static esp_err_t ssh_transport_disconnect(uint32_t id) { disconnected=id; return ESP_OK; }
|
|
static esp_err_t ssh_transport_replace_host_key(bool r) { if(r) ++reset; else ++rotated; return ESP_OK; }
|
|
static void esp_restart(void) { ++restarted; }
|
|
static void publish_slot(const ssh_slot_t *, size_t);
|
|
static bool admin_console_drained(const admin_ssh_console_token_t *);
|
|
static bool admin_console_is_current(const admin_ssh_console_token_t *, const user_principal_t *);
|
|
static bool consume_external_close(const ssh_slot_t *, size_t);
|
|
static esp_err_t admin_console_perform(const admin_ssh_console_token_t *, admin_ssh_deferred_action_type_t, uint32_t);
|
|
static const admin_console_owner_t s_admin_console_owner;
|
|
static void test_adapter(void)
|
|
{
|
|
admin_ssh_console_token_t token={ .slot_index=0, .session_id=7, .slot_generation=3 };
|
|
user_principal_t admin={ .role=USER_ROLE_ADMIN, .user_id=11, .auth_generation=2,
|
|
.username_length=5, .username="admin" };
|
|
assert(admin_ssh_console_init()==ESP_OK);
|
|
assert(admin_ssh_console_start_uart_frontend()==ESP_OK);
|
|
assert(admin_ssh_console_open_available(&token,&admin,&s_admin_console_owner)==ESP_OK);
|
|
assert(!admin_console_drained(&token));
|
|
s_session_snapshots[0]=(ssh_transport_session_snapshot_t){ .active=true, .session_id=7, .generation=3 };
|
|
assert(!admin_console_drained(&token)); /* No published console binding. */
|
|
assert(!admin_console_is_current(&token,&admin));
|
|
ssh_slot_t active={ .session_id=7, .generation=3, .authenticated=true,
|
|
.principal_valid=true, .state=SSH_TRANSPORT_SESSION_ACTIVE,
|
|
.route=SSH_TRANSPORT_ROUTE_ADMIN_CONSOLE, .principal=admin };
|
|
publish_slot(&active,0);
|
|
assert(admin_console_is_current(&token,&admin));
|
|
/* Production publication carries both console output and principal binding. */
|
|
assert(s_session_snapshots[0].tx_pending && s_session_snapshots[0].admin_output_pending);
|
|
assert(!strcmp(s_session_snapshots[0].username,"admin"));
|
|
uint8_t output[4096]; size_t n;
|
|
assert(admin_ssh_console_read_output(&token,output,sizeof(output),&n)==ESP_OK && n);
|
|
publish_slot(&active,0);
|
|
assert(admin_console_drained(&token));
|
|
active.state=SSH_TRANSPORT_SESSION_FREE; active.principal_valid=false;
|
|
publish_slot(&active,0);
|
|
user_principal_t empty={0};
|
|
assert(!memcmp(&s_console_principals[0],&empty,sizeof(empty)));
|
|
assert(!admin_console_is_current(&token,&admin));
|
|
active.state=SSH_TRANSPORT_SESSION_ACTIVE; active.principal_valid=true;
|
|
publish_slot(&active,0);
|
|
assert(admin_console_is_current(&token,&admin));
|
|
admin.username[0]='A'; assert(!admin_console_is_current(&token,&admin)); admin.username[0]='a';
|
|
active.route=0; publish_slot(&active,0); assert(!admin_console_is_current(&token,&admin));
|
|
active.route=SSH_TRANSPORT_ROUTE_ADMIN_CONSOLE;
|
|
active.authenticated=false; publish_slot(&active,0); assert(!admin_console_is_current(&token,&admin));
|
|
active.authenticated=true; publish_slot(&active,0);
|
|
s_external_close_id[0]=7; assert(!admin_console_is_current(&token,&admin));
|
|
ssh_slot_t closing={.session_id=7, .state=SSH_TRANSPORT_SESSION_ACTIVE};
|
|
assert(consume_external_close(&closing,0) && !s_external_close_id[0]);
|
|
assert(!admin_console_is_current(&token,&admin));
|
|
s_session_snapshots[0].close_requested=true;
|
|
assert(!admin_console_is_current(&token,&admin)); s_session_snapshots[0].close_requested=false;
|
|
++admin.auth_generation; assert(!admin_console_is_current(&token,&admin)); --admin.auth_generation;
|
|
++admin.user_id; assert(!admin_console_is_current(&token,&admin)); --admin.user_id;
|
|
++admin.method; assert(!admin_console_is_current(&token,&admin)); --admin.method;
|
|
admin.username_length=1; assert(!admin_console_is_current(&token,&admin)); admin.username_length=5;
|
|
token.transport=1; assert(!admin_console_drained(&token));
|
|
assert(!admin_console_is_current(&token,&admin));
|
|
|
|
token.transport=0; token.slot_generation=4; assert(!admin_console_drained(&token));
|
|
assert(!admin_console_is_current(&token,&admin));
|
|
assert(admin_console_perform(&token,ADMIN_SSH_DEFER_STOP,0)==ESP_ERR_NOT_FOUND && stopped==0);
|
|
token.slot_generation=3;
|
|
/* Same physical session can be assigned the other console slot. */
|
|
admin_ssh_console_close(&token);
|
|
admin_ssh_console_token_t other={ .session_id=8, .slot_generation=1 };
|
|
assert(admin_ssh_console_open_available(&other,&admin,&s_admin_console_owner)==ESP_OK);
|
|
assert(other.slot_index==0);
|
|
assert(admin_ssh_console_open_available(&token,&admin,&s_admin_console_owner)==ESP_OK);
|
|
assert(token.slot_index==1);
|
|
active.console_slot_index=token.slot_index; publish_slot(&active,0);
|
|
assert(admin_console_is_current(&token,&admin));
|
|
assert(s_console_slot_indices[0]==1);
|
|
admin_ssh_console_token_t wrong=token; wrong.slot_index=0;
|
|
assert(!admin_console_is_current(&wrong,&admin) && !admin_console_drained(&wrong));
|
|
assert(admin_console_perform(&wrong,ADMIN_SSH_DEFER_STOP,0)==ESP_ERR_NOT_FOUND);
|
|
/* A colliding published ID with a stale generation cannot steal the lookup. */
|
|
s_session_snapshots[1]=s_session_snapshots[0];
|
|
++s_session_snapshots[1].generation; s_console_slot_indices[1]=0;
|
|
assert(admin_console_is_current(&token,&admin));
|
|
assert(admin_ssh_console_read_output(&token,output,sizeof(output),&n)==ESP_OK && n);
|
|
publish_slot(&active,0);
|
|
s_session_snapshots[0].tx_pending=true;
|
|
assert(!admin_console_drained(&token)); s_session_snapshots[0].tx_pending=false;
|
|
assert(admin_console_perform(&token,ADMIN_CONSOLE_DEFER_SELF_CLOSE,99)==ESP_OK && disconnected==7);
|
|
assert(admin_console_perform(&token,ADMIN_SSH_DEFER_DISCONNECT,99)==ESP_OK && disconnected==99);
|
|
assert(admin_console_perform(&token,ADMIN_SSH_DEFER_STOP,0)==ESP_OK && stopped==1);
|
|
assert(admin_console_perform(&token,ADMIN_SSH_DEFER_HOST_KEY_ROTATE,0)==ESP_OK && rotated==1);
|
|
assert(admin_console_perform(&token,ADMIN_SSH_DEFER_HOST_KEY_RESET,0)==ESP_OK && reset==1);
|
|
assert(admin_console_perform(&token,ADMIN_SSH_DEFER_REBOOT,0)==ESP_OK && restarted==1);
|
|
puts("PASS: actual SSH snapshot/principal publication and wiping, adapter identity/drain checks, shared-slot admission and lifecycle action routing");
|
|
}
|