v0.0.29 - Feather firmware: auto-approve sign_event and always return explicit JSON-RPC errors for unhandled/oversized requests
This commit is contained in:
680
firmware/feather_s3_tft/main/ui.c
Normal file
680
firmware/feather_s3_tft/main/ui.c
Normal file
@@ -0,0 +1,680 @@
|
||||
#include "ui.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "buttons.h"
|
||||
#include "display.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "mnemonic.h"
|
||||
#include "secure_mem.h"
|
||||
|
||||
typedef enum {
|
||||
UI_MAIN_GENERATE = 0,
|
||||
UI_MAIN_ENTER,
|
||||
UI_MAIN_VIEW,
|
||||
} ui_main_menu_item_t;
|
||||
|
||||
#define UI_CHAR_W_SMALL 6
|
||||
#define UI_CHAR_W_LARGE 12
|
||||
|
||||
typedef struct {
|
||||
size_t count;
|
||||
size_t first_idx[3];
|
||||
int exact_idx;
|
||||
} prefix_matches_t;
|
||||
|
||||
typedef struct {
|
||||
char phrase[256];
|
||||
bool has_phrase;
|
||||
|
||||
ui_main_menu_item_t main_sel;
|
||||
|
||||
int letter_idx; /* 0..25 */
|
||||
char prefix[12];
|
||||
size_t prefix_len;
|
||||
|
||||
char entered_words[12][12];
|
||||
size_t entered_count;
|
||||
} ui_state_t;
|
||||
|
||||
static btn_event_t wait_any_button(void)
|
||||
{
|
||||
btn_event_t evt;
|
||||
do {
|
||||
evt = buttons_wait(0xFFFFFFFFu);
|
||||
} while (evt.kind == BTN_EVT_NONE);
|
||||
return evt;
|
||||
}
|
||||
|
||||
static void draw_splash(void)
|
||||
{
|
||||
ESP_ERROR_CHECK(display_clear(RGB565_BLACK));
|
||||
ESP_ERROR_CHECK(display_draw_text_centered(60, "N_SIGNER", RGB565_RED, RGB565_BLACK));
|
||||
}
|
||||
|
||||
static void draw_main_menu_row(bool has_phrase, int row_idx, bool selected)
|
||||
{
|
||||
static const char *items[3] = {"Generate", "Enter", "View"};
|
||||
static const int ys[3] = {28, 52, 76};
|
||||
bool disabled = (row_idx == UI_MAIN_VIEW && !has_phrase);
|
||||
char line[24];
|
||||
|
||||
snprintf(line, sizeof(line), "%s%s", selected ? "> " : " ", items[row_idx]);
|
||||
ESP_ERROR_CHECK(display_fill_rect(24, ys[row_idx], 200, 16, RGB565_BLACK));
|
||||
ESP_ERROR_CHECK(display_draw_text(
|
||||
24,
|
||||
ys[row_idx],
|
||||
line,
|
||||
disabled ? RGB565_DIM_GRAY : (selected ? RGB565_RED : RGB565_WHITE),
|
||||
RGB565_BLACK));
|
||||
}
|
||||
|
||||
static void draw_main_menu_full(const ui_state_t *st)
|
||||
{
|
||||
ESP_ERROR_CHECK(display_clear(RGB565_BLACK));
|
||||
ESP_ERROR_CHECK(display_draw_text_centered(4, "MAIN MENU", RGB565_WHITE, RGB565_BLACK));
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
draw_main_menu_row(st->has_phrase, i, ((int)st->main_sel == i));
|
||||
}
|
||||
|
||||
ESP_ERROR_CHECK(display_draw_text_small(12, 120, "D2 up D1 dn D0 ok", RGB565_DIM_GRAY, RGB565_BLACK));
|
||||
}
|
||||
|
||||
static void draw_main_menu_selection_change(bool has_phrase, int prev_sel, int new_sel)
|
||||
{
|
||||
draw_main_menu_row(has_phrase, prev_sel, false);
|
||||
draw_main_menu_row(has_phrase, new_sel, true);
|
||||
}
|
||||
|
||||
static void split_phrase_words(const char *phrase, char words[12][12])
|
||||
{
|
||||
char buf[256];
|
||||
char *tok;
|
||||
size_t i = 0;
|
||||
|
||||
memset(words, 0, 12 * 12);
|
||||
|
||||
if (phrase == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
strncpy(buf, phrase, sizeof(buf) - 1);
|
||||
buf[sizeof(buf) - 1] = '\0';
|
||||
|
||||
tok = strtok(buf, " \t\r\n");
|
||||
while (tok != NULL && i < 12) {
|
||||
strncpy(words[i], tok, 11);
|
||||
words[i][11] = '\0';
|
||||
i++;
|
||||
tok = strtok(NULL, " \t\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_word_grid(const char words[12][12], int highlight_idx)
|
||||
{
|
||||
const int col_w = 80;
|
||||
const int row_h = 24;
|
||||
|
||||
for (int i = 0; i < 12; ++i) {
|
||||
int row = i / 3;
|
||||
int col = i % 3;
|
||||
int x = col * col_w + 2;
|
||||
int y = 14 + row * row_h;
|
||||
char line[20];
|
||||
|
||||
snprintf(line, sizeof(line), "%2d. %.11s", i + 1, words[i]);
|
||||
ESP_ERROR_CHECK(display_draw_text_small(
|
||||
x,
|
||||
y,
|
||||
line,
|
||||
(i == highlight_idx) ? RGB565_RED : RGB565_WHITE,
|
||||
RGB565_BLACK));
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_view_page(const ui_state_t *st)
|
||||
{
|
||||
char words[12][12];
|
||||
|
||||
split_phrase_words(st->phrase, words);
|
||||
|
||||
ESP_ERROR_CHECK(display_clear(RGB565_BLACK));
|
||||
ESP_ERROR_CHECK(display_draw_text_small_centered(2, "YOUR MNEMONIC", RGB565_WHITE, RGB565_BLACK));
|
||||
draw_word_grid(words, -1);
|
||||
ESP_ERROR_CHECK(display_draw_text_small_centered(120, "any key = back", RGB565_DIM_GRAY, RGB565_BLACK));
|
||||
}
|
||||
|
||||
static void draw_generate_page(void)
|
||||
{
|
||||
ESP_ERROR_CHECK(display_clear(RGB565_BLACK));
|
||||
ESP_ERROR_CHECK(display_draw_text_centered(56, "GENERATING...", RGB565_WHITE, RGB565_BLACK));
|
||||
}
|
||||
|
||||
static void clear_enter_words(ui_state_t *st)
|
||||
{
|
||||
secure_memzero(st->entered_words, sizeof(st->entered_words));
|
||||
st->entered_count = 0;
|
||||
st->prefix_len = 0;
|
||||
st->prefix[0] = '\0';
|
||||
}
|
||||
|
||||
static void letter_cell_pos(int idx, int *x, int *y)
|
||||
{
|
||||
int row = (idx < 13) ? 0 : 1;
|
||||
int col = (idx < 13) ? idx : (idx - 13);
|
||||
*x = 10 + col * 16;
|
||||
*y = row ? 68 : 44;
|
||||
}
|
||||
|
||||
static void compute_allowed_next_letters(const ui_state_t *st, bool allowed[26])
|
||||
{
|
||||
for (int i = 0; i < 26; ++i) {
|
||||
allowed[i] = false;
|
||||
}
|
||||
|
||||
if (st->prefix_len == 0) {
|
||||
for (int i = 0; i < 26; ++i) {
|
||||
allowed[i] = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 2048; ++i) {
|
||||
const char *w = mnemonic_word_at(i);
|
||||
size_t wlen;
|
||||
char c;
|
||||
int idx;
|
||||
|
||||
if (w == NULL) {
|
||||
continue;
|
||||
}
|
||||
if (strncmp(w, st->prefix, st->prefix_len) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
wlen = strlen(w);
|
||||
if (wlen <= st->prefix_len) {
|
||||
continue;
|
||||
}
|
||||
|
||||
c = w[st->prefix_len];
|
||||
if (c < 'a' || c > 'z') {
|
||||
continue;
|
||||
}
|
||||
|
||||
idx = (int)(c - 'a');
|
||||
allowed[idx] = true;
|
||||
}
|
||||
}
|
||||
|
||||
static int find_next_allowed_letter(int current, int delta, const bool allowed[26])
|
||||
{
|
||||
int idx = current;
|
||||
|
||||
for (int i = 0; i < 26; ++i) {
|
||||
idx = (idx + delta + 26) % 26;
|
||||
if (allowed[idx]) {
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
static int first_allowed_letter(const bool allowed[26])
|
||||
{
|
||||
for (int i = 0; i < 26; ++i) {
|
||||
if (allowed[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void draw_enter_letter_cell(int idx, bool selected, const bool allowed[26])
|
||||
{
|
||||
int x;
|
||||
int y;
|
||||
char c[2] = {(char)('A' + idx), '\0'};
|
||||
uint16_t color = RGB565_WHITE;
|
||||
|
||||
letter_cell_pos(idx, &x, &y);
|
||||
ESP_ERROR_CHECK(display_fill_rect(x, y, 12, 16, RGB565_BLACK));
|
||||
|
||||
if (!allowed[idx]) {
|
||||
color = RGB565_DIM_GRAY;
|
||||
}
|
||||
if (selected && allowed[idx]) {
|
||||
color = RGB565_RED;
|
||||
}
|
||||
|
||||
ESP_ERROR_CHECK(display_draw_text(x, y, c, color, RGB565_BLACK));
|
||||
}
|
||||
|
||||
static void draw_enter_letter_cursor_change(int prev_idx, int new_idx, const bool allowed[26])
|
||||
{
|
||||
draw_enter_letter_cell(prev_idx, false, allowed);
|
||||
draw_enter_letter_cell(new_idx, true, allowed);
|
||||
}
|
||||
|
||||
static void compute_prefix_matches(const ui_state_t *st, prefix_matches_t *m)
|
||||
{
|
||||
m->count = 0;
|
||||
m->first_idx[0] = 0;
|
||||
m->first_idx[1] = 0;
|
||||
m->first_idx[2] = 0;
|
||||
m->exact_idx = -1;
|
||||
|
||||
if (st->prefix_len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 2048; ++i) {
|
||||
const char *w = mnemonic_word_at(i);
|
||||
if (w == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strncmp(w, st->prefix, st->prefix_len) == 0) {
|
||||
if (m->count < 3) {
|
||||
m->first_idx[m->count] = i;
|
||||
}
|
||||
m->count++;
|
||||
if (strcmp(w, st->prefix) == 0 && m->exact_idx < 0) {
|
||||
m->exact_idx = (int)i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_enter_compact_static(const ui_state_t *st, const bool allowed[26])
|
||||
{
|
||||
char hdr[24];
|
||||
unsigned current_word = (unsigned)(st->entered_count + 1);
|
||||
|
||||
ESP_ERROR_CHECK(display_clear(RGB565_BLACK));
|
||||
|
||||
if (current_word > 12) {
|
||||
current_word = 12;
|
||||
}
|
||||
snprintf(hdr, sizeof(hdr), "WORD %u/12", current_word);
|
||||
ESP_ERROR_CHECK(display_draw_text(10, 8, hdr, RGB565_WHITE, RGB565_BLACK));
|
||||
|
||||
for (int i = 0; i < 26; ++i) {
|
||||
draw_enter_letter_cell(i, i == st->letter_idx, allowed);
|
||||
}
|
||||
}
|
||||
|
||||
static int draw_word_with_prefix_large(int x, int y, const char *word, size_t prefix_len)
|
||||
{
|
||||
char pref[12];
|
||||
char rest[12];
|
||||
size_t wlen;
|
||||
size_t plen;
|
||||
|
||||
if (word == NULL) {
|
||||
return x;
|
||||
}
|
||||
|
||||
wlen = strlen(word);
|
||||
plen = (prefix_len > wlen) ? wlen : prefix_len;
|
||||
|
||||
memset(pref, 0, sizeof(pref));
|
||||
memset(rest, 0, sizeof(rest));
|
||||
|
||||
if (plen > 0) {
|
||||
memcpy(pref, word, plen);
|
||||
}
|
||||
if (wlen > plen) {
|
||||
memcpy(rest, word + plen, wlen - plen);
|
||||
}
|
||||
|
||||
ESP_ERROR_CHECK(display_draw_text(x, y, pref, RGB565_RED, RGB565_BLACK));
|
||||
ESP_ERROR_CHECK(display_draw_text(x + ((int)plen * UI_CHAR_W_LARGE), y, rest, RGB565_WHITE, RGB565_BLACK));
|
||||
|
||||
return x + ((int)wlen * UI_CHAR_W_LARGE);
|
||||
}
|
||||
|
||||
static void draw_match_row_large(const ui_state_t *st)
|
||||
{
|
||||
int x = 4;
|
||||
|
||||
for (size_t i = 0; i < 2048; ++i) {
|
||||
const char *w = mnemonic_word_at(i);
|
||||
size_t wlen;
|
||||
int needed;
|
||||
|
||||
if (w == NULL) {
|
||||
continue;
|
||||
}
|
||||
if (strncmp(w, st->prefix, st->prefix_len) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
wlen = strlen(w);
|
||||
needed = (int)(wlen * UI_CHAR_W_LARGE) + UI_CHAR_W_LARGE;
|
||||
if (x + needed > DISPLAY_WIDTH) {
|
||||
break;
|
||||
}
|
||||
|
||||
x = draw_word_with_prefix_large(x, 88, w, st->prefix_len);
|
||||
ESP_ERROR_CHECK(display_draw_text(x, 88, " ", RGB565_WHITE, RGB565_BLACK));
|
||||
x += UI_CHAR_W_LARGE;
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_enter_compact_dynamic(const ui_state_t *st, const prefix_matches_t *m)
|
||||
{
|
||||
ESP_ERROR_CHECK(display_fill_rect(0, 86, DISPLAY_WIDTH, 49, RGB565_BLACK));
|
||||
|
||||
if (st->prefix_len == 0) {
|
||||
ESP_ERROR_CHECK(display_draw_text_small(4, 118, "D0=letter D1h=back D2h=del", RGB565_DIM_GRAY, RGB565_BLACK));
|
||||
return;
|
||||
}
|
||||
|
||||
if (m->count == 0) {
|
||||
ESP_ERROR_CHECK(display_draw_text_centered(88, "NO MATCH", RGB565_RED, RGB565_BLACK));
|
||||
} else {
|
||||
draw_match_row_large(st);
|
||||
}
|
||||
|
||||
if (m->count == 1) {
|
||||
ESP_ERROR_CHECK(display_draw_text_small(4, 108, "One word left: D0=YES D2=NO", RGB565_RED, RGB565_BLACK));
|
||||
}
|
||||
|
||||
if (st->entered_count < 12) {
|
||||
ESP_ERROR_CHECK(display_draw_text_small(4, 118, "D0=letter D1h=back D2h=del", RGB565_DIM_GRAY, RGB565_BLACK));
|
||||
} else {
|
||||
ESP_ERROR_CHECK(display_draw_text_small(4, 118, "12/12: D0 hold=save D2 hold=del", RGB565_DIM_GRAY, RGB565_BLACK));
|
||||
}
|
||||
}
|
||||
|
||||
static int build_phrase_from_entered(ui_state_t *st)
|
||||
{
|
||||
size_t cursor = 0;
|
||||
|
||||
st->phrase[0] = '\0';
|
||||
for (size_t i = 0; i < st->entered_count; ++i) {
|
||||
size_t wlen = strlen(st->entered_words[i]);
|
||||
|
||||
if (i > 0) {
|
||||
if (cursor + 1 >= sizeof(st->phrase)) {
|
||||
return -1;
|
||||
}
|
||||
st->phrase[cursor++] = ' ';
|
||||
}
|
||||
|
||||
if (cursor + wlen >= sizeof(st->phrase)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(st->phrase + cursor, st->entered_words[i], wlen);
|
||||
cursor += wlen;
|
||||
st->phrase[cursor] = '\0';
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ui_run_until_working(char out_mnemonic[256])
|
||||
{
|
||||
ui_state_t st;
|
||||
bool main_menu_drawn = false;
|
||||
|
||||
if (out_mnemonic == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(&st, 0, sizeof(st));
|
||||
st.main_sel = UI_MAIN_GENERATE;
|
||||
|
||||
draw_splash();
|
||||
(void)wait_any_button();
|
||||
|
||||
while (1) {
|
||||
btn_event_t evt;
|
||||
|
||||
if (!main_menu_drawn) {
|
||||
draw_main_menu_full(&st);
|
||||
main_menu_drawn = true;
|
||||
}
|
||||
|
||||
evt = wait_any_button();
|
||||
|
||||
if (evt.id == BTN_D2) {
|
||||
int prev = (int)st.main_sel;
|
||||
if (st.has_phrase) {
|
||||
st.main_sel = (st.main_sel == UI_MAIN_GENERATE) ? UI_MAIN_VIEW : (ui_main_menu_item_t)((int)st.main_sel - 1);
|
||||
} else {
|
||||
st.main_sel = (st.main_sel == UI_MAIN_GENERATE) ? UI_MAIN_ENTER : UI_MAIN_GENERATE;
|
||||
}
|
||||
if ((int)st.main_sel != prev) {
|
||||
draw_main_menu_selection_change(st.has_phrase, prev, (int)st.main_sel);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (evt.id == BTN_D1) {
|
||||
int prev = (int)st.main_sel;
|
||||
if (st.has_phrase) {
|
||||
st.main_sel = (st.main_sel == UI_MAIN_VIEW) ? UI_MAIN_GENERATE : (ui_main_menu_item_t)((int)st.main_sel + 1);
|
||||
} else {
|
||||
st.main_sel = (st.main_sel == UI_MAIN_ENTER) ? UI_MAIN_GENERATE : UI_MAIN_ENTER;
|
||||
}
|
||||
if ((int)st.main_sel != prev) {
|
||||
draw_main_menu_selection_change(st.has_phrase, prev, (int)st.main_sel);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (evt.id != BTN_D0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (st.main_sel == UI_MAIN_VIEW) {
|
||||
if (!st.has_phrase) {
|
||||
continue;
|
||||
}
|
||||
|
||||
draw_view_page(&st);
|
||||
(void)wait_any_button();
|
||||
main_menu_drawn = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (st.main_sel == UI_MAIN_GENERATE) {
|
||||
draw_generate_page();
|
||||
if (generate_mnemonic_12(st.phrase, sizeof(st.phrase)) != 0) {
|
||||
ESP_ERROR_CHECK(display_clear(RGB565_BLACK));
|
||||
ESP_ERROR_CHECK(display_draw_text_centered(56, "GEN FAILED", RGB565_RED, RGB565_BLACK));
|
||||
vTaskDelay(pdMS_TO_TICKS(2000));
|
||||
main_menu_drawn = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
st.has_phrase = true;
|
||||
draw_view_page(&st);
|
||||
(void)wait_any_button();
|
||||
|
||||
strncpy(out_mnemonic, st.phrase, 255);
|
||||
out_mnemonic[255] = '\0';
|
||||
secure_memzero(&st, sizeof(st));
|
||||
return 0;
|
||||
}
|
||||
|
||||
clear_enter_words(&st);
|
||||
st.letter_idx = 0;
|
||||
|
||||
{
|
||||
prefix_matches_t matches;
|
||||
bool allowed_letters[26];
|
||||
|
||||
compute_prefix_matches(&st, &matches);
|
||||
compute_allowed_next_letters(&st, allowed_letters);
|
||||
st.letter_idx = first_allowed_letter(allowed_letters);
|
||||
draw_enter_compact_static(&st, allowed_letters);
|
||||
draw_enter_compact_dynamic(&st, &matches);
|
||||
|
||||
while (1) {
|
||||
evt = wait_any_button();
|
||||
|
||||
if (evt.id == BTN_D2 && evt.kind == BTN_EVT_PRESS) {
|
||||
if (matches.count == 1 && st.prefix_len > 0) {
|
||||
st.prefix_len = 0;
|
||||
st.prefix[0] = '\0';
|
||||
compute_prefix_matches(&st, &matches);
|
||||
draw_enter_compact_dynamic(&st, &matches);
|
||||
} else {
|
||||
int prev_idx = st.letter_idx;
|
||||
st.letter_idx = find_next_allowed_letter(st.letter_idx, -1, allowed_letters);
|
||||
draw_enter_letter_cursor_change(prev_idx, st.letter_idx, allowed_letters);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (evt.id == BTN_D1 && evt.kind == BTN_EVT_PRESS) {
|
||||
int prev_idx = st.letter_idx;
|
||||
st.letter_idx = find_next_allowed_letter(st.letter_idx, +1, allowed_letters);
|
||||
draw_enter_letter_cursor_change(prev_idx, st.letter_idx, allowed_letters);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (evt.id == BTN_D1 && evt.kind == BTN_EVT_LONG_PRESS) {
|
||||
if (st.prefix_len > 0) {
|
||||
st.prefix_len--;
|
||||
st.prefix[st.prefix_len] = '\0';
|
||||
}
|
||||
compute_prefix_matches(&st, &matches);
|
||||
compute_allowed_next_letters(&st, allowed_letters);
|
||||
st.letter_idx = allowed_letters[st.letter_idx] ? st.letter_idx : first_allowed_letter(allowed_letters);
|
||||
draw_enter_compact_static(&st, allowed_letters);
|
||||
draw_enter_compact_dynamic(&st, &matches);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (evt.id == BTN_D2 && evt.kind == BTN_EVT_LONG_PRESS) {
|
||||
if (st.prefix_len > 0) {
|
||||
st.prefix_len = 0;
|
||||
st.prefix[0] = '\0';
|
||||
} else if (st.entered_count > 0) {
|
||||
secure_memzero(st.entered_words[st.entered_count - 1], sizeof(st.entered_words[0]));
|
||||
st.entered_count--;
|
||||
}
|
||||
compute_prefix_matches(&st, &matches);
|
||||
compute_allowed_next_letters(&st, allowed_letters);
|
||||
st.letter_idx = allowed_letters[st.letter_idx] ? st.letter_idx : first_allowed_letter(allowed_letters);
|
||||
draw_enter_compact_static(&st, allowed_letters);
|
||||
draw_enter_compact_dynamic(&st, &matches);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (evt.id == BTN_D0 && evt.kind == BTN_EVT_PRESS) {
|
||||
if (st.entered_count >= 12) {
|
||||
if (build_phrase_from_entered(&st) == 0 && mnemonic_validate(st.phrase) == 0) {
|
||||
draw_view_page(&st);
|
||||
(void)wait_any_button();
|
||||
strncpy(out_mnemonic, st.phrase, 255);
|
||||
out_mnemonic[255] = '\0';
|
||||
secure_memzero(&st, sizeof(st));
|
||||
return 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (matches.count == 1 && st.prefix_len > 0) {
|
||||
const char *w = mnemonic_word_at(matches.first_idx[0]);
|
||||
if (w != NULL) {
|
||||
strncpy(st.entered_words[st.entered_count], w, 11);
|
||||
st.entered_words[st.entered_count][11] = '\0';
|
||||
st.entered_count++;
|
||||
}
|
||||
st.prefix_len = 0;
|
||||
st.prefix[0] = '\0';
|
||||
|
||||
if (st.entered_count == 12) {
|
||||
if (build_phrase_from_entered(&st) != 0 || mnemonic_validate(st.phrase) != 0) {
|
||||
ESP_ERROR_CHECK(display_clear(RGB565_BLACK));
|
||||
ESP_ERROR_CHECK(display_draw_text_centered(56, "BAD CHECKSUM", RGB565_RED, RGB565_BLACK));
|
||||
vTaskDelay(pdMS_TO_TICKS(1200));
|
||||
compute_prefix_matches(&st, &matches);
|
||||
compute_allowed_next_letters(&st, allowed_letters);
|
||||
st.letter_idx = allowed_letters[st.letter_idx] ? st.letter_idx : first_allowed_letter(allowed_letters);
|
||||
draw_enter_compact_static(&st, allowed_letters);
|
||||
draw_enter_compact_dynamic(&st, &matches);
|
||||
continue;
|
||||
}
|
||||
|
||||
draw_view_page(&st);
|
||||
(void)wait_any_button();
|
||||
strncpy(out_mnemonic, st.phrase, 255);
|
||||
out_mnemonic[255] = '\0';
|
||||
secure_memzero(&st, sizeof(st));
|
||||
return 0;
|
||||
}
|
||||
|
||||
compute_prefix_matches(&st, &matches);
|
||||
compute_allowed_next_letters(&st, allowed_letters);
|
||||
st.letter_idx = first_allowed_letter(allowed_letters);
|
||||
draw_enter_compact_static(&st, allowed_letters);
|
||||
draw_enter_compact_dynamic(&st, &matches);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (st.prefix_len + 1 < sizeof(st.prefix)) {
|
||||
st.prefix[st.prefix_len++] = (char)('a' + st.letter_idx);
|
||||
st.prefix[st.prefix_len] = '\0';
|
||||
|
||||
compute_prefix_matches(&st, &matches);
|
||||
if (matches.count == 0) {
|
||||
st.prefix_len--;
|
||||
st.prefix[st.prefix_len] = '\0';
|
||||
compute_prefix_matches(&st, &matches);
|
||||
}
|
||||
compute_allowed_next_letters(&st, allowed_letters);
|
||||
st.letter_idx = allowed_letters[st.letter_idx] ? st.letter_idx : first_allowed_letter(allowed_letters);
|
||||
draw_enter_compact_static(&st, allowed_letters);
|
||||
draw_enter_compact_dynamic(&st, &matches);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (evt.id == BTN_D0 && evt.kind == BTN_EVT_LONG_PRESS) {
|
||||
compute_prefix_matches(&st, &matches);
|
||||
|
||||
if (st.entered_count == 12 && st.prefix_len == 0) {
|
||||
if (build_phrase_from_entered(&st) != 0 || mnemonic_validate(st.phrase) != 0) {
|
||||
ESP_ERROR_CHECK(display_clear(RGB565_BLACK));
|
||||
ESP_ERROR_CHECK(display_draw_text_centered(56, "BAD CHECKSUM", RGB565_RED, RGB565_BLACK));
|
||||
vTaskDelay(pdMS_TO_TICKS(1200));
|
||||
compute_prefix_matches(&st, &matches);
|
||||
compute_allowed_next_letters(&st, allowed_letters);
|
||||
st.letter_idx = allowed_letters[st.letter_idx] ? st.letter_idx : first_allowed_letter(allowed_letters);
|
||||
draw_enter_compact_static(&st, allowed_letters);
|
||||
draw_enter_compact_dynamic(&st, &matches);
|
||||
continue;
|
||||
}
|
||||
|
||||
strncpy(out_mnemonic, st.phrase, 255);
|
||||
out_mnemonic[255] = '\0';
|
||||
secure_memzero(&st, sizeof(st));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (st.prefix_len == 0) {
|
||||
clear_enter_words(&st);
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main_menu_drawn = false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user