4 Commits
7 changed files with 31 additions and 13 deletions
+1 -1
View File
@@ -153,7 +153,7 @@ The dispatcher is the sole caller of `esp_console_run()`, serializing UART0 and
For SSH, standard output/error is redirected to the invoking session's bounded output ring. `console_input` routes visible or hidden prompts to UART0 or the active SSH session. Session tokens include slot and generation so late queued work cannot attach to a reused SSH slot. Only the SSH owner task moves ring output through wolfSSH.
Remote reboot, SSH stop/disconnect, and host-key rotate/reset use deferred control. The control task waits up to ten seconds for command state plus administration and transport application buffers to clear, then adds a short delay; this is a bounded best-effort heuristic, not peer-delivery confirmation. UART0 invokes these actions synchronously. User mutations and their revocations are not part of this mechanism. UART0 linenoise and the SSH editor consume the same manually maintained completion-hint provider, so the two administration routes cannot drift from each other; the hints can still drift from command registration and are not an authorization list.
Remote reboot, SSH stop/disconnect, and host-key rotate/reset use deferred control. The control task waits up to ten seconds for command state plus administration and transport application buffers to clear, then adds a short delay; this is a bounded best-effort heuristic, not peer-delivery confirmation. UART0 invokes these actions synchronously. User mutations and their revocations are not part of this mechanism. UART0 linenoise and the SSH editor consume the same manually maintained completion matcher and candidate formatter, so the two administration routes cannot drift in offered or displayed ambiguous completions; the hints can still drift from command registration and are not an authorization list.
## Wi-Fi and persistence
+1 -1
View File
@@ -26,7 +26,7 @@ Based on checked-in source plus `README.md` and `docs/roadmap.md`:
These observations should be checked when touching the relevant area; they are not automatically bugs requiring unrelated cleanup.
- `src/main.c` logs a Phase 7E startup banner although the implementation/roadmap is at Phase 8C.
- Some source comments still call shared commands UART0-only or call the current local status/control task read-only.
- `USER_DATABASE_LOAD_EMPTY` is only an initialization/failure sentinel at the checked-in revision: every successful `user_database_init()` path returns `STORED` or `MIGRATED_LEGACY`, so `main.c`'s successful "new empty" log branch is unreachable.
- SSH startup is currently gated on successful `web_security` initialization even though SSH uses separate host-key material. **Needs verification:** whether this coupling is intentional recovery policy or an accidental startup dependency.
+1 -1
View File
@@ -142,7 +142,7 @@ SSH listens on port 22 and accepts user-database passwords plus stored `ssh-ed25
UART0 and admin SSH submit to one bounded queue, and one dispatcher task is the sole caller of `esp_console_run()`. Consequently, SSH commands execute the canonical UART0 handlers and produce the same status and mutation behavior rather than using a second command implementation. Remote output is routed into the authenticated session's bounded output ring; only the SSH transport task accesses wolfSSH.
Admin SSH supports four-entry per-session command history with Up/Down, shared whole-line Tab completion using the same hints as UART0, inline cursor editing with Left/Right, Home/End (including Pos1/Ende terminal sequences), Backspace/Delete, Ctrl-C, and visible or no-echo interactive prompts. A Tab that cannot extend an ambiguous command prefix prints the same matching candidates supplied to the UART0 completion callback, then redraws the current input line. History is RAM-only, private to the session, and wiped on disconnect. Ping callbacks enqueue bounded typed results so all formatting remains on the dispatcher task.
UART0 and admin SSH use shared whole-line Tab completion. A unique/common prefix expands inline; a Tab that cannot extend an ambiguous prefix prints the matching candidates and redraws the unchanged input line instead of cycling candidates. Admin SSH additionally supports four-entry per-session command history with Up/Down, inline cursor editing with Left/Right, Home/End (including Pos1/Ende terminal sequences), Backspace/Delete, Ctrl-C, and visible or no-echo interactive prompts. Its history is RAM-only, private to the session, and wiped on disconnect. Ping callbacks enqueue bounded typed results so all formatting remains on the dispatcher task.
`reboot`, `ssh stop`, session disconnect, and SSH host-key reset/rotation are deferred until the command acknowledgement has left both the administration output ring and transport TX buffer. The shell stops accepting another command while such an action is pending. SSH host-key replacement or service stop closes all SSH sessions; reconnect and verify the new fingerprint where applicable. Web recovery credentials/certificates, Wi-Fi secrets, and interactive user passwords/keys are available to authenticated administrators and must therefore be treated as remotely accessible administrative material. `user bootstrap` and `user recover --force` remain UART0-only. A connected administrator also cannot generate its own replacement password remotely, preventing the one-time password from being lost during self-revocation. SSH does not provide `exec`, SFTP, SCP, forwarding, or subsystems.
+1 -1
View File
@@ -137,7 +137,7 @@ Use one disposable `admin` and one disposable `user`. Keep UART0 attached throug
Exercise printable input, backspace, Ctrl-C, CR/LF, an empty line, and a line longer than the documented limit. Confirm the command line is bounded, overflow is discarded through a clear diagnostic, and a new prompt remains usable. Run `help`, `user list`, and `broker clients` in a normal ANSI terminal and confirm every line starts in column zero: canonical LF output must be normalized to CRLF without doubling handlers that already emit CRLF.
Run at least five distinct commands, then use Up/Down to navigate the four-entry per-session history, return to a saved draft with Down, and confirm older entries are bounded out. Verify history does not survive reconnect and is not shared with a second administrator. Exercise Tab on root and nested prefixes such as `us`, `user l`, `wifi ap sh`, `wifi next`, and `ssh host-key i`; confirm the same hints are offered by UART0 and SSH. For an ambiguous prefix, confirm SSH prints the matching candidates and redraws the unchanged input line, matching UART0's candidate set; unique/common prefixes must redraw cleanly without inserting escape-sequence bytes into the command. Type a command, use Left/Right to insert and delete characters in its middle, then use Home/End and the terminal's Pos1/Ende keys; verify cursor placement, Backspace, and Delete change exactly the intended byte before submission.
Run at least five distinct commands, then use Up/Down to navigate the four-entry per-session history, return to a saved draft with Down, and confirm older entries are bounded out. Verify history does not survive reconnect and is not shared with a second administrator. Exercise Tab on root and nested prefixes such as `us`, `user l`, `wifi ap sh`, `wifi next`, and `ssh host-key i`; confirm the same hints are offered by UART0 and SSH. For an ambiguous prefix, confirm both routes print the same matching candidates and redraw the unchanged input line instead of cycling candidates; unique/common prefixes must redraw cleanly without inserting escape-sequence bytes into the command. Type a command, use Left/Right to insert and delete characters in its middle, then use Home/End and the terminal's Pos1/Ende keys; verify cursor placement, Backspace, and Delete change exactly the intended byte before submission.
Run an unsupported command and confirm it is rejected without affecting UART0 or the serial broker. Run the full root `help` output to exercise output-ring draining. With the SSH client temporarily unable to read output, confirm the worker applies input backpressure rather than accepting an unbounded command/output backlog; inspect `ssh counters` for admin-console admission and input-backpressure values.
+26 -6
View File
@@ -4,6 +4,7 @@
#include "console_completion.h"
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
@@ -327,15 +328,34 @@ static ssize_t console_read_with_late_terminal_upgrade(int file_descriptor,
return received;
}
static bool add_linenoise_completion(const char *candidate, void *context)
{
linenoiseAddCompletion(context, candidate);
return true;
}
/* The UART frontend is the sole caller of linenoise's completion callback. */
static char s_uart_completion_output[2048U];
static void console_completion_callback(const char *buffer, linenoiseCompletions *completions)
{
console_completion_visit(buffer, add_linenoise_completion, completions);
char completed[257U] = {0};
if (console_completion_expand(buffer, completed, sizeof(completed))) {
linenoiseAddCompletion(completions, completed);
return;
}
size_t output_length = 0U;
if (!console_completion_format_matches(buffer, s_uart_completion_output,
sizeof(s_uart_completion_output),
&output_length) ||
output_length == 0U) {
return;
}
/*
* Linenoise cycles every completion it receives. Print the shared list
* ourselves, then return the unchanged line as its one completion so its
* normal refresh restores the prompt without selecting a candidate.
*/
fputs("\r\n", stdout);
(void)fwrite(s_uart_completion_output, 1U, output_length, stdout);
fflush(stdout);
linenoiseAddCompletion(completions, buffer);
}
void console_completion_install(void)
-1
View File
@@ -46,7 +46,6 @@ static const char *TAG = "firmware";
void app_main(void)
{
ESP_LOGI(TAG, "ESP32-S3 Serial Swiss Army Knife Phase 7E local UI reliability started");
if (esp_psram_is_initialized()) {
ESP_LOGI(TAG, "PSRAM initialized: %u bytes", (unsigned int)esp_psram_get_size());
+1 -2
View File
@@ -547,8 +547,7 @@ static esp_err_t create_context(void)
(void)wolfSSH_CTX_SetChannelReqShellCb(context, accept_shell);
(void)wolfSSH_CTX_SetChannelReqExecCb(context, reject_channel_request);
(void)wolfSSH_CTX_SetChannelReqSubsysCb(context, reject_channel_request);
(void)wolfSSH_CTX_SetBanner(
context, "ESP32 Serial Swiss Army Knife - authenticated serial transport\r\n");
s_context = context;
return ESP_OK;
}