23#include <goodisplay/gdey029T94.h>
29static const char*
TAG =
"2FA";
34 {
"mod_2fa.title",
"2FA"},
35 {
"mod_2fa.add_account",
"Add Account"},
36 {
"mod_2fa.account_name",
"Account Name"},
37 {
"mod_2fa.secret",
"Secret (Base32)"},
38 {
"mod_2fa.issuer",
"Issuer (optional)"},
39 {
"mod_2fa.digits",
"Digits"},
40 {
"mod_2fa.algorithm",
"Algorithm"},
41 {
"mod_2fa.period",
"Period"},
42 {
"mod_2fa.code",
"Code"},
43 {
"mod_2fa.time_invalid",
"Time not set"},
44 {
"mod_2fa.invalid_input",
"Invalid input"},
45 {
"mod_2fa.hint_edit",
"[3] Edit [N] Back"},
46 {
"mod_2fa.hint_type",
"[Y] Type [3] Edit [N] Back"},
47 {
"mod_2fa.hint_hotp",
"[5] Next [Y] Type [3] Edit [N] Back"},
48 {
"mod_2fa.no_keyboard",
"No keyboard connected"},
49 {
"mod_2fa.type",
"Type"},
50 {
"mod_2fa.counter",
"Counter"},
51 {
"mod_2fa.touch",
"Touch confirm"},
52 {
"mod_2fa.touch_on",
"Required"},
53 {
"mod_2fa.touch_off",
"Not required"},
54 {
"mod_2fa.cr_confirm",
"Allow challenge-response?"},
55 {
"mod_2fa.cr_entry",
"Challenge-Response"},
56 {
"mod_2fa.usb_cr",
"USB slot 2"},
57 {
"mod_2fa.usb_cr_on",
"Designate"},
58 {
"mod_2fa.usb_cr_off",
"No"},
83 for (; token[i] && i + 1 <
sizeof(buf); i++) {
84 buf[i] =
static_cast<char>(std::tolower(
static_cast<unsigned char>(token[i])));
91 int value = atoi(buf);
95 return static_cast<uint8_t
>(value);
104 if (!token || !*token)
return static_cast<uint8_t
>(
OathType::TOTP);
107 for (; token[i] && i + 1 <
sizeof(buf); i++) {
108 buf[i] =
static_cast<char>(std::tolower(
static_cast<unsigned char>(token[i])));
111 if (strcmp(buf,
"totp") == 0)
return static_cast<uint8_t
>(
OathType::TOTP);
112 if (strcmp(buf,
"hotp") == 0)
return static_cast<uint8_t
>(
OathType::HOTP);
113 if (strcmp(buf,
"cr") == 0)
return static_cast<uint8_t
>(
OathType::CR);
114 int value = atoi(buf);
127 if (!slotOut)
return false;
129 if (!store.hasSlotRange())
return false;
135 } ctx = { index, 0, 0,
false };
138 auto* c =
static_cast<Ctx*
>(user);
139 if (c->found)
return;
140 uint16_t logical = 0;
142 if (c->current == c->target) {
156 if (!ctx.found)
return false;
175 auto* c =
static_cast<ListCtx*
>(user);
176 uint16_t logical = 0;
179 const char* typeStr =
"?";
184 default: typeStr =
"TOTP";
break;
208 static const char* usage =
209 "Usage: TOTP ADD <type:totp|hotp> <name> <secret> [issuer] [digits] [period] [algo] [counter]\r\n";
210 char typeBuf[8] = {};
214 char digitsBuf[8] = {};
215 char periodBuf[8] = {};
216 char algoBuf[8] = {};
217 char counterBuf[24] = {};
219 const char* p =
nextToken(args, typeBuf,
sizeof(typeBuf));
220 if (!p || !*typeBuf) {
229 p =
nextToken(p, secret,
sizeof(secret));
230 if (!p || !*secret) {
234 p =
nextToken(p, issuer,
sizeof(issuer));
235 p =
nextToken(p, digitsBuf,
sizeof(digitsBuf));
236 p =
nextToken(p, periodBuf,
sizeof(periodBuf));
237 p =
nextToken(p, algoBuf,
sizeof(algoBuf));
238 p =
nextToken(p, counterBuf,
sizeof(counterBuf));
244 uint64_t counter = counterBuf[0] ? strtoull(counterBuf,
nullptr, 10) : 0;
249 issuer[0] ? issuer :
nullptr,
264 if (!args || !*args) {
268 uint16_t index =
static_cast<uint16_t
>(atoi(args));
286 if (!args || !*args) {
290 uint16_t index =
static_cast<uint16_t
>(atoi(args));
307 const char* issuer = account.
issuer;
311 if (issuer && issuer[0]) {
313 static_cast<unsigned long long>(account.
counter), issuer);
316 static_cast<unsigned long long>(account.
counter));
320 if (issuer && issuer[0]) {
334static int hexDecode(
const char* hex, uint8_t* out,
size_t outMax) {
335 if (!hex || !out)
return -1;
336 size_t len = strlen(hex);
337 if (len == 0 || (len % 2) != 0)
return -1;
338 size_t count = len / 2;
339 if (count > outMax)
return -1;
341 auto nibble = [](
char c) ->
int {
342 if (c >=
'0' && c <=
'9')
return c -
'0';
343 if (c >=
'a' && c <=
'f')
return c -
'a' + 10;
344 if (c >=
'A' && c <=
'F')
return c -
'A' + 10;
347 for (
size_t i = 0; i < count; i++) {
348 int hi = nibble(hex[i * 2]);
349 int lo = nibble(hex[i * 2 + 1]);
350 if (hi < 0 || lo < 0)
return -1;
351 out[i] =
static_cast<uint8_t
>((hi << 4) | lo);
353 return static_cast<int>(count);
367 char hexBuf[2 * 128 + 2] = {};
374 p =
nextToken(p, hexBuf,
sizeof(hexBuf));
383 if (strlen(hexBuf) > 2 * 128) {
388 uint8_t challenge[128] = {};
389 int clen =
hexDecode(hexBuf, challenge,
sizeof(challenge));
397 name, challenge,
static_cast<size_t>(clen), response,
nullptr);
404 for (
int i = 0; i < rlen; i++) {
405 snprintf(hexOut + i * 2, 3,
"%02x", response[i]);
415 {
"ADD",
"<type> <name> <secret> [issuer] [digits] [period] [algo] [counter]",
"Add 2FA entry",
cmd_totp_add},
416 {
"DEL",
"<index>",
"Delete 2FA entry by index",
cmd_totp_del},
417 {
"GET",
"<index>",
"Generate code by index",
cmd_totp_get},
418 {
nullptr,
nullptr,
nullptr,
nullptr},
431 reg.registerCommand({
"TOTP",
432 "2FA authenticator: LIST/ADD/DEL/GET (TOTP+HOTP)",
434 reg.registerCommand({
"CHALRESP",
435 "Challenge-response: <name> <hex-challenge> (CR entries)",
454 strncpy(name_,
name ?
name :
"",
sizeof(name_) - 1);
455 name_[
sizeof(name_) - 1] =
'\0';
469 if (isTotp_ && !timeValid_) {
496 if (!isTotp_)
return;
497 if (nowMs - lastUpdateMs_ >= 1000) {
498 lastUpdateMs_ = nowMs;
510 if (!display)
return;
512 auto* gfx =
static_cast<Gdey029T94*
>(display->getNativeHandle());
516 gfx->fillScreen(EPD_WHITE);
519 gfx->setTextColor(EPD_BLACK);
521 gfx->setCursor(8, 6);
523 gfx->drawFastHLine(0, 22, display->getWidth(), EPD_BLACK);
525 gfx->setCursor(8, 28);
528 gfx->setCursor(8, 40);
533 gfx->setCursor(8, 60);
539 if (isTotp_ && !timeValid_) {
541 gfx->setCursor(8, 60);
551 gfx->getTextBounds(code_, 0, 0, &x1, &y1, &w, &h);
552 int16_t codeX = (display->getWidth() - w) / 2;
553 gfx->setCursor(codeX, 58);
561 const int barW = display->getWidth() - 40;
563 gfx->drawRect(barX, barY, barW, barH, EPD_BLACK);
564 uint32_t period = (period_ == 0) ? 1 : period_;
565 uint32_t rem = (remaining_ > period) ? period : remaining_;
566 uint16_t fillW =
static_cast<uint16_t
>((barW - 2) * rem / period);
567 gfx->fillRect(barX + 1, barY + 1, fillW, barH - 2, EPD_BLACK);
568 gfx->setCursor(barX, barY + 14);
569 display->printf(
"%us",
static_cast<unsigned>(remaining_));
573 snprintf(line,
sizeof(line),
"%s: %llu",
ui::tr(
"mod_2fa.counter"),
574 static_cast<unsigned long long>(counter_));
575 gfx->setCursor(8, 92);
595 if (key ==
'5' && !isTotp_ && !isCr_) {
602 if (key ==
'Y' && !isCr_) {
604 if (kb && kb->isConnected()) {
605 bool valid = isTotp_ ? (timeValid_ && code_[0] !=
'-') : code_[0] !=
'\0';
607 kb->typeString(code_);
622 const char*
getName()
const override {
return "OathCodeView"; }
630 return ui::tr(
"mod_2fa.hint_edit");
633 return ui::tr(
"mod_2fa.hint_hotp");
636 if (kb && kb->isConnected()) {
637 return ui::tr(
"mod_2fa.hint_type");
639 return ui::tr(
"mod_2fa.hint_edit");
657 strncpy(code_,
"------",
sizeof(code_) - 1);
665 strncpy(issuer_, account.
issuer,
sizeof(issuer_) - 1);
666 issuer_[
sizeof(issuer_) - 1] =
'\0';
678 if (generated_)
return;
680 int8_t rc = store.
generateCode(slot_, code_,
sizeof(code_));
682 strncpy(code_,
"------",
sizeof(code_) - 1);
691 strncpy(code_,
"------",
sizeof(code_) - 1);
695 int8_t rem = store.
generateCode(slot_, code_,
sizeof(code_));
697 remaining_ =
static_cast<uint8_t
>(rem);
700 strncpy(code_,
"------",
sizeof(code_) - 1);
709 uint8_t remaining_ = 0;
710 uint32_t period_ = 0;
711 uint64_t counter_ = 0;
714 bool timeValid_ =
false;
715 bool generated_ =
false;
716 uint32_t lastUpdateMs_ = 0;
765 s_t9Input.init(title, initialText, maxLen);
785static void onListSelect(uint16_t index,
void* userData);
788static void onWizardType(uint16_t index,
void* userData);
793static void onWizardAlgo(uint16_t index,
void* userData);
809static void base32Encode(
const uint8_t* data,
size_t dataLen,
char* out,
size_t outMax) {
810 static const char* alphabet =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
811 if (!out || outMax == 0)
return;
814 uint8_t bitsLeft = 0;
816 for (
size_t i = 0; i < dataLen; i++) {
817 buffer = (buffer << 8) | data[i];
819 while (bitsLeft >= 5) {
820 uint8_t index = (buffer >> (bitsLeft - 5)) & 0x1F;
822 if (outPos + 1 >= outMax) {
826 out[outPos++] = alphabet[index];
831 uint8_t index = (buffer << (5 - bitsLeft)) & 0x1F;
832 if (outPos + 1 < outMax) {
833 out[outPos++] = alphabet[index];
845 if (cap == 0)
return false;
879 "2FA list allocation failed");
888 uint16_t logical = 0;
897 s_listItems[idx].userData =
reinterpret_cast<void*
>(
static_cast<uintptr_t
>(logical));
944 {
"TOTP", 0,
false,
nullptr},
945 {
"HOTP", 0,
false,
nullptr},
946 {
"CR", 0,
false,
nullptr}
1027 {
"6", 0,
false,
nullptr},
1028 {
"7", 0,
false,
nullptr},
1029 {
"8", 0,
false,
nullptr}
1043 static const uint8_t digitMap[3] = {6, 7, 8};
1044 s_wizard.digits = digitMap[index % 3];
1057 {
"SHA1", 0,
false,
nullptr},
1058 {
"SHA256", 0,
false,
nullptr},
1059 {
"SHA512", 0,
false,
nullptr}
1074 s_wizard.algorithm =
static_cast<uint8_t
>(index % 3);
1089 {
"30s", 0,
false,
nullptr},
1090 {
"60s", 0,
false,
nullptr}
1104 s_wizard.period = (index == 0) ? 30 : 60;
1116 touchItems[0] = {
ui::tr(
"mod_2fa.touch_on"), 0,
false,
nullptr};
1117 touchItems[1] = {
ui::tr(
"mod_2fa.touch_off"), 0,
false,
nullptr};
1146 usbCrItems[0] = {
ui::tr(
"mod_2fa.usb_cr_on"), 0,
false,
nullptr};
1147 usbCrItems[1] = {
ui::tr(
"mod_2fa.usb_cr_off"), 0,
false,
nullptr};
1228 static TwoFaModule inst;
1237 LOG_I(
TAG,
"Initializing 2FA module");
1242 if (slotRange_.hasRmem) {
1256 LOG_W(
TAG,
"BLE CR init failed (BLE might not be available)");
1296 size_t clen, uint8_t* out) {
1313 uint8_t* out,
bool* touchRequiredOut) {
1352 if (!items || maxItems == 0)
return 0;
1384 if (!out)
return false;
1389 cJSON_AddNumberToObject(out,
"schema_ver",
kSchemaVer);
1390 cJSON* entries = cJSON_AddArrayToObject(out,
"entries");
1391 if (!entries)
return false;
1396 } ctx = { entries, 0 };
1399 auto* c =
static_cast<ExportCtx*
>(user);
1402 uint16_t logical = 0;
1411 cJSON* obj = cJSON_CreateObject();
1414 cJSON_AddStringToObject(obj,
"name", entry.
name);
1415 cJSON_AddStringToObject(obj,
"issuer", entry.
issuer);
1416 cJSON_AddNumberToObject(obj,
"type", entry.
type);
1417 cJSON_AddNumberToObject(obj,
"algorithm", entry.
algorithm);
1418 cJSON_AddNumberToObject(obj,
"digits", entry.
digits);
1419 cJSON_AddNumberToObject(obj,
"period", entry.
period);
1422 cJSON_AddNumberToObject(obj,
"counter",
static_cast<double>(entry.
counter));
1423 cJSON_AddNumberToObject(obj,
"flags", entry.
flags);
1424 cJSON_AddStringToObject(obj,
"secret", b32);
1426 cJSON_AddItemToArray(c->arr, obj);
1436 return ctx.count > 0;
1452 if (!cJSON_IsObject(entry))
return false;
1454 const cJSON* jName = cJSON_GetObjectItemCaseSensitive(entry,
"name");
1455 const cJSON* jIssuer = cJSON_GetObjectItemCaseSensitive(entry,
"issuer");
1456 const cJSON* jType = cJSON_GetObjectItemCaseSensitive(entry,
"type");
1457 const cJSON* jAlgo = cJSON_GetObjectItemCaseSensitive(entry,
"algorithm");
1458 const cJSON* jDigits = cJSON_GetObjectItemCaseSensitive(entry,
"digits");
1459 const cJSON* jPeriod = cJSON_GetObjectItemCaseSensitive(entry,
"period");
1460 const cJSON* jCounter = cJSON_GetObjectItemCaseSensitive(entry,
"counter");
1461 const cJSON* jFlags = cJSON_GetObjectItemCaseSensitive(entry,
"flags");
1462 const cJSON* jSecret = cJSON_GetObjectItemCaseSensitive(entry,
"secret");
1464 if (!cJSON_IsString(jName) || !jName->valuestring || jName->valuestring[0] ==
'\0' ||
1465 !cJSON_IsString(jSecret) || !jSecret->valuestring || jSecret->valuestring[0] ==
'\0' ||
1466 !cJSON_IsNumber(jType) ||
1467 !cJSON_IsNumber(jAlgo) ||
1468 !cJSON_IsNumber(jDigits) ||
1469 !cJSON_IsNumber(jPeriod)) {
1470 LOG_W(
TAG,
"2FA import: skipping malformed entry");
1474 const char*
name = jName->valuestring;
1475 const char* issuer = (cJSON_IsString(jIssuer) && jIssuer->valuestring) ? jIssuer->valuestring :
nullptr;
1476 const char* secret = jSecret->valuestring;
1477 uint8_t type =
static_cast<uint8_t
>(jType->valuedouble);
1478 uint8_t algorithm =
static_cast<uint8_t
>(jAlgo->valuedouble);
1479 uint8_t digits =
static_cast<uint8_t
>(jDigits->valuedouble);
1480 uint32_t period =
static_cast<uint32_t
>(jPeriod->valuedouble);
1481 uint64_t counter = cJSON_IsNumber(jCounter)
1482 ?
static_cast<uint64_t
>(jCounter->valuedouble)
1484 uint8_t
flags = cJSON_IsNumber(jFlags)
1485 ?
static_cast<uint8_t
>(jFlags->valuedouble)
1489 uint16_t existingSlot = 0;
1493 secret, digits, period, algorithm, counter,
flags);
1495 ok = store.
addAccount(type,
name, issuer, secret, digits, period, algorithm, counter,
flags);
1499 LOG_W(
TAG,
"2FA import: failed to store entry '%s'",
name);
1516 const cJSON* schemaVer = cJSON_GetObjectItemCaseSensitive(in,
"schema_ver");
1517 if (cJSON_IsNumber(schemaVer) &&
static_cast<int>(schemaVer->valuedouble) !=
kSchemaVer) {
1518 LOG_W(
TAG,
"2FA backup schema_ver %d != expected %d, skipping",
1519 static_cast<int>(schemaVer->valuedouble),
kSchemaVer);
1523 const cJSON* entries = cJSON_GetObjectItemCaseSensitive(in,
"entries");
1534 auto&
module = cdc::mod_2fa::TwoFaModule::instance();
Internationalization with English fallbacks in code and overlay translations loaded at runtime from a...
char name[cdc::hal::ISecureElement::RMEM_NAME_LEN]
void mod_2fa_register()
Registers 2FA module initializer in the global module registry.
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_W(tag, fmt,...)
#define LOG_I(tag, fmt,...)
Challenge-response provider interface.
static constexpr size_t MAX_RESPONSE_LEN
Largest possible raw HMAC response (SHA256). Callers size out to this.
const char * getName() const override
Returns the module name supplied to the constructor.
void reportModuleError(const char *name, const char *message)
Records and publishes an operational module error by module name.
bool registerModule(IModule *module)
Registers a module instance in the runtime registry.
static ModuleRegistry & instance()
Returns the singleton module registry instance.
void registerInitializer(ModuleInitFunc initFunc)
Registers a deferred module initializer callback.
void clearModuleErrorByName(const char *name)
Clears stored module error by module name.
bool provide(ServiceType type, T *service)
static ServiceRegistry & instance()
Returns singleton service registry instance.
static TropicStorage & instance()
Returns singleton instance of TROPIC metadata cache manager.
bool forEachSlot(uint8_t moduleId, SlotCallback cb, void *ctx)
Iterates all cached slots for one module across its allowed range.
void onEnter(void *context) override
Refreshes code state when the view is entered.
const char * getFooterHint() const override
Returns context-aware footer hint text.
void onTick(uint32_t nowMs) override
Updates the TOTP countdown/code once per second.
ui::InputResult onKey(char key) override
Handles key actions for back, edit, next (HOTP), and typing.
void init(uint16_t slot, const char *name)
Initializes the code view for a specific account slot.
const char * getName() const override
Returns the static view identifier.
void onResume() override
Refreshes code state when the view resumes.
void render(bool partial) override
Renders account metadata, code, and validity/progress UI.
int challengeResponseUsbSlot(const uint8_t *challenge, size_t clen, uint8_t *out, bool *touchRequiredOut=nullptr)
Computes the raw HMAC challenge-response for the USB-CR slot entry.
bool isTimeValid() const
Returns whether system time is considered valid for TOTP.
void clearUsbCrFlagExcept(uint16_t keepSlot)
Clears the USB-CR-slot flag on every entry except keepSlot.
int challengeResponse(const char *entryName, const uint8_t *challenge, size_t clen, uint8_t *out, bool *touchRequiredOut=nullptr)
Computes the raw HMAC challenge-response for a CR entry by name.
static constexpr uint8_t NAME_LEN
bool hasSlotRange() const
bool toLogicalSlot(uint16_t slot, uint16_t *logicalIndexOut) const
bool addAccount(uint8_t type, const char *name, const char *issuer, const char *secretBase32, uint8_t digits, uint32_t period, uint8_t algorithm, uint64_t counter, uint8_t flags=0)
Adds a new OATH entry from a Base32 secret.
static OathStore & instance()
Returns singleton OATH store instance.
static constexpr uint8_t DEFAULT_DIGITS
int8_t generateCode(uint16_t slot, char *codeOut, size_t codeOutLen)
Renders the current code for an entry into codeOut.
uint16_t capacity() const
bool updateAccount(uint16_t slot, uint8_t type, const char *name, const char *issuer, const char *secretBase32, uint8_t digits, uint32_t period, uint8_t algorithm, uint64_t counter, uint8_t flags=0)
Updates an existing OATH entry.
uint16_t rmemStart() const
static constexpr uint32_t DEFAULT_PERIOD
void setSlotRange(const cdc::core::IModule::SlotRange &range)
Configures logical-to-physical slot mapping for OATH entries.
static constexpr uint8_t ISSUER_LEN
bool findByName(const char *name, uint16_t *slotOut) const
Finds a logical slot index by account name.
bool readAccount(uint16_t slot, OathEntry *out)
Reads one OATH entry from secure-element storage.
bool deleteAccount(uint16_t slot)
Deletes account in logical slot.
bool exportBackup(cJSON *out) override
Exports all stored OATH entries into the module's backup section.
uint8_t getMenuItems(core::ModuleMenuItem *items, uint8_t maxItems) override
Provides main-menu entry for the 2FA module.
static TwoFaModule & instance()
Returns singleton 2FA module instance.
void stop() override
Stops the 2FA module and releases list buffers.
core::IModule::BackupResult importBackup(const cJSON *in) override
Restores OATH entries from the module's backup section.
int challengeResponseUsbSlot(const uint8_t *challenge, size_t clen, uint8_t *out, bool *touchRequiredOut) override
Computes the raw HMAC response for the designated USB-CR slot entry.
bool init() override
Initializes module resources, translations, commands, and slot mapping.
void setSlotRange(const core::IModule::SlotRange &range) override
Stores assigned Tropic slot range for the module.
int challengeResponse(const char *entryName, const uint8_t *challenge, size_t clen, uint8_t *out) override
Computes the raw HMAC challenge-response for a named CR entry.
core::IModule::SlotRequest getSlotRequest() const override
Declares minimum slot requirements for the 2FA module.
void onTick(uint32_t nowMs) override
Forwards the BLE CR state machine on the main task.
bool start() override
Starts the 2FA module service.
static void printf(const char *format,...) __attribute__((format(printf
Prints formatted text to console.
static I18n & instance()
Singleton accessor.
void registerEnglishTable(const I18nEntry *entries, std::size_t count)
Append English entries to the lookup table.
void clearDirty() override
void markDirty() override
static ViewStack & instance()
Returns singleton view-stack instance.
void popToAnchor(IView *anchor)
Pops views until the specified anchor view is the current view.
void push(IView *view, void *context=nullptr)
const char * skipSpaces(const char *s)
Advances over leading ASCII whitespace in a C string.
IKeyboardProvider * getKeyboard()
const char * nextToken(const char *s, char *out, size_t outSize)
Extracts one whitespace-delimited token from a string.
IDisplay * getDisplayInstance()
Returns lazily created singleton display instance.
Per-entry flag bits stored in OathEntry::flags.
constexpr uint8_t USB_CR_SLOT
Designate this CR entry as the USB OTP-HID slot-2 responder.
constexpr uint8_t TOUCH_REQUIRED
Require an on-device touch confirmation before answering a CR request.
static void onWizardAlgo(uint16_t index, void *userData)
Saves selected algorithm; opens period step (TOTP) or finishes (HOTP).
static WizardState s_wizard
static void wizardEdit(uint16_t slot)
OATH code detail view implementation.
static uint8_t parseType(const char *token)
Parses a textual or numeric entry-type token.
static void cmd_chalresp(const char *args)
Serial command computing a raw challenge-response for a named CR entry.
static ui::ListView s_digitsMenu
static void freeListBuffers()
Frees dynamically allocated list buffers used by the account list.
static bool ensureListBuffers()
Ensures account list backing buffers are allocated for current capacity.
static constexpr const char * CMD_MODULE
Serial command handlers for 2FA module.
static void rebuildList()
Rebuilds the account list view content from Tropic storage cache.
static ui::ListItem * s_listItems
Dynamic list buffers released by freeListBuffers.
static bool findSlotByIndex(uint16_t index, uint16_t *slotOut)
Resolves a displayed list index to the logical OATH slot number.
static uint8_t parseAlgo(const char *token)
Parses textual or numeric algorithm identifiers into store values.
OathType
OATH entry type discriminator.
static void cmd_totp_list(const char *args)
Serial command handler printing all configured OATH entries.
void ble_chalresp_deinit()
Tears down the BLE CR subsystem and removes GATT callbacks.
static constexpr int kSchemaVer
Schema version written to and expected from the 2FA backup section.
static void onWizardType(uint16_t index, void *userData)
Saves selected entry type and opens the name step.
static void onWizardSecret(const char *text)
Saves wizard secret and opens issuer step.
static void pushAlgoStep()
Pushes the algorithm-selection step.
static ui::ListView s_listView
2FA module UI state.
static ui::ListView s_touchMenu
bool ble_chalresp_init()
BLE GATT challenge-response transport.
static ui::ListView s_periodMenu
static void cmd_totp(const char *args)
static constexpr size_t SECRET_B32_LEN
static void registerCommands()
Registers serial commands exposed by the 2FA module.
static bool importOathEntry(const cJSON *entry, void *user)
Maps and upserts one OATH entry from its JSON representation.
static void wizardStart()
Starts the add-account wizard with default values.
static void onWizardIssuer(const char *text)
Saves wizard issuer and opens digit-selection step.
static void wizardFinish()
Validates wizard data and persists account changes.
constexpr ui::I18nEntry kStrings[]
static void pushT9WizardStep(const char *title, const char *initialText, uint16_t maxLen, ui::T9InputView::SaveCallback onSave)
Pushes a configured T9 input step for the account wizard flow.
static ui::ListView s_typeMenu
const char * nextToken(const char *s, char *out, size_t outSize)
Extracts one whitespace-delimited token from a string.
static void cmd_totp_add(const char *args)
Serial command handler adding an OATH entry from tokens.
static void onListSelect(uint16_t index, void *userData)
Handles selection from the account list.
static void onWizardPeriod(uint16_t index, void *userData)
Saves selected period and finalizes add/edit operation.
static const cdc::serial::SubCommand kTotpSubs[]
Sub-command table for the TOTP serial command group.
static bool s_commandsRegistered
static void onWizardTouch(uint16_t index, void *userData)
Saves the CR touch-confirm choice and finalizes the entry.
static void cmd_totp_get(const char *args)
Serial command handler generating one code by index.
static uint16_t s_capacity
static OathCodeView s_codeView
static void onWizardUsbCr(uint16_t index, void *userData)
Saves the USB-CR-slot designation and finalizes the entry.
static constexpr size_t kBase32BufLen
Maximum Base32 string length for the longest supported secret (64 bytes raw).
static void pushTouchStep()
Pushes the CR touch-confirm toggle step.
static int hexDecode(const char *hex, uint8_t *out, size_t outMax)
Decodes a hex string into bytes.
static void onWizardDigits(uint16_t index, void *userData)
Saves selected code length and opens algorithm-selection step.
static void registerStrings()
static void onWizardName(const char *text)
Saves wizard account name and opens secret step.
static bool s_viewsInitialized
static ui::T9InputView s_t9Input
static ui::ListView s_usbCrMenu
static char(* s_listLabels)[24]
static uint16_t * s_listSlots
static ui::ListView s_algoMenu
static uint16_t s_accountCount
static void base32Encode(const uint8_t *data, size_t dataLen, char *out, size_t outMax)
Encodes binary secret bytes into unpadded Base32 text.
static void cmd_totp_del(const char *args)
Serial command handler deleting an OATH entry by index.
void ble_chalresp_tick(uint32_t nowMs)
Main-task tick: processes a pending challenge (confirm + notify).
static void pushUsbCrStep()
Pushes the CR USB-slot-2 designation step.
ICommandRegistry & getCommandRegistry()
Returns singleton command-registry interface.
void dispatchSubCommand(const char *parent, const char *args, const SubCommand *table)
Routes a sub-command line to its handler.
void printText(Gdey029T94 *gfx, const char *text)
Draws CP437 text with the built-in 6x8 glyph font, byte-for-byte.
const char * tr(const char *key)
Look up a translation by string key.
cdc::core::IModule::BackupResult importJsonArray(const cJSON *array, BackupEntryHandler handler, void *user)
Iterates a JSON backup array best-effort and tallies the outcome.
void showToastSuccess(const char *message, uint16_t durationMs=1500)
Shows a success toast message.
void showToastError(const char *message, uint16_t durationMs=1500)
Shows an error toast message.
Per-module restore outcome reported by importBackup().
Unified OATH credential record (TOTP, HOTP, and reserved CR).
uint8_t digits
Output digit count (TOTP/HOTP).
uint8_t secret[64]
Raw HMAC key.
uint8_t algorithm
OathAlgorithm value.
uint8_t secretLen
Valid bytes in secret.
uint64_t counter
Moving factor (HOTP only).
uint8_t type
OathType discriminator.
uint32_t period
TOTP step in seconds (TOTP only).
char issuer[32+1]
Optional issuer text.
char name[16+1]
Account label.
uint8_t flags
Reserved entry flags.
char name[OathStore::NAME_LEN+1]
char secret[SECRET_B32_LEN]
char issuer[OathStore::ISSUER_LEN+1]
Single English translation entry.