mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-05-08 09:58:46 +00:00
refactor(core/prodtest): make cli non-blocking, event-loop compatible
[no changelog]
This commit is contained in:
parent
cf00130409
commit
970e8aae82
@ -248,7 +248,11 @@ int main(void) {
|
|||||||
pair_optiga(&g_cli);
|
pair_optiga(&g_cli);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
cli_run_loop(&g_cli);
|
while (true) {
|
||||||
|
if (usb_vcp_can_read(VCP_IFACE)) {
|
||||||
|
cli_run(&g_cli);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -331,163 +331,170 @@ static char cli_autocomplete(cli_t* cli, const char* prefix) {
|
|||||||
return next_char;
|
return next_char;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reads a line from the console input and stores it in the `cli->line` buffer
|
// Reads next char and processes it.
|
||||||
//
|
//
|
||||||
// Returns false if the input line is too long
|
// Returns 1 if the input line is complete,
|
||||||
static bool cli_readln(cli_t* cli) {
|
// returns 0 if more characters are needed,
|
||||||
|
// returns negative value if the input line is too long
|
||||||
|
static int cli_process_char(cli_t* cli) {
|
||||||
char* buf = cli->line_buffer;
|
char* buf = cli->line_buffer;
|
||||||
int len = 0; // number of characters in the buffer (excluding '\0')
|
|
||||||
int cursor = 0; // cursor position in the buffer
|
|
||||||
|
|
||||||
int hist_idx = 0;
|
int ch = cli_readch(cli);
|
||||||
int hist_prefix = 0;
|
|
||||||
|
|
||||||
buf[0] = '\0';
|
switch (ch) {
|
||||||
|
case ESC_SEQ('A'): // ESC[A
|
||||||
for (;;) {
|
// Up arrow - search history backwards
|
||||||
int ch = cli_readch(cli);
|
if (cli->hist_idx == 0) {
|
||||||
|
cli->hist_prefix = cli->len;
|
||||||
switch (ch) {
|
}
|
||||||
case ESC_SEQ('A'): // ESC[A
|
const char* hist_line =
|
||||||
// Up arrow - search history backwards
|
cli_history_rev(cli, &cli->hist_idx, buf, cli->hist_prefix);
|
||||||
if (hist_idx == 0) {
|
if (hist_line != NULL) {
|
||||||
hist_prefix = len;
|
if (cli->cursor > 0) {
|
||||||
|
// Move the cursor to the beginning of the line
|
||||||
|
cli_printf(cli, "\e[%dD", cli->cursor);
|
||||||
}
|
}
|
||||||
|
// Replace original text
|
||||||
|
strcpy(buf, hist_line);
|
||||||
|
cli->len = cli->cursor = strlen(buf);
|
||||||
|
cli_printf(cli, "%s\e[K", buf);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case ESC_SEQ('B'): // ESC[B
|
||||||
|
// Down arrow - search history forwards
|
||||||
|
if (cli->hist_idx > 0) {
|
||||||
const char* hist_line =
|
const char* hist_line =
|
||||||
cli_history_rev(cli, &hist_idx, buf, hist_prefix);
|
cli_history_fwd(cli, &cli->hist_idx, buf, cli->hist_prefix);
|
||||||
if (hist_line != NULL) {
|
if (hist_line != NULL) {
|
||||||
if (cursor > 0) {
|
if (cli->cursor > 0) {
|
||||||
// Move the cursor to the beginning of the line
|
// Move the cursor to the beginning of the line
|
||||||
cli_printf(cli, "\e[%dD", cursor);
|
cli_printf(cli, "\e[%dD", cli->cursor);
|
||||||
}
|
}
|
||||||
// Replace original text
|
// Replace original text
|
||||||
strcpy(buf, hist_line);
|
strcpy(buf, hist_line);
|
||||||
len = cursor = strlen(buf);
|
cli->len = cli->cursor = strlen(buf);
|
||||||
cli_printf(cli, "%s\e[K", buf);
|
cli_printf(cli, "%s\e[K", buf);
|
||||||
}
|
} else {
|
||||||
continue;
|
if (cli->cursor > cli->hist_prefix) {
|
||||||
|
cli_printf(cli, "\e[%dD", cli->cursor - cli->hist_prefix);
|
||||||
case ESC_SEQ('B'): // ESC[B
|
|
||||||
// Down arrow - search history forwards
|
|
||||||
if (hist_idx > 0) {
|
|
||||||
const char* hist_line =
|
|
||||||
cli_history_fwd(cli, &hist_idx, buf, hist_prefix);
|
|
||||||
if (hist_line != NULL) {
|
|
||||||
if (cursor > 0) {
|
|
||||||
// Move the cursor to the beginning of the line
|
|
||||||
cli_printf(cli, "\e[%dD", cursor);
|
|
||||||
}
|
|
||||||
// Replace original text
|
|
||||||
strcpy(buf, hist_line);
|
|
||||||
len = cursor = strlen(buf);
|
|
||||||
cli_printf(cli, "%s\e[K", buf);
|
|
||||||
} else {
|
|
||||||
if (cursor > hist_prefix) {
|
|
||||||
cli_printf(cli, "\e[%dD", cursor - hist_prefix);
|
|
||||||
}
|
|
||||||
cli_printf(cli, "\e[K");
|
|
||||||
len = cursor = hist_prefix;
|
|
||||||
buf[len] = '\0';
|
|
||||||
}
|
}
|
||||||
|
cli_printf(cli, "\e[K");
|
||||||
|
cli->len = cli->cursor = cli->hist_prefix;
|
||||||
|
buf[cli->len] = '\0';
|
||||||
}
|
}
|
||||||
continue;
|
}
|
||||||
}
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Reset the history index, if the user types something else
|
// Reset the history index, if the user types something else
|
||||||
hist_idx = 0;
|
cli->hist_idx = 0;
|
||||||
|
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
case ESC_SEQ('C'): // ESC[C
|
case ESC_SEQ('C'): // ESC[C
|
||||||
// Right arrow
|
// Right arrow
|
||||||
if (cursor < len) {
|
if (cli->cursor < cli->len) {
|
||||||
if (cli->interactive) {
|
if (cli->interactive) {
|
||||||
cli_printf(cli, "\e[C");
|
cli_printf(cli, "\e[C");
|
||||||
}
|
}
|
||||||
cursor++;
|
cli->cursor++;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ESC_SEQ('D'): // ESC[D
|
case ESC_SEQ('D'): // ESC[D
|
||||||
// Left arrow
|
// Left arrow
|
||||||
if (cursor > 0) {
|
if (cli->cursor > 0) {
|
||||||
if (cli->interactive) {
|
|
||||||
cli_printf(cli, "\e[D");
|
|
||||||
}
|
|
||||||
cursor--;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '\b':
|
|
||||||
case 0x7F:
|
|
||||||
// backspace => delete last character
|
|
||||||
if (cursor == 0) break;
|
|
||||||
if (cli->interactive) {
|
if (cli->interactive) {
|
||||||
// Move the cursor left
|
|
||||||
cli_printf(cli, "\e[D");
|
cli_printf(cli, "\e[D");
|
||||||
}
|
}
|
||||||
--cursor;
|
cli->cursor--;
|
||||||
// do not break, fall through
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case ESC_SEQ(3): // ESC[3~
|
case '\b':
|
||||||
// Delete
|
case 0x7F:
|
||||||
if (cursor < len) {
|
// backspace => delete last character
|
||||||
// Delete the character at the cursor
|
if (cli->cursor == 0) break;
|
||||||
memmove(&buf[cursor], &buf[cursor + 1], len - cursor);
|
if (cli->interactive) {
|
||||||
--len;
|
// Move the cursor left
|
||||||
if (cli->interactive) {
|
cli_printf(cli, "\e[D");
|
||||||
// Print the rest of the line and move the cursor back
|
}
|
||||||
cli_printf(cli, "%s \b", &buf[cursor]);
|
--cli->cursor;
|
||||||
if (cursor < len) {
|
// do not break, fall through
|
||||||
cli_printf(cli, "\e[%dD", len - cursor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '\r':
|
case ESC_SEQ(3): // ESC[3~
|
||||||
case '\n':
|
// Delete
|
||||||
// end of line
|
if (cli->cursor < cli->len) {
|
||||||
|
// Delete the character at the cursor
|
||||||
|
memmove(&buf[cli->cursor], &buf[cli->cursor + 1],
|
||||||
|
cli->len - cli->cursor);
|
||||||
|
--cli->len;
|
||||||
if (cli->interactive) {
|
if (cli->interactive) {
|
||||||
cli_printf(cli, "\r\n");
|
// Print the rest of the line and move the cursor back
|
||||||
}
|
cli_printf(cli, "%s \b", &buf[cli->cursor]);
|
||||||
return len < CLI_LINE_BUFFER_SIZE;
|
if (cli->cursor < cli->len) {
|
||||||
|
cli_printf(cli, "\e[%dD", cli->len - cli->cursor);
|
||||||
case '\t':
|
|
||||||
// tab => autocomplete
|
|
||||||
if (cli->interactive && len == cursor) {
|
|
||||||
char ch;
|
|
||||||
while ((ch = cli_autocomplete(cli, buf)) != '\0') {
|
|
||||||
if (len < CLI_LINE_BUFFER_SIZE - 1) {
|
|
||||||
cli_printf(cli, "%c", ch);
|
|
||||||
buf[len++] = ch;
|
|
||||||
buf[len] = '\0';
|
|
||||||
cursor++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
case '\r':
|
||||||
if (ch >= 0x20 && ch <= 0x7E) {
|
case '\n':
|
||||||
// Printable character
|
// end of line
|
||||||
if (len < CLI_LINE_BUFFER_SIZE - 1) {
|
if (cli->interactive) {
|
||||||
// Insert the character at the cursor
|
cli_printf(cli, "\r\n");
|
||||||
++len;
|
}
|
||||||
memmove(&buf[cursor + 1], &buf[cursor], len - cursor);
|
if (cli->len < CLI_LINE_BUFFER_SIZE) {
|
||||||
buf[cursor] = ch;
|
return 1;
|
||||||
// Print new character and the rest of the line
|
}
|
||||||
if (cli->interactive) {
|
return -1;
|
||||||
cli_printf(cli, "%s", &buf[cursor]);
|
|
||||||
}
|
case '\t':
|
||||||
++cursor;
|
// tab => autocomplete
|
||||||
if (cli->interactive && cursor < len) {
|
if (cli->interactive && cli->len == cli->cursor) {
|
||||||
// Move the cursor back
|
char ch;
|
||||||
cli_printf(cli, "\e[%dD", len - cursor);
|
while ((ch = cli_autocomplete(cli, buf)) != '\0') {
|
||||||
}
|
if (cli->len < CLI_LINE_BUFFER_SIZE - 1) {
|
||||||
|
cli_printf(cli, "%c", ch);
|
||||||
|
buf[cli->len++] = ch;
|
||||||
|
buf[cli->len] = '\0';
|
||||||
|
cli->cursor++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (ch >= 0x20 && ch <= 0x7E) {
|
||||||
|
// Printable character
|
||||||
|
if (cli->len < CLI_LINE_BUFFER_SIZE - 1) {
|
||||||
|
// Insert the character at the cursor
|
||||||
|
++cli->len;
|
||||||
|
memmove(&buf[cli->cursor + 1], &buf[cli->cursor],
|
||||||
|
cli->len - cli->cursor);
|
||||||
|
buf[cli->cursor] = ch;
|
||||||
|
// Print new character and the rest of the line
|
||||||
|
if (cli->interactive) {
|
||||||
|
cli_printf(cli, "%s", &buf[cli->cursor]);
|
||||||
|
}
|
||||||
|
++cli->cursor;
|
||||||
|
if (cli->interactive && cli->cursor < cli->len) {
|
||||||
|
// Move the cursor back
|
||||||
|
cli_printf(cli, "\e[%dD", cli->len - cli->cursor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cli_clear_line(cli_t* cli) {
|
||||||
|
cli->len = 0;
|
||||||
|
cli->cursor = 0;
|
||||||
|
cli->hist_idx = 0;
|
||||||
|
cli->hist_prefix = 0;
|
||||||
|
memset(cli->line_buffer, 0, sizeof(cli->line_buffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Splits the command line into arguments
|
// Splits the command line into arguments
|
||||||
@ -526,82 +533,92 @@ static bool cli_split_args(cli_t* cli) {
|
|||||||
return *cstr_skip_whitespace(buf) == '\0';
|
return *cstr_skip_whitespace(buf) == '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
void cli_run_loop(cli_t* cli) {
|
static void cli_process_command(cli_t* cli, const cli_command_t* cmd) {
|
||||||
while (true) {
|
cli->current_cmd = cmd;
|
||||||
if (cli->interactive) {
|
cli->final_status = false;
|
||||||
if (cli->final_status) {
|
cli->aborted = false;
|
||||||
// Finalize the last command with an empty line
|
|
||||||
cli_printf(cli, "\r\n");
|
// Call the command handler
|
||||||
}
|
cmd->func(cli);
|
||||||
// Print the prompt
|
|
||||||
cli_printf(cli, "> ");
|
if (!cli->final_status) {
|
||||||
}
|
// Command handler hasn't sent final status
|
||||||
|
if (cli->aborted) {
|
||||||
cli->final_status = false;
|
cli_error(cli, CLI_ERROR_ABORT, "");
|
||||||
cli->aborted = false;
|
} else {
|
||||||
|
cli_error(cli, CLI_ERROR_FATAL,
|
||||||
// Read the next line
|
"Command handler didn't finish properly.");
|
||||||
if (!cli_readln(cli)) {
|
|
||||||
cli_error(cli, CLI_ERROR_FATAL, "Input line too long.");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
cli_history_add(cli, cli->line_buffer);
|
|
||||||
|
|
||||||
// Split command line into arguments
|
|
||||||
if (!cli_split_args(cli)) {
|
|
||||||
cli_error(cli, CLI_ERROR_FATAL, "Too many arguments.");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty line?
|
|
||||||
if (*cli->cmd_name == '\0') {
|
|
||||||
// Switch to interactive mode if two empty lines are entered
|
|
||||||
if (++cli->empty_lines >= 2 && !cli->interactive) {
|
|
||||||
cli->interactive = true;
|
|
||||||
// Print the welcome message
|
|
||||||
const cli_command_t* cmd = cli_find_command(cli, "$intro");
|
|
||||||
if (cmd != NULL) {
|
|
||||||
cmd->func(cli);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
cli->empty_lines = 0;
|
|
||||||
|
|
||||||
// Quit interactive mode on `.+ENTER`
|
|
||||||
if ((strcmp(cli->cmd_name, ".") == 0)) {
|
|
||||||
if (cli->interactive) {
|
|
||||||
cli->interactive = false;
|
|
||||||
cli_trace(cli, "Exiting interactive mode...");
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the command handler
|
|
||||||
cli->current_cmd = cli_find_command(cli, cli->cmd_name);
|
|
||||||
|
|
||||||
if (cli->current_cmd == NULL) {
|
|
||||||
cli_error(cli, CLI_ERROR_INVALID_CMD, "Invalid command '%s', try 'help'.",
|
|
||||||
cli->cmd_name);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call the command handler
|
|
||||||
cli->current_cmd->func(cli);
|
|
||||||
|
|
||||||
if (!cli->final_status) {
|
|
||||||
// Command handler hasn't send final status
|
|
||||||
if (cli->aborted) {
|
|
||||||
cli_error(cli, CLI_ERROR_ABORT, "");
|
|
||||||
} else {
|
|
||||||
cli_error(cli, CLI_ERROR_FATAL,
|
|
||||||
"Command handler didn't finish properly.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Finalize the last command with an empty line
|
||||||
|
cli_printf(cli, "\r\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void cli_run(cli_t* cli) {
|
||||||
|
// Read the next line
|
||||||
|
int res = cli_process_char(cli);
|
||||||
|
|
||||||
|
if (res < 0) {
|
||||||
|
cli_error(cli, CLI_ERROR_FATAL, "Input line too long.");
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cli_history_add(cli, cli->line_buffer);
|
||||||
|
|
||||||
|
// Split command line into arguments
|
||||||
|
if (!cli_split_args(cli)) {
|
||||||
|
cli_error(cli, CLI_ERROR_FATAL, "Too many arguments.");
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty line?
|
||||||
|
if (*cli->cmd_name == '\0') {
|
||||||
|
// Switch to interactive mode if two empty lines are entered
|
||||||
|
if (++cli->empty_lines >= 2 && !cli->interactive) {
|
||||||
|
cli->interactive = true;
|
||||||
|
// Print the welcome message
|
||||||
|
const cli_command_t* cmd = cli_find_command(cli, "$intro");
|
||||||
|
if (cmd != NULL) {
|
||||||
|
cmd->func(cli);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
cli->empty_lines = 0;
|
||||||
|
|
||||||
|
// Quit interactive mode on `.+ENTER`
|
||||||
|
if ((strcmp(cli->cmd_name, ".") == 0)) {
|
||||||
|
if (cli->interactive) {
|
||||||
|
cli->interactive = false;
|
||||||
|
cli_trace(cli, "Exiting interactive mode...");
|
||||||
|
}
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the command handler
|
||||||
|
const cli_command_t* cmd = cli_find_command(cli, cli->cmd_name);
|
||||||
|
|
||||||
|
if (cmd == NULL) {
|
||||||
|
cli_error(cli, CLI_ERROR_INVALID_CMD, "Invalid command '%s', try 'help'.",
|
||||||
|
cli->cmd_name);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
cli_process_command(cli, cmd);
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
if (cli->interactive) {
|
||||||
|
// Print the prompt
|
||||||
|
cli_printf(cli, "> ");
|
||||||
|
}
|
||||||
|
cli_clear_line(cli);
|
||||||
|
}
|
||||||
|
|
||||||
// Return position of the argument with the given name in
|
// Return position of the argument with the given name in
|
||||||
// the command definition.
|
// the command definition.
|
||||||
//
|
//
|
||||||
|
@ -90,6 +90,11 @@ struct cli {
|
|||||||
|
|
||||||
// Current line buffer
|
// Current line buffer
|
||||||
char line_buffer[CLI_LINE_BUFFER_SIZE];
|
char line_buffer[CLI_LINE_BUFFER_SIZE];
|
||||||
|
int len; // number of characters in the buffer (excluding '\0')
|
||||||
|
int cursor; // cursor position in the buffer
|
||||||
|
int hist_idx;
|
||||||
|
int hist_prefix;
|
||||||
|
|
||||||
// Command name (pointer to the line buffer)
|
// Command name (pointer to the line buffer)
|
||||||
const char* cmd_name;
|
const char* cmd_name;
|
||||||
// Number of parsed arguments
|
// Number of parsed arguments
|
||||||
@ -123,8 +128,8 @@ bool cli_init(cli_t* cli, cli_read_cb_t read, cli_write_cb_t write,
|
|||||||
void cli_set_commands(cli_t* cli, const cli_command_t* cmd_array,
|
void cli_set_commands(cli_t* cli, const cli_command_t* cmd_array,
|
||||||
size_t cmd_count);
|
size_t cmd_count);
|
||||||
|
|
||||||
// Runs the CLI command loop
|
// Runs the CLI. Only to be called when new data are available.
|
||||||
void cli_run_loop(cli_t* cli);
|
void cli_run(cli_t* cli);
|
||||||
|
|
||||||
// Returne the number of arguments in the command line
|
// Returne the number of arguments in the command line
|
||||||
size_t cli_arg_count(cli_t* cli);
|
size_t cli_arg_count(cli_t* cli);
|
||||||
|
Loading…
Reference in New Issue
Block a user