Add Typed SSH Service Controls
Provide admin-only SSH status plus generation-safe start, stop, and single-session disconnect operations through the bounded dispatcher. Include Settings UI coverage, lifecycle safeguards, and host-side regression tests.
This commit is contained in:
+67
-2
@@ -100,6 +100,8 @@ static bool s_running;
|
||||
static bool s_transitioning;
|
||||
static bool s_desired_running;
|
||||
static bool s_cleanup_pending;
|
||||
/* Saturates independently of the internal completion sequence; never reset by counters. */
|
||||
static uint32_t s_management_generation = 1U;
|
||||
static uint32_t s_requested_sequence;
|
||||
static uint32_t s_completed_sequence;
|
||||
static esp_err_t s_command_result = ESP_ERR_INVALID_STATE;
|
||||
@@ -837,7 +839,8 @@ static ssh_slot_t *find_free_slot(size_t *slot_index)
|
||||
}
|
||||
|
||||
for (size_t index = 0U; index < SSH_TRANSPORT_MAX_SESSIONS; ++index) {
|
||||
if (s_slots[index].state == SSH_TRANSPORT_SESSION_FREE) {
|
||||
if (s_slots[index].state == SSH_TRANSPORT_SESSION_FREE &&
|
||||
s_slots[index].generation < SSH_TRANSPORT_GENERATION_MAX) {
|
||||
*slot_index = index;
|
||||
return &s_slots[index];
|
||||
}
|
||||
@@ -907,7 +910,8 @@ static void accept_connections(void)
|
||||
(void)setsockopt(socket_fd, IPPROTO_TCP, TCP_NODELAY,
|
||||
&enabled, sizeof(enabled));
|
||||
|
||||
uint32_t generation = next_generation(slot->generation);
|
||||
/* Exhausted slots are retired by find_free_slot(), never reused after wrap. */
|
||||
uint32_t generation = slot->generation + 1U;
|
||||
memset(slot, 0, sizeof(*slot));
|
||||
slot->state = SSH_TRANSPORT_SESSION_HANDSHAKE;
|
||||
slot->generation = generation;
|
||||
@@ -1512,6 +1516,7 @@ static esp_err_t request_running_locked(bool desired)
|
||||
return ESP_OK;
|
||||
}
|
||||
s_desired_running = desired;
|
||||
if (s_management_generation != UINT32_MAX) ++s_management_generation;
|
||||
s_transitioning = true;
|
||||
s_requested_sequence = next_generation(s_requested_sequence);
|
||||
sequence = s_requested_sequence;
|
||||
@@ -1633,6 +1638,66 @@ esp_err_t ssh_transport_get_snapshot(ssh_transport_snapshot_t *snapshot)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t ssh_transport_get_management_snapshot(ssh_transport_management_snapshot_t *snapshot)
|
||||
{
|
||||
if (snapshot == NULL) return ESP_ERR_INVALID_ARG;
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
if (!s_initialized) {
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
memset(snapshot, 0, sizeof(*snapshot));
|
||||
snapshot->generation = s_management_generation;
|
||||
snapshot->running = s_running;
|
||||
snapshot->transitioning = s_transitioning || s_cleanup_pending;
|
||||
for (size_t i = 0; i < SSH_TRANSPORT_MAX_SESSIONS; ++i) {
|
||||
snapshot->sessions[i] = s_session_snapshots[i];
|
||||
snapshot->sessions[i].close_requested |=
|
||||
snapshot->sessions[i].active && s_external_close_id[i] == snapshot->sessions[i].session_id;
|
||||
}
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t ssh_transport_manage_current(ssh_transport_management_action_t action,
|
||||
uint32_t target, uint32_t generation)
|
||||
{
|
||||
if (!generation || generation == UINT32_MAX ||
|
||||
action < SSH_TRANSPORT_MANAGE_START || action > SSH_TRANSPORT_MANAGE_DISCONNECT ||
|
||||
((action == SSH_TRANSPORT_MANAGE_DISCONNECT) != (target != 0U))) return ESP_ERR_INVALID_ARG;
|
||||
if (s_command_mutex == NULL) return ESP_ERR_INVALID_STATE;
|
||||
if (xSemaphoreTake(s_command_mutex, 0U) != pdTRUE) return ESP_ERR_TIMEOUT;
|
||||
esp_err_t error = ESP_ERR_INVALID_STATE;
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
if (s_initialized && !s_transitioning && !s_cleanup_pending &&
|
||||
generation == s_management_generation) {
|
||||
if (action == SSH_TRANSPORT_MANAGE_DISCONNECT) {
|
||||
error = ESP_ERR_NOT_FOUND;
|
||||
for (size_t i = 0; i < SSH_TRANSPORT_MAX_SESSIONS; ++i) {
|
||||
const ssh_transport_session_snapshot_t *session = &s_session_snapshots[i];
|
||||
if (session->active && session->session_id == target &&
|
||||
!session->close_requested && session->state != SSH_TRANSPORT_SESSION_CLOSING &&
|
||||
s_external_close_id[i] != target) {
|
||||
s_external_close_id[i] = target;
|
||||
error = ESP_OK;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (s_running != (action == SSH_TRANSPORT_MANAGE_START)) {
|
||||
error = ESP_OK;
|
||||
}
|
||||
}
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
/* The command mutex spans comparison and canonical lifecycle admission.
|
||||
* No HTTPD work/lock is involved; only the SSH owner touches sockets/wolfSSH. */
|
||||
if (error == ESP_OK) {
|
||||
if (action == SSH_TRANSPORT_MANAGE_DISCONNECT) notify_task();
|
||||
else error = request_running_locked(action == SSH_TRANSPORT_MANAGE_START);
|
||||
}
|
||||
xSemaphoreGive(s_command_mutex);
|
||||
return error;
|
||||
}
|
||||
|
||||
esp_err_t ssh_transport_clear_counters(void)
|
||||
{
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
|
||||
Reference in New Issue
Block a user