ESP32: доработать HOME экран и поток секретов
This commit is contained in:
parent
f4e7210a40
commit
1488bc3d6d
@ -3,11 +3,12 @@
|
|||||||
- Краткое описание: минимальный UI-прототип для сабсервера на базе `LVGL + subserver touch`, с Wi-Fi flow, серверными адресами и общим экраном редактирования текста.
|
- Краткое описание: минимальный UI-прототип для сабсервера на базе `LVGL + subserver touch`, с Wi-Fi flow, серверными адресами и общим экраном редактирования текста.
|
||||||
- Что проверять:
|
- Что проверять:
|
||||||
- стартует экран `HOME`;
|
- стартует экран `HOME`;
|
||||||
- на `HOME` видны реальное значение логина или `login not set`, реальное значение сабсервера или `subserver not set`, при отсутствии секрета строка `secret not set`, а также `W BAT`, `STATUS`, Wi-Fi статус и кнопка `SETTINGS`;
|
- на `HOME` видны реальное значение сабсервера или `subserver not set`, реальное значение логина или `login not set`, при отсутствии секрета строка `secret not set`, а также `STATUS`, верхний правый блок с процентом батареи, иконкой батареи и индикатором Wi-Fi, и кнопка `SETTINGS`;
|
||||||
- Wi-Fi статус на `HOME` корректно показывает одно из состояний:
|
- строка Wi-Fi на `HOME` корректно показывает одно из состояний:
|
||||||
- `Wi-Fi not configured`
|
- `Wi-Fi (not configured) not configured`
|
||||||
- `Wi-Fi disconnected`
|
- `Wi-Fi (<saved_ssid>) disconnected`
|
||||||
- `Wi-Fi connected`
|
- `Wi-Fi (<current_ssid>) connected`
|
||||||
|
- пока открыт `HOME`, статус сам обновляется без перехода на другие экраны;
|
||||||
- кнопка `SETTINGS` открывает `SETTINGS_MENU`;
|
- кнопка `SETTINGS` открывает `SETTINGS_MENU`;
|
||||||
- свайп влево на `HOME` открывает `SETTINGS_MENU`;
|
- свайп влево на `HOME` открывает `SETTINGS_MENU`;
|
||||||
- в `SETTINGS_MENU` сначала видны только `Wi-Fi` и `Server`;
|
- в `SETTINGS_MENU` сначала видны только `Wi-Fi` и `Server`;
|
||||||
@ -57,6 +58,10 @@
|
|||||||
- `USE SUBSERVER1` возвращает стандартное значение `subserver1`;
|
- `USE SUBSERVER1` возвращает стандартное значение `subserver1`;
|
||||||
- `EDIT MANUALLY` открывает общий экран редактирования и сохраняет значение в NVS;
|
- `EDIT MANUALLY` открывает общий экран редактирования и сохраняет значение в NVS;
|
||||||
- `Secret` открывает экран-заглушку, где сказано, что настройка ещё не реализована;
|
- `Secret` открывает экран-заглушку, где сказано, что настройка ещё не реализована;
|
||||||
|
- `Secret` теперь открывает меню секрета с показом секрета, ручным вводом и генерацией;
|
||||||
|
- при смене `login` сохранённый секрет сбрасывается в `not set`;
|
||||||
|
- во время генерации секрета есть `CANCEL` и подтверждение остановки;
|
||||||
|
- при отмене генерации старый секрет, если он был, не должен теряться;
|
||||||
- свайп вправо из внутренних экранов возвращает в `SETTINGS_MENU`;
|
- свайп вправо из внутренних экранов возвращает в `SETTINGS_MENU`;
|
||||||
- свайп вправо из `ACCOUNT_SUBSERVER_SCREEN` и `ACCOUNT_SECRET_SCREEN` возвращает в `ACCOUNT_SCREEN`;
|
- свайп вправо из `ACCOUNT_SUBSERVER_SCREEN` и `ACCOUNT_SECRET_SCREEN` возвращает в `ACCOUNT_SCREEN`;
|
||||||
- если во время реального свайпа палец проходит по кнопке, это не должно открывать кнопку как обычный `click`.
|
- если во время реального свайпа палец проходит по кнопке, это не должно открывать кнопку как обычный `click`.
|
||||||
|
|||||||
@ -32,19 +32,21 @@
|
|||||||
## HOME
|
## HOME
|
||||||
|
|
||||||
Показывает:
|
Показывает:
|
||||||
- сверху слева значение логина или `login not set`;
|
- сверху слева значение сабсервера или `subserver not set`;
|
||||||
- ниже значение сабсервера или `subserver not set`;
|
- ниже значение логина или `login not set`;
|
||||||
- третьей строкой `secret not set`, если секрет ещё не помечен как установленный;
|
- третьей строкой `secret not set`, если секрет ещё не помечен как установленный;
|
||||||
- сверху справа простые индикаторы `W BAT`;
|
- сверху справа один ряд индикаторов:
|
||||||
|
- процент батареи;
|
||||||
|
- иконка батареи;
|
||||||
|
- индикатор Wi-Fi уровня сигнала;
|
||||||
- по центру крупный текст `STATUS`;
|
- по центру крупный текст `STATUS`;
|
||||||
- статус Wi-Fi;
|
- одна строка Wi-Fi вида `Wi-Fi (<ssid>) connected/disconnected`;
|
||||||
- сохранённый SSID или пометку, что он не сохранён;
|
|
||||||
- снизу большую кнопку `SETTINGS`.
|
- снизу большую кнопку `SETTINGS`.
|
||||||
|
|
||||||
Статусы Wi-Fi на `HOME`:
|
Строка Wi-Fi на `HOME`:
|
||||||
- `Wi-Fi not configured`
|
- `Wi-Fi (not configured) not configured`
|
||||||
- `Wi-Fi disconnected`
|
- `Wi-Fi (<saved_ssid>) disconnected`
|
||||||
- `Wi-Fi connected`
|
- `Wi-Fi (<current_ssid>) connected`
|
||||||
|
|
||||||
Переходы:
|
Переходы:
|
||||||
- кнопка `SETTINGS` -> `SETTINGS_MENU`;
|
- кнопка `SETTINGS` -> `SETTINGS_MENU`;
|
||||||
@ -215,6 +217,11 @@
|
|||||||
- после потери связи устройство автоматически пытается переподключиться;
|
- после потери связи устройство автоматически пытается переподключиться;
|
||||||
- первые повторные попытки идут раз в `10` секунд;
|
- первые повторные попытки идут раз в `10` секунд;
|
||||||
- если связи нет долго, интервал увеличивается до `30` секунд.
|
- если связи нет долго, интервал увеличивается до `30` секунд.
|
||||||
|
- пока открыт `HOME`, экран сам обновляется примерно раз в секунду и подтягивает актуальные:
|
||||||
|
- статус Wi-Fi;
|
||||||
|
- SSID;
|
||||||
|
- уровень сигнала;
|
||||||
|
- процент батареи.
|
||||||
|
|
||||||
## Хранение серверов
|
## Хранение серверов
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,9 @@
|
|||||||
#include <lvgl.h>
|
#include <lvgl.h>
|
||||||
#include <Arduino_GFX_Library.h>
|
#include <Arduino_GFX_Library.h>
|
||||||
#include <TouchDrvCSTXXX.hpp>
|
#include <TouchDrvCSTXXX.hpp>
|
||||||
|
#define XPOWERS_CHIP_AXP2101
|
||||||
|
#include "XPowersLib.h"
|
||||||
|
#include "shine_secret_generation.h"
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#define PIN_LCD_CS 12
|
#define PIN_LCD_CS 12
|
||||||
@ -42,6 +45,10 @@ enum Screen {
|
|||||||
SCREEN_ACCOUNT,
|
SCREEN_ACCOUNT,
|
||||||
SCREEN_ACCOUNT_SUBSERVER,
|
SCREEN_ACCOUNT_SUBSERVER,
|
||||||
SCREEN_ACCOUNT_SECRET,
|
SCREEN_ACCOUNT_SECRET,
|
||||||
|
SCREEN_SECRET_SHOW,
|
||||||
|
SCREEN_SECRET_GENERATE_INFO,
|
||||||
|
SCREEN_SECRET_GENERATE_RUNNING,
|
||||||
|
SCREEN_SECRET_GENERATE_CANCEL_CONFIRM,
|
||||||
SCREEN_TEXT_EDIT,
|
SCREEN_TEXT_EDIT,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -70,6 +77,14 @@ enum ActionId {
|
|||||||
ACTION_ACCOUNT_EDIT_SECRET,
|
ACTION_ACCOUNT_EDIT_SECRET,
|
||||||
ACTION_ACCOUNT_SUBSERVER_USE_DEFAULT,
|
ACTION_ACCOUNT_SUBSERVER_USE_DEFAULT,
|
||||||
ACTION_ACCOUNT_SUBSERVER_EDIT_MANUAL,
|
ACTION_ACCOUNT_SUBSERVER_EDIT_MANUAL,
|
||||||
|
ACTION_SECRET_SHOW,
|
||||||
|
ACTION_SECRET_MANUAL,
|
||||||
|
ACTION_SECRET_GENERATE,
|
||||||
|
ACTION_SECRET_GENERATE_START,
|
||||||
|
ACTION_SECRET_GENERATE_CANCEL,
|
||||||
|
ACTION_SECRET_GENERATE_CANCEL_YES,
|
||||||
|
ACTION_SECRET_GENERATE_CANCEL_NO,
|
||||||
|
ACTION_BACK_SECRET_MENU,
|
||||||
ACTION_BACK_ACCOUNT,
|
ACTION_BACK_ACCOUNT,
|
||||||
ACTION_EDITOR_SAVE,
|
ACTION_EDITOR_SAVE,
|
||||||
ACTION_EDITOR_CANCEL,
|
ACTION_EDITOR_CANCEL,
|
||||||
@ -87,6 +102,8 @@ enum EditContext {
|
|||||||
EDIT_CONTEXT_SHINE_SERVER,
|
EDIT_CONTEXT_SHINE_SERVER,
|
||||||
EDIT_CONTEXT_LOGIN,
|
EDIT_CONTEXT_LOGIN,
|
||||||
EDIT_CONTEXT_SUBSERVER,
|
EDIT_CONTEXT_SUBSERVER,
|
||||||
|
EDIT_CONTEXT_SECRET_MANUAL,
|
||||||
|
EDIT_CONTEXT_SECRET_GENERATE_PASSWORD,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum KeyboardMode {
|
enum KeyboardMode {
|
||||||
@ -108,6 +125,7 @@ Arduino_DataBus *gBus = new Arduino_ESP32QSPI(
|
|||||||
Arduino_CO5300 *gfx = new Arduino_CO5300(
|
Arduino_CO5300 *gfx = new Arduino_CO5300(
|
||||||
gBus, PIN_LCD_RST, 0, DISP_W, DISP_H, 0, 0, 0, 0);
|
gBus, PIN_LCD_RST, 0, DISP_W, DISP_H, 0, 0, 0, 0);
|
||||||
TouchDrvCST92xx gTouch;
|
TouchDrvCST92xx gTouch;
|
||||||
|
XPowersPMU gPower;
|
||||||
Preferences gPrefs;
|
Preferences gPrefs;
|
||||||
|
|
||||||
static Screen gCurrentScreen = SCREEN_HOME;
|
static Screen gCurrentScreen = SCREEN_HOME;
|
||||||
@ -134,6 +152,8 @@ static String gServerStatusMessage = "Edit RPC or shine host";
|
|||||||
static String gLoginValue;
|
static String gLoginValue;
|
||||||
static String gSubserverValue = "subserver1";
|
static String gSubserverValue = "subserver1";
|
||||||
static bool gSecretConfigured = false;
|
static bool gSecretConfigured = false;
|
||||||
|
static String gSecretBase58;
|
||||||
|
static uint8_t gSecretBytes[32] = {};
|
||||||
static String gAccountStatusMessage = "Edit account fields";
|
static String gAccountStatusMessage = "Edit account fields";
|
||||||
static bool gWifiKnownGood = false;
|
static bool gWifiKnownGood = false;
|
||||||
static bool gWifiReconnectEnabled = false;
|
static bool gWifiReconnectEnabled = false;
|
||||||
@ -141,6 +161,7 @@ static bool gWifiOperationBusy = false;
|
|||||||
static unsigned long gWifiDisconnectedSinceMs = 0;
|
static unsigned long gWifiDisconnectedSinceMs = 0;
|
||||||
static unsigned long gWifiLastReconnectAttemptMs = 0;
|
static unsigned long gWifiLastReconnectAttemptMs = 0;
|
||||||
static wl_status_t gLastWifiStatus = WL_IDLE_STATUS;
|
static wl_status_t gLastWifiStatus = WL_IDLE_STATUS;
|
||||||
|
static bool gPowerReady = false;
|
||||||
|
|
||||||
static EditContext gEditContext = EDIT_CONTEXT_NONE;
|
static EditContext gEditContext = EDIT_CONTEXT_NONE;
|
||||||
static Screen gEditReturnScreen = SCREEN_HOME;
|
static Screen gEditReturnScreen = SCREEN_HOME;
|
||||||
@ -165,6 +186,7 @@ static void scanWifiNetworks();
|
|||||||
static bool connectWifiNow(const String &ssid, const String &password);
|
static bool connectWifiNow(const String &ssid, const String &password);
|
||||||
static String wifiHomeStatus();
|
static String wifiHomeStatus();
|
||||||
static String wifiSavedLabel();
|
static String wifiSavedLabel();
|
||||||
|
static String wifiHomeSummary();
|
||||||
static void updateWifiReconnectState();
|
static void updateWifiReconnectState();
|
||||||
static void manageWifiReconnect();
|
static void manageWifiReconnect();
|
||||||
static void openEditor(EditContext context,
|
static void openEditor(EditContext context,
|
||||||
@ -182,6 +204,12 @@ static String loginDisplayValue();
|
|||||||
static String subserverDisplayValue();
|
static String subserverDisplayValue();
|
||||||
static String homeSecretStatus();
|
static String homeSecretStatus();
|
||||||
static String secretButtonValue();
|
static String secretButtonValue();
|
||||||
|
static void clearSecretValue();
|
||||||
|
static void setSecretValue(const uint8_t *bytes32);
|
||||||
|
static void initPowerManagement();
|
||||||
|
static int batteryPercentValue();
|
||||||
|
static int wifiSignalLevel();
|
||||||
|
static void drawTopStatusIndicators();
|
||||||
|
|
||||||
static void lvglFlushCb(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *colorP) {
|
static void lvglFlushCb(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *colorP) {
|
||||||
uint32_t w = area->x2 - area->x1 + 1;
|
uint32_t w = area->x2 - area->x1 + 1;
|
||||||
@ -302,6 +330,15 @@ static void loadPrefs() {
|
|||||||
gLoginValue = gPrefs.getString("login", "");
|
gLoginValue = gPrefs.getString("login", "");
|
||||||
gSubserverValue = gPrefs.getString("subserver", "subserver1");
|
gSubserverValue = gPrefs.getString("subserver", "subserver1");
|
||||||
gSecretConfigured = gPrefs.getBool("secret_set", false);
|
gSecretConfigured = gPrefs.getBool("secret_set", false);
|
||||||
|
gSecretBase58 = gPrefs.getString("secret_b58", "");
|
||||||
|
if (gSecretConfigured && gPrefs.getBytesLength("secret_bytes") == 32) {
|
||||||
|
gPrefs.getBytes("secret_bytes", gSecretBytes, 32);
|
||||||
|
} else {
|
||||||
|
memset(gSecretBytes, 0, sizeof(gSecretBytes));
|
||||||
|
if (!gSecretConfigured) {
|
||||||
|
gSecretBase58 = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void saveWifiPrefs() {
|
static void saveWifiPrefs() {
|
||||||
@ -319,6 +356,13 @@ static void saveAccountPrefs() {
|
|||||||
gPrefs.putString("login", gLoginValue);
|
gPrefs.putString("login", gLoginValue);
|
||||||
gPrefs.putString("subserver", gSubserverValue);
|
gPrefs.putString("subserver", gSubserverValue);
|
||||||
gPrefs.putBool("secret_set", gSecretConfigured);
|
gPrefs.putBool("secret_set", gSecretConfigured);
|
||||||
|
gPrefs.putString("secret_b58", gSecretBase58);
|
||||||
|
if (gSecretConfigured) {
|
||||||
|
gPrefs.putBytes("secret_bytes", gSecretBytes, 32);
|
||||||
|
} else {
|
||||||
|
gPrefs.remove("secret_b58");
|
||||||
|
gPrefs.remove("secret_bytes");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void clearWifiPrefs() {
|
static void clearWifiPrefs() {
|
||||||
@ -364,6 +408,16 @@ static String wifiSavedLabel() {
|
|||||||
return String("Saved: ") + gWifiSavedSsid;
|
return String("Saved: ") + gWifiSavedSsid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static String wifiHomeSummary() {
|
||||||
|
if (gWifiSavedSsid.isEmpty()) {
|
||||||
|
return "Wi-Fi (not configured) not configured";
|
||||||
|
}
|
||||||
|
if (WiFi.status() == WL_CONNECTED) {
|
||||||
|
return String("Wi-Fi (") + WiFi.SSID() + ") connected";
|
||||||
|
}
|
||||||
|
return String("Wi-Fi (") + gWifiSavedSsid + ") disconnected";
|
||||||
|
}
|
||||||
|
|
||||||
static String loginDisplayValue() {
|
static String loginDisplayValue() {
|
||||||
return gLoginValue.isEmpty() ? "login not set" : gLoginValue;
|
return gLoginValue.isEmpty() ? "login not set" : gLoginValue;
|
||||||
}
|
}
|
||||||
@ -380,6 +434,117 @@ static String secretButtonValue() {
|
|||||||
return gSecretConfigured ? "*****" : "not set";
|
return gSecretConfigured ? "*****" : "not set";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void initPowerManagement() {
|
||||||
|
gPowerReady = gPower.begin(Wire, AXP2101_SLAVE_ADDRESS, PIN_I2C_SDA, PIN_I2C_SCL);
|
||||||
|
if (!gPowerReady) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gPower.disableIRQ(XPOWERS_AXP2101_ALL_IRQ);
|
||||||
|
gPower.setChargeTargetVoltage(3);
|
||||||
|
gPower.clearIrqStatus();
|
||||||
|
gPower.enableTemperatureMeasure();
|
||||||
|
gPower.enableBattDetection();
|
||||||
|
gPower.enableBattVoltageMeasure();
|
||||||
|
gPower.enableVbusVoltageMeasure();
|
||||||
|
gPower.enableSystemVoltageMeasure();
|
||||||
|
}
|
||||||
|
|
||||||
|
static int batteryPercentValue() {
|
||||||
|
if (!gPowerReady || !gPower.isBatteryConnect()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int value = gPower.getBatteryPercent();
|
||||||
|
if (value < 0) value = 0;
|
||||||
|
if (value > 100) value = 100;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int wifiSignalLevel() {
|
||||||
|
if (WiFi.status() != WL_CONNECTED) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int rssi = WiFi.RSSI();
|
||||||
|
if (rssi >= -55) return 4;
|
||||||
|
if (rssi >= -67) return 3;
|
||||||
|
if (rssi >= -75) return 2;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drawTopStatusIndicators() {
|
||||||
|
int batt = batteryPercentValue();
|
||||||
|
String battText = batt >= 0 ? String(batt) + "%" : "--";
|
||||||
|
|
||||||
|
lv_obj_t *battLabel = lv_label_create(gRoot);
|
||||||
|
lv_label_set_text(battLabel, battText.c_str());
|
||||||
|
lv_obj_set_style_text_font(battLabel, &lv_font_montserrat_16, 0);
|
||||||
|
lv_obj_set_style_text_color(battLabel, lv_color_hex(0xC9D3DE), 0);
|
||||||
|
lv_obj_set_pos(battLabel, 286, 18);
|
||||||
|
|
||||||
|
lv_obj_t *battery = lv_obj_create(gRoot);
|
||||||
|
lv_obj_set_size(battery, 32, 16);
|
||||||
|
lv_obj_set_pos(battery, 338, 20);
|
||||||
|
lv_obj_set_style_radius(battery, 4, 0);
|
||||||
|
lv_obj_set_style_bg_opa(battery, LV_OPA_TRANSP, 0);
|
||||||
|
lv_obj_set_style_border_width(battery, 2, 0);
|
||||||
|
lv_obj_set_style_border_color(battery, lv_color_hex(0xC9D3DE), 0);
|
||||||
|
lv_obj_clear_flag(battery, LV_OBJ_FLAG_SCROLLABLE);
|
||||||
|
|
||||||
|
lv_obj_t *batteryCap = lv_obj_create(gRoot);
|
||||||
|
lv_obj_set_size(batteryCap, 4, 8);
|
||||||
|
lv_obj_set_pos(batteryCap, 370, 24);
|
||||||
|
lv_obj_set_style_radius(batteryCap, 2, 0);
|
||||||
|
lv_obj_set_style_bg_color(batteryCap, lv_color_hex(0xC9D3DE), 0);
|
||||||
|
lv_obj_set_style_border_width(batteryCap, 0, 0);
|
||||||
|
|
||||||
|
if (batt > 10) {
|
||||||
|
lv_obj_t *batteryFill = lv_obj_create(gRoot);
|
||||||
|
int fillWidth = max(2, min(26, (batt * 26) / 100));
|
||||||
|
lv_obj_set_size(batteryFill, fillWidth, 10);
|
||||||
|
lv_obj_set_pos(batteryFill, 341, 23);
|
||||||
|
lv_obj_set_style_radius(batteryFill, 2, 0);
|
||||||
|
lv_obj_set_style_bg_color(batteryFill, lv_color_hex(0x38B26D), 0);
|
||||||
|
lv_obj_set_style_border_width(batteryFill, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int level = wifiSignalLevel();
|
||||||
|
int baseX = 388;
|
||||||
|
int baseY = 34;
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
lv_obj_t *bar = lv_obj_create(gRoot);
|
||||||
|
int h = 4 + i * 4;
|
||||||
|
lv_obj_set_size(bar, 5, h);
|
||||||
|
lv_obj_set_pos(bar, baseX + i * 8, baseY - h);
|
||||||
|
lv_obj_set_style_radius(bar, 2, 0);
|
||||||
|
lv_obj_set_style_border_width(bar, 0, 0);
|
||||||
|
uint32_t color = (level > i) ? 0x4FB3FF : 0x4A5560;
|
||||||
|
if (level == 0) color = 0x4A5560;
|
||||||
|
lv_obj_set_style_bg_color(bar, lv_color_hex(color), 0);
|
||||||
|
}
|
||||||
|
if (level == 0) {
|
||||||
|
lv_obj_t *off = lv_label_create(gRoot);
|
||||||
|
lv_label_set_text(off, "x");
|
||||||
|
lv_obj_set_style_text_font(off, &lv_font_montserrat_18, 0);
|
||||||
|
lv_obj_set_style_text_color(off, lv_color_hex(0xD26969), 0);
|
||||||
|
lv_obj_set_pos(off, baseX + 10, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clearSecretValue() {
|
||||||
|
gSecretConfigured = false;
|
||||||
|
gSecretBase58 = "";
|
||||||
|
memset(gSecretBytes, 0, sizeof(gSecretBytes));
|
||||||
|
saveAccountPrefs();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setSecretValue(const uint8_t *bytes32) {
|
||||||
|
memcpy(gSecretBytes, bytes32, 32);
|
||||||
|
char b58[64];
|
||||||
|
shineSecretBase58Encode(bytes32, 32, b58, sizeof(b58));
|
||||||
|
gSecretBase58 = b58;
|
||||||
|
gSecretConfigured = true;
|
||||||
|
saveAccountPrefs();
|
||||||
|
}
|
||||||
|
|
||||||
static void updateWifiReconnectState() {
|
static void updateWifiReconnectState() {
|
||||||
wl_status_t status = WiFi.status();
|
wl_status_t status = WiFi.status();
|
||||||
if (status == WL_CONNECTED) {
|
if (status == WL_CONNECTED) {
|
||||||
@ -543,7 +708,11 @@ static void applyEditorValue() {
|
|||||||
|
|
||||||
if (gEditContext == EDIT_CONTEXT_LOGIN) {
|
if (gEditContext == EDIT_CONTEXT_LOGIN) {
|
||||||
value.trim();
|
value.trim();
|
||||||
|
String oldLogin = gLoginValue;
|
||||||
gLoginValue = value;
|
gLoginValue = value;
|
||||||
|
if (gLoginValue != oldLogin) {
|
||||||
|
clearSecretValue();
|
||||||
|
}
|
||||||
saveAccountPrefs();
|
saveAccountPrefs();
|
||||||
gAccountStatusMessage = gLoginValue.isEmpty() ? "Login cleared" : "Login saved";
|
gAccountStatusMessage = gLoginValue.isEmpty() ? "Login cleared" : "Login saved";
|
||||||
showScreen(SCREEN_ACCOUNT);
|
showScreen(SCREEN_ACCOUNT);
|
||||||
@ -558,6 +727,49 @@ static void applyEditorValue() {
|
|||||||
showScreen(SCREEN_ACCOUNT);
|
showScreen(SCREEN_ACCOUNT);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (gEditContext == EDIT_CONTEXT_SECRET_MANUAL) {
|
||||||
|
value.trim();
|
||||||
|
uint8_t decoded[64] = {};
|
||||||
|
size_t decodedLen = 0;
|
||||||
|
String error;
|
||||||
|
if (!shineSecretBase58Decode(value.c_str(), decoded, &decodedLen, sizeof(decoded), error)) {
|
||||||
|
gAccountStatusMessage = error;
|
||||||
|
showScreen(SCREEN_ACCOUNT_SECRET);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (decodedLen < 32) {
|
||||||
|
gAccountStatusMessage = "secret too short";
|
||||||
|
showScreen(SCREEN_ACCOUNT_SECRET);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (decodedLen > 32) {
|
||||||
|
gAccountStatusMessage = "secret too long";
|
||||||
|
showScreen(SCREEN_ACCOUNT_SECRET);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSecretValue(decoded);
|
||||||
|
gAccountStatusMessage = "Secret saved";
|
||||||
|
showScreen(SCREEN_SECRET_SHOW);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gEditContext == EDIT_CONTEXT_SECRET_GENERATE_PASSWORD) {
|
||||||
|
String error;
|
||||||
|
if (gLoginValue.isEmpty()) {
|
||||||
|
gAccountStatusMessage = "Login not set";
|
||||||
|
showScreen(SCREEN_SECRET_GENERATE_INFO);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!shineSecretStart(gLoginValue.c_str(), value.c_str(), error)) {
|
||||||
|
gAccountStatusMessage = error;
|
||||||
|
showScreen(SCREEN_SECRET_GENERATE_INFO);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gAccountStatusMessage = "Secret generation started";
|
||||||
|
showScreen(SCREEN_SECRET_GENERATE_RUNNING);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void cancelEditor() {
|
static void cancelEditor() {
|
||||||
@ -730,6 +942,41 @@ static void actionButtonCb(lv_event_t *event) {
|
|||||||
case ACTION_ACCOUNT_EDIT_SECRET:
|
case ACTION_ACCOUNT_EDIT_SECRET:
|
||||||
showScreen(SCREEN_ACCOUNT_SECRET);
|
showScreen(SCREEN_ACCOUNT_SECRET);
|
||||||
break;
|
break;
|
||||||
|
case ACTION_SECRET_SHOW:
|
||||||
|
if (gSecretConfigured) {
|
||||||
|
showScreen(SCREEN_SECRET_SHOW);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ACTION_SECRET_MANUAL:
|
||||||
|
openEditor(EDIT_CONTEXT_SECRET_MANUAL,
|
||||||
|
SCREEN_ACCOUNT_SECRET,
|
||||||
|
"ENTER SECRET",
|
||||||
|
"",
|
||||||
|
gSecretBase58,
|
||||||
|
false);
|
||||||
|
break;
|
||||||
|
case ACTION_SECRET_GENERATE:
|
||||||
|
showScreen(SCREEN_SECRET_GENERATE_INFO);
|
||||||
|
break;
|
||||||
|
case ACTION_SECRET_GENERATE_START:
|
||||||
|
openEditor(EDIT_CONTEXT_SECRET_GENERATE_PASSWORD,
|
||||||
|
SCREEN_SECRET_GENERATE_INFO,
|
||||||
|
"ENTER PASSWORD",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
false);
|
||||||
|
break;
|
||||||
|
case ACTION_SECRET_GENERATE_CANCEL:
|
||||||
|
showScreen(SCREEN_SECRET_GENERATE_CANCEL_CONFIRM);
|
||||||
|
break;
|
||||||
|
case ACTION_SECRET_GENERATE_CANCEL_YES:
|
||||||
|
shineSecretAbort();
|
||||||
|
gAccountStatusMessage = "Secret generation cancelled";
|
||||||
|
showScreen(SCREEN_ACCOUNT_SECRET);
|
||||||
|
break;
|
||||||
|
case ACTION_SECRET_GENERATE_CANCEL_NO:
|
||||||
|
showScreen(SCREEN_SECRET_GENERATE_RUNNING);
|
||||||
|
break;
|
||||||
case ACTION_ACCOUNT_SUBSERVER_USE_DEFAULT:
|
case ACTION_ACCOUNT_SUBSERVER_USE_DEFAULT:
|
||||||
gSubserverValue = "subserver1";
|
gSubserverValue = "subserver1";
|
||||||
saveAccountPrefs();
|
saveAccountPrefs();
|
||||||
@ -744,6 +991,9 @@ static void actionButtonCb(lv_event_t *event) {
|
|||||||
gSubserverValue,
|
gSubserverValue,
|
||||||
false);
|
false);
|
||||||
break;
|
break;
|
||||||
|
case ACTION_BACK_SECRET_MENU:
|
||||||
|
showScreen(SCREEN_ACCOUNT_SECRET);
|
||||||
|
break;
|
||||||
case ACTION_EDITOR_SAVE:
|
case ACTION_EDITOR_SAVE:
|
||||||
applyEditorValue();
|
applyEditorValue();
|
||||||
break;
|
break;
|
||||||
@ -813,14 +1063,6 @@ static lv_obj_t *makeKeyboardButton(const char *label,
|
|||||||
return btn;
|
return btn;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void makeTopIcons() {
|
|
||||||
lv_obj_t *icons = lv_label_create(gRoot);
|
|
||||||
lv_label_set_text(icons, "W BAT");
|
|
||||||
lv_obj_set_style_text_font(icons, &lv_font_montserrat_18, 0);
|
|
||||||
lv_obj_set_style_text_color(icons, lv_color_hex(0xC9D3DE), 0);
|
|
||||||
lv_obj_align(icons, LV_ALIGN_TOP_RIGHT, -24, 18);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void makeVersionTag() {
|
static void makeVersionTag() {
|
||||||
lv_obj_t *tag = lv_label_create(gRoot);
|
lv_obj_t *tag = lv_label_create(gRoot);
|
||||||
lv_label_set_text(tag, TEST_VERSION);
|
lv_label_set_text(tag, TEST_VERSION);
|
||||||
@ -832,30 +1074,29 @@ static void makeVersionTag() {
|
|||||||
static void drawHome() {
|
static void drawHome() {
|
||||||
setRootStyle();
|
setRootStyle();
|
||||||
|
|
||||||
lv_obj_t *login = lv_label_create(gRoot);
|
|
||||||
lv_label_set_text(login, loginDisplayValue().c_str());
|
|
||||||
lv_obj_set_style_text_font(login, &lv_font_montserrat_18, 0);
|
|
||||||
lv_obj_set_style_text_color(login, lv_color_hex(0xFFFFFF), 0);
|
|
||||||
lv_obj_align(login, LV_ALIGN_TOP_LEFT, 24, 18);
|
|
||||||
|
|
||||||
lv_obj_t *subserver = lv_label_create(gRoot);
|
lv_obj_t *subserver = lv_label_create(gRoot);
|
||||||
lv_label_set_text(subserver, subserverDisplayValue().c_str());
|
lv_label_set_text(subserver, subserverDisplayValue().c_str());
|
||||||
lv_obj_set_style_text_font(subserver, &lv_font_montserrat_18, 0);
|
lv_obj_set_style_text_font(subserver, &lv_font_montserrat_18, 0);
|
||||||
lv_obj_set_style_text_color(subserver, lv_color_hex(0xD5DEE7), 0);
|
lv_obj_set_style_text_color(subserver, lv_color_hex(0xFFFFFF), 0);
|
||||||
lv_obj_align_to(subserver, login, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 6);
|
lv_obj_align(subserver, LV_ALIGN_TOP_LEFT, 24, 18);
|
||||||
|
|
||||||
|
lv_obj_t *login = lv_label_create(gRoot);
|
||||||
|
lv_label_set_text(login, loginDisplayValue().c_str());
|
||||||
|
lv_obj_set_style_text_font(login, &lv_font_montserrat_22, 0);
|
||||||
|
lv_obj_set_style_text_color(login, lv_color_hex(0xD5DEE7), 0);
|
||||||
|
lv_obj_align_to(login, subserver, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 6);
|
||||||
|
|
||||||
if (!gSecretConfigured) {
|
if (!gSecretConfigured) {
|
||||||
lv_obj_t *secret = lv_label_create(gRoot);
|
lv_obj_t *secret = lv_label_create(gRoot);
|
||||||
lv_label_set_text(secret, homeSecretStatus().c_str());
|
lv_label_set_text(secret, homeSecretStatus().c_str());
|
||||||
lv_obj_set_style_text_font(secret, &lv_font_montserrat_16, 0);
|
lv_obj_set_style_text_font(secret, &lv_font_montserrat_16, 0);
|
||||||
lv_obj_set_style_text_color(secret, lv_color_hex(0xB8C6D3), 0);
|
lv_obj_set_style_text_color(secret, lv_color_hex(0xB8C6D3), 0);
|
||||||
lv_obj_align_to(secret, subserver, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 6);
|
lv_obj_align_to(secret, login, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
makeTopIcons();
|
drawTopStatusIndicators();
|
||||||
makeTitle("STATUS", 150, &lv_font_montserrat_28);
|
makeTitle("STATUS", 150, &lv_font_montserrat_28);
|
||||||
showMessageAt(wifiHomeStatus(), 204);
|
showMessageAt(wifiHomeSummary(), 214);
|
||||||
showMessageAt(wifiSavedLabel(), 238);
|
|
||||||
makeBody("Swipe left or tap the button below.", 274, 360);
|
makeBody("Swipe left or tap the button below.", 274, 360);
|
||||||
makeButton("SETTINGS", 22, 360, 436, 78, 0x2A6F97, ACTION_OPEN_SETTINGS, &lv_font_montserrat_24);
|
makeButton("SETTINGS", 22, 360, 436, 78, 0x2A6F97, ACTION_OPEN_SETTINGS, &lv_font_montserrat_24);
|
||||||
makeVersionTag();
|
makeVersionTag();
|
||||||
@ -995,9 +1236,85 @@ static void drawAccountSubserverScreen() {
|
|||||||
static void drawAccountSecretScreen() {
|
static void drawAccountSecretScreen() {
|
||||||
setRootStyle();
|
setRootStyle();
|
||||||
makeTitle("SECRET", 18, &lv_font_montserrat_24);
|
makeTitle("SECRET", 18, &lv_font_montserrat_24);
|
||||||
showMessageAt(String("Status: ") + (gSecretConfigured ? "set" : "not set"), 56);
|
showMessageAt(gAccountStatusMessage, 56);
|
||||||
makeBody("Secret setup is not implemented yet.", 116, 420);
|
makeButton(gSecretConfigured ? "SHOW SECRET" : "SECRET NOT SET",
|
||||||
makeButton("BACK", 140, 360, 200, 72, 0x5A6570, ACTION_BACK_ACCOUNT, &lv_font_montserrat_22);
|
22, 118, 436, 84, gSecretConfigured ? 0x2A6F97 : 0x4A5560,
|
||||||
|
gSecretConfigured ? ACTION_SECRET_SHOW : ACTION_NONE, &lv_font_montserrat_22);
|
||||||
|
makeButton("ENTER SECRET MANUALLY (NOT RECOMMENDED)",
|
||||||
|
22, 222, 436, 84, 0x355C7D, ACTION_SECRET_MANUAL, &lv_font_montserrat_18);
|
||||||
|
makeButton("GENERATE SECRET",
|
||||||
|
22, 326, 436, 84, 0x2A9D8F, ACTION_SECRET_GENERATE, &lv_font_montserrat_22);
|
||||||
|
makeBody("Swipe right to return to Account.", 420, 420);
|
||||||
|
makeVersionTag();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drawSecretShowScreen() {
|
||||||
|
setRootStyle();
|
||||||
|
makeTitle("SECRET", 18, &lv_font_montserrat_24);
|
||||||
|
if (gSecretConfigured) {
|
||||||
|
showMessageAt("Current secret in base58:", 56);
|
||||||
|
lv_obj_t *label = lv_label_create(gRoot);
|
||||||
|
lv_label_set_text(label, gSecretBase58.c_str());
|
||||||
|
lv_obj_set_width(label, 420);
|
||||||
|
lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP);
|
||||||
|
lv_obj_set_style_text_font(label, &lv_font_montserrat_16, 0);
|
||||||
|
lv_obj_set_style_text_color(label, lv_color_hex(0xD9E1EA), 0);
|
||||||
|
lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 112);
|
||||||
|
} else {
|
||||||
|
showMessageAt("Secret not set", 96);
|
||||||
|
}
|
||||||
|
makeButton("BACK", 140, 360, 200, 72, 0x5A6570, ACTION_BACK_SECRET_MENU, &lv_font_montserrat_22);
|
||||||
|
makeVersionTag();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drawSecretGenerateInfoScreen() {
|
||||||
|
setRootStyle();
|
||||||
|
makeTitle("GENERATE SECRET", 18, &lv_font_montserrat_24);
|
||||||
|
showMessageAt(String("Login: ") + (gLoginValue.isEmpty() ? "not set" : gLoginValue), 56);
|
||||||
|
makeBody("SD card is required. After password input, secret generation will start and can take 10-30 min.", 100, 420);
|
||||||
|
makeButton("ENTER PASSWORD AND START", 22, 248, 436, 84, 0x2A9D8F, ACTION_SECRET_GENERATE_START, &lv_font_montserrat_20);
|
||||||
|
makeButton("BACK", 140, 360, 200, 72, 0x5A6570, ACTION_BACK_SECRET_MENU, &lv_font_montserrat_22);
|
||||||
|
makeVersionTag();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drawSecretGenerateRunningScreen() {
|
||||||
|
setRootStyle();
|
||||||
|
makeTitle("GENERATING SECRET", 18, &lv_font_montserrat_24);
|
||||||
|
ShineSecretGenerationStatus status = shineSecretStatus();
|
||||||
|
showMessageAt(status.message, 56);
|
||||||
|
showMessageAt(String("Login: ") + gLoginValue, 88);
|
||||||
|
|
||||||
|
lv_obj_t *bar = lv_bar_create(gRoot);
|
||||||
|
lv_obj_set_size(bar, 436, 34);
|
||||||
|
lv_obj_set_pos(bar, 22, 150);
|
||||||
|
lv_bar_set_range(bar, 0, 1000);
|
||||||
|
int value = status.totalBlocks == 0 ? 0 : (int)((uint64_t)status.doneBlocks * 1000ULL / status.totalBlocks);
|
||||||
|
lv_bar_set_value(bar, value, LV_ANIM_OFF);
|
||||||
|
|
||||||
|
char progress[96];
|
||||||
|
snprintf(progress, sizeof(progress), "%lu / %lu blocks", (unsigned long)status.doneBlocks, (unsigned long)status.totalBlocks);
|
||||||
|
showMessageAt(progress, 204);
|
||||||
|
char elapsed[64];
|
||||||
|
snprintf(elapsed, sizeof(elapsed), "Elapsed: %lum %lus", (unsigned long)(status.elapsedSec / 60), (unsigned long)(status.elapsedSec % 60));
|
||||||
|
showMessageAt(elapsed, 236);
|
||||||
|
if (status.done) {
|
||||||
|
showMessageAt("Secret generated successfully", 280);
|
||||||
|
makeButton("SHOW SECRET", 22, 328, 210, 72, 0x2A6F97, ACTION_SECRET_SHOW, &lv_font_montserrat_20);
|
||||||
|
makeButton("BACK", 248, 328, 210, 72, 0x5A6570, ACTION_BACK_SECRET_MENU, &lv_font_montserrat_20);
|
||||||
|
} else {
|
||||||
|
makeBody("Keep device powered and SD card inserted.", 328, 420);
|
||||||
|
makeButton("CANCEL", 140, 372, 200, 56, 0x7D3A3A, ACTION_SECRET_GENERATE_CANCEL, &lv_font_montserrat_20);
|
||||||
|
}
|
||||||
|
makeVersionTag();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drawSecretGenerateCancelConfirmScreen() {
|
||||||
|
setRootStyle();
|
||||||
|
makeTitle("STOP GENERATION?", 26, &lv_font_montserrat_24);
|
||||||
|
makeBody("Are you sure you want to stop secret generation?", 102, 420);
|
||||||
|
makeBody("Existing saved secret will remain unchanged.", 146, 420);
|
||||||
|
makeButton("YES, STOP", 22, 262, 200, 76, 0x7D3A3A, ACTION_SECRET_GENERATE_CANCEL_YES, &lv_font_montserrat_20);
|
||||||
|
makeButton("NO, CONTINUE", 238, 262, 220, 76, 0x355C7D, ACTION_SECRET_GENERATE_CANCEL_NO, &lv_font_montserrat_20);
|
||||||
makeVersionTag();
|
makeVersionTag();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1146,6 +1463,18 @@ static void rebuildScreen() {
|
|||||||
case SCREEN_ACCOUNT_SECRET:
|
case SCREEN_ACCOUNT_SECRET:
|
||||||
drawAccountSecretScreen();
|
drawAccountSecretScreen();
|
||||||
break;
|
break;
|
||||||
|
case SCREEN_SECRET_SHOW:
|
||||||
|
drawSecretShowScreen();
|
||||||
|
break;
|
||||||
|
case SCREEN_SECRET_GENERATE_INFO:
|
||||||
|
drawSecretGenerateInfoScreen();
|
||||||
|
break;
|
||||||
|
case SCREEN_SECRET_GENERATE_RUNNING:
|
||||||
|
drawSecretGenerateRunningScreen();
|
||||||
|
break;
|
||||||
|
case SCREEN_SECRET_GENERATE_CANCEL_CONFIRM:
|
||||||
|
drawSecretGenerateCancelConfirmScreen();
|
||||||
|
break;
|
||||||
case SCREEN_TEXT_EDIT:
|
case SCREEN_TEXT_EDIT:
|
||||||
drawTextEditScreen();
|
drawTextEditScreen();
|
||||||
break;
|
break;
|
||||||
@ -1212,6 +1541,12 @@ static void handleAccountSubscreenSwipe(SwipeDirection swipe) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void handleSecretSubscreenSwipe(SwipeDirection swipe) {
|
||||||
|
if (swipe == SWIPE_RIGHT) {
|
||||||
|
showScreen(SCREEN_ACCOUNT_SECRET);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void handleTextEditSwipe(SwipeDirection swipe) {
|
static void handleTextEditSwipe(SwipeDirection swipe) {
|
||||||
if (!isTextEditKeyboardSwipeArea(gTouchStartX, gTouchStartY)) {
|
if (!isTextEditKeyboardSwipeArea(gTouchStartX, gTouchStartY)) {
|
||||||
return;
|
return;
|
||||||
@ -1258,6 +1593,12 @@ static void handleSwipe(SwipeDirection swipe) {
|
|||||||
case SCREEN_ACCOUNT_SECRET:
|
case SCREEN_ACCOUNT_SECRET:
|
||||||
handleAccountSubscreenSwipe(swipe);
|
handleAccountSubscreenSwipe(swipe);
|
||||||
break;
|
break;
|
||||||
|
case SCREEN_SECRET_SHOW:
|
||||||
|
case SCREEN_SECRET_GENERATE_INFO:
|
||||||
|
case SCREEN_SECRET_GENERATE_RUNNING:
|
||||||
|
case SCREEN_SECRET_GENERATE_CANCEL_CONFIRM:
|
||||||
|
handleSecretSubscreenSwipe(swipe);
|
||||||
|
break;
|
||||||
case SCREEN_TEXT_EDIT:
|
case SCREEN_TEXT_EDIT:
|
||||||
handleTextEditSwipe(swipe);
|
handleTextEditSwipe(swipe);
|
||||||
break;
|
break;
|
||||||
@ -1267,6 +1608,7 @@ static void handleSwipe(SwipeDirection swipe) {
|
|||||||
void setup() {
|
void setup() {
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
Wire.begin(PIN_I2C_SDA, PIN_I2C_SCL);
|
Wire.begin(PIN_I2C_SDA, PIN_I2C_SCL);
|
||||||
|
initPowerManagement();
|
||||||
|
|
||||||
gfx->begin();
|
gfx->begin();
|
||||||
gBus->writeC8D8(0x36, 0xA0);
|
gBus->writeC8D8(0x36, 0xA0);
|
||||||
@ -1322,6 +1664,28 @@ void loop() {
|
|||||||
lv_timer_handler();
|
lv_timer_handler();
|
||||||
manageWifiReconnect();
|
manageWifiReconnect();
|
||||||
|
|
||||||
|
static unsigned long lastHomeRefreshMs = 0;
|
||||||
|
if (gCurrentScreen == SCREEN_HOME && millis() - lastHomeRefreshMs >= 1000) {
|
||||||
|
lastHomeRefreshMs = millis();
|
||||||
|
rebuildScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gCurrentScreen == SCREEN_SECRET_GENERATE_RUNNING) {
|
||||||
|
ShineSecretGenerationStatus status = shineSecretStatus();
|
||||||
|
if (status.running) {
|
||||||
|
shineSecretStep(4);
|
||||||
|
static unsigned long lastSecretUiMs = 0;
|
||||||
|
if (millis() - lastSecretUiMs >= 500) {
|
||||||
|
lastSecretUiMs = millis();
|
||||||
|
rebuildScreen();
|
||||||
|
}
|
||||||
|
} else if (status.done) {
|
||||||
|
setSecretValue(shineSecretBytes());
|
||||||
|
gAccountStatusMessage = "Secret generated";
|
||||||
|
showScreen(SCREEN_SECRET_SHOW);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (gPendingSwipe != SWIPE_NONE) {
|
if (gPendingSwipe != SWIPE_NONE) {
|
||||||
SwipeDirection swipe = gPendingSwipe;
|
SwipeDirection swipe = gPendingSwipe;
|
||||||
gPendingSwipe = SWIPE_NONE;
|
gPendingSwipe = SWIPE_NONE;
|
||||||
|
|||||||
@ -0,0 +1,578 @@
|
|||||||
|
#include "shine_secret_generation.h"
|
||||||
|
|
||||||
|
#include <SD_MMC.h>
|
||||||
|
#include <Preferences.h>
|
||||||
|
#include <mbedtls/sha256.h>
|
||||||
|
#include <mbedtls/base64.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <driver/gpio.h>
|
||||||
|
|
||||||
|
#define PIN_SD_CLK 2
|
||||||
|
#define PIN_SD_CMD 1
|
||||||
|
#define PIN_SD_D0 3
|
||||||
|
#define SD_MEM_FILE "/argon2.bin"
|
||||||
|
|
||||||
|
#define A2_T 2u
|
||||||
|
#define A2_M 65536u
|
||||||
|
#define A2_P 1u
|
||||||
|
#define A2_DKLEN 32u
|
||||||
|
#define A2_SYNC 4u
|
||||||
|
#define A2_SEG (A2_M / A2_SYNC)
|
||||||
|
#define A2_VERSION 0x13u
|
||||||
|
#define A2_TYPE 2u
|
||||||
|
#define A2_BLKSZ 1024u
|
||||||
|
#define TOTAL_FILLS (A2_T * A2_M - 2u)
|
||||||
|
|
||||||
|
struct B2State {
|
||||||
|
uint64_t h[8], t[2], f[2];
|
||||||
|
uint8_t buf[128];
|
||||||
|
size_t buflen, outlen;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool gSdReady = false;
|
||||||
|
static bool gRunning = false;
|
||||||
|
static bool gDone = false;
|
||||||
|
static bool gError = false;
|
||||||
|
static char gMessage[96] = {};
|
||||||
|
static uint8_t gSecret[32] = {};
|
||||||
|
static char gSecretB58[64] = {};
|
||||||
|
static uint32_t gDoneBlocks = 0;
|
||||||
|
static uint32_t gStartMs = 0;
|
||||||
|
static uint32_t gCurPass = 0;
|
||||||
|
static uint32_t gCurBlock = 2;
|
||||||
|
static bool gInitDone = false;
|
||||||
|
static File gSdFile;
|
||||||
|
static uint8_t *gBufPrev = nullptr;
|
||||||
|
static uint8_t *gBufRef = nullptr;
|
||||||
|
static uint8_t *gBufOut = nullptr;
|
||||||
|
static uint8_t *gBufZero = nullptr;
|
||||||
|
static uint8_t *gBufAddr = nullptr;
|
||||||
|
static uint32_t *gJ1Seg = nullptr;
|
||||||
|
static uint8_t gH0[64] = {};
|
||||||
|
static B2State gB2S;
|
||||||
|
|
||||||
|
#define ROTR64(x,n) (((x)>>(n))|((x)<<(64-(n))))
|
||||||
|
static const uint64_t B2IV[8] = {
|
||||||
|
0x6A09E667F3BCC908ULL,0xBB67AE8584CAA73BULL,
|
||||||
|
0x3C6EF372FE94F82BULL,0xA54FF53A5F1D36F1ULL,
|
||||||
|
0x510E527FADE682D1ULL,0x9B05688C2B3E6C1FULL,
|
||||||
|
0x1F83D9ABFB41BD6BULL,0x5BE0CD19137E2179ULL
|
||||||
|
};
|
||||||
|
static const uint8_t B2SIGMA[12][16] = {
|
||||||
|
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15},
|
||||||
|
{14,10,4,8,9,15,13,6,1,12,0,2,11,7,5,3},
|
||||||
|
{11,8,12,0,5,2,15,13,10,14,3,6,7,1,9,4},
|
||||||
|
{7,9,3,1,13,12,11,14,2,6,5,10,4,0,15,8},
|
||||||
|
{9,0,5,7,2,4,10,15,14,1,11,12,6,8,3,13},
|
||||||
|
{2,12,6,10,0,11,8,3,4,13,7,5,15,14,1,9},
|
||||||
|
{12,5,1,15,14,13,4,10,0,7,6,3,9,2,8,11},
|
||||||
|
{13,11,7,14,12,1,3,9,5,0,15,4,8,6,2,10},
|
||||||
|
{6,15,14,9,11,3,0,8,12,2,13,7,1,4,10,5},
|
||||||
|
{10,2,8,4,7,6,1,5,15,11,9,14,3,12,13,0},
|
||||||
|
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15},
|
||||||
|
{14,10,4,8,9,15,13,6,1,12,0,2,11,7,5,3}
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char B58_ALPHA[] = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
||||||
|
|
||||||
|
static void setMessage(const char *message) {
|
||||||
|
snprintf(gMessage, sizeof(gMessage), "%s", message ? message : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void b2_compress(B2State *S, const uint8_t *blk) {
|
||||||
|
uint64_t m[16], v[16];
|
||||||
|
for (int i = 0; i < 16; i++) m[i] = ((const uint64_t *)blk)[i];
|
||||||
|
for (int i = 0; i < 8; i++) v[i] = S->h[i];
|
||||||
|
v[8]=B2IV[0];v[9]=B2IV[1];v[10]=B2IV[2];v[11]=B2IV[3];
|
||||||
|
v[12]=B2IV[4]^S->t[0];v[13]=B2IV[5]^S->t[1];
|
||||||
|
v[14]=B2IV[6]^S->f[0];v[15]=B2IV[7]^S->f[1];
|
||||||
|
#define BG(r,i,a,b,c,d) \
|
||||||
|
v[a]+=v[b]+m[B2SIGMA[r][2*(i)]]; v[d]=ROTR64(v[d]^v[a],32); \
|
||||||
|
v[c]+=v[d]; v[b]=ROTR64(v[b]^v[c],24); \
|
||||||
|
v[a]+=v[b]+m[B2SIGMA[r][2*(i)+1]]; v[d]=ROTR64(v[d]^v[a],16); \
|
||||||
|
v[c]+=v[d]; v[b]=ROTR64(v[b]^v[c],63)
|
||||||
|
for (int r = 0; r < 12; r++) {
|
||||||
|
BG(r,0,0,4,8,12);BG(r,1,1,5,9,13);BG(r,2,2,6,10,14);BG(r,3,3,7,11,15);
|
||||||
|
BG(r,4,0,5,10,15);BG(r,5,1,6,11,12);BG(r,6,2,7,8,13);BG(r,7,3,4,9,14);
|
||||||
|
}
|
||||||
|
#undef BG
|
||||||
|
for (int i = 0; i < 8; i++) S->h[i] ^= v[i] ^ v[i + 8];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void b2_init(B2State *S, size_t outlen) {
|
||||||
|
memset(S, 0, sizeof(*S));
|
||||||
|
memcpy(S->h, B2IV, 64);
|
||||||
|
S->h[0] ^= 0x01010000ULL | outlen;
|
||||||
|
S->outlen = outlen;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void b2_update(B2State *S, const uint8_t *in, size_t inlen) {
|
||||||
|
while (inlen > 0) {
|
||||||
|
size_t fill = 128 - S->buflen;
|
||||||
|
size_t use = inlen < fill ? inlen : fill;
|
||||||
|
memcpy(S->buf + S->buflen, in, use);
|
||||||
|
S->buflen += use;
|
||||||
|
in += use;
|
||||||
|
inlen -= use;
|
||||||
|
if (S->buflen == 128 && inlen > 0) {
|
||||||
|
S->t[0] += 128;
|
||||||
|
if (S->t[0] < 128) S->t[1]++;
|
||||||
|
b2_compress(S, S->buf);
|
||||||
|
S->buflen = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void b2_final(B2State *S, uint8_t *digest) {
|
||||||
|
S->f[0] = ~0ULL;
|
||||||
|
S->t[0] += S->buflen;
|
||||||
|
if (S->t[0] < S->buflen) S->t[1]++;
|
||||||
|
memset(S->buf + S->buflen, 0, 128 - S->buflen);
|
||||||
|
b2_compress(S, S->buf);
|
||||||
|
uint8_t tmp[64];
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
uint64_t w = S->h[i];
|
||||||
|
for (int j = 0; j < 8; j++) tmp[i * 8 + j] = (uint8_t)(w >> (j * 8));
|
||||||
|
}
|
||||||
|
memcpy(digest, tmp, S->outlen);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void b2hash(const uint8_t *in, size_t inlen, uint8_t *digest, size_t outlen) {
|
||||||
|
b2_init(&gB2S, outlen);
|
||||||
|
b2_update(&gB2S, in, inlen);
|
||||||
|
b2_final(&gB2S, digest);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void b2long(const uint8_t *in, size_t inlen, uint8_t *digest, uint32_t outlen) {
|
||||||
|
uint8_t lenle[4] = {(uint8_t)outlen, (uint8_t)(outlen >> 8), (uint8_t)(outlen >> 16), (uint8_t)(outlen >> 24)};
|
||||||
|
if (outlen <= 64) {
|
||||||
|
b2_init(&gB2S, outlen);
|
||||||
|
b2_update(&gB2S, lenle, 4);
|
||||||
|
b2_update(&gB2S, in, inlen);
|
||||||
|
b2_final(&gB2S, digest);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint8_t tmp[64];
|
||||||
|
b2_init(&gB2S, 64);
|
||||||
|
b2_update(&gB2S, lenle, 4);
|
||||||
|
b2_update(&gB2S, in, inlen);
|
||||||
|
b2_final(&gB2S, tmp);
|
||||||
|
memcpy(digest, tmp, 32);
|
||||||
|
digest += 32;
|
||||||
|
uint32_t rem = outlen - 32;
|
||||||
|
while (rem > 64) {
|
||||||
|
b2hash(tmp, 64, tmp, 64);
|
||||||
|
memcpy(digest, tmp, 32);
|
||||||
|
digest += 32;
|
||||||
|
rem -= 32;
|
||||||
|
}
|
||||||
|
b2hash(tmp, 64, tmp, rem);
|
||||||
|
memcpy(digest, tmp, rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define A2G(a,b,c,d) do{ \
|
||||||
|
(a)+=(b)+2*((a)&0xFFFFFFFFULL)*((b)&0xFFFFFFFFULL); \
|
||||||
|
(d)=ROTR64((d)^(a),32); \
|
||||||
|
(c)+=(d)+2*((c)&0xFFFFFFFFULL)*((d)&0xFFFFFFFFULL); \
|
||||||
|
(b)=ROTR64((b)^(c),24); \
|
||||||
|
(a)+=(b)+2*((a)&0xFFFFFFFFULL)*((b)&0xFFFFFFFFULL); \
|
||||||
|
(d)=ROTR64((d)^(a),16); \
|
||||||
|
(c)+=(d)+2*((c)&0xFFFFFFFFULL)*((d)&0xFFFFFFFFULL); \
|
||||||
|
(b)=ROTR64((b)^(c),63); \
|
||||||
|
}while(0)
|
||||||
|
#define A2P(v) do{ \
|
||||||
|
A2G((v)[0],(v)[4],(v)[8],(v)[12]);A2G((v)[1],(v)[5],(v)[9],(v)[13]); \
|
||||||
|
A2G((v)[2],(v)[6],(v)[10],(v)[14]);A2G((v)[3],(v)[7],(v)[11],(v)[15]); \
|
||||||
|
A2G((v)[0],(v)[5],(v)[10],(v)[15]);A2G((v)[1],(v)[6],(v)[11],(v)[12]); \
|
||||||
|
A2G((v)[2],(v)[7],(v)[8],(v)[13]);A2G((v)[3],(v)[4],(v)[9],(v)[14]); \
|
||||||
|
}while(0)
|
||||||
|
|
||||||
|
static void fillBlock(uint32_t prevIdx, uint32_t refIdx, uint32_t outIdx, bool xorMode) {
|
||||||
|
gSdFile.seek((uint64_t)prevIdx * A2_BLKSZ); gSdFile.read(gBufPrev, A2_BLKSZ);
|
||||||
|
gSdFile.seek((uint64_t)refIdx * A2_BLKSZ); gSdFile.read(gBufRef, A2_BLKSZ);
|
||||||
|
uint64_t *R = (uint64_t *)gBufOut, *P = (uint64_t *)gBufPrev, *F = (uint64_t *)gBufRef;
|
||||||
|
for (int i = 0; i < 128; i++) R[i] = P[i] ^ F[i];
|
||||||
|
uint64_t *Q = (uint64_t *)gBufRef; memcpy(Q, R, A2_BLKSZ);
|
||||||
|
for (int j = 0; j < 8; j++) A2P(&Q[16 * j]);
|
||||||
|
uint64_t row[16];
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
for (int k = 0; k < 8; k++) { row[2 * k] = Q[2 * i + 16 * k]; row[2 * k + 1] = Q[2 * i + 16 * k + 1]; }
|
||||||
|
A2P(row);
|
||||||
|
for (int k = 0; k < 8; k++) { Q[2 * i + 16 * k] = row[2 * k]; Q[2 * i + 16 * k + 1] = row[2 * k + 1]; }
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 128; i++) R[i] = Q[i] ^ R[i];
|
||||||
|
if (xorMode) {
|
||||||
|
gSdFile.seek((uint64_t)outIdx * A2_BLKSZ); gSdFile.read(gBufPrev, A2_BLKSZ);
|
||||||
|
uint64_t *O = (uint64_t *)gBufPrev; for (int i = 0; i < 128; i++) R[i] ^= O[i];
|
||||||
|
}
|
||||||
|
gSdFile.seek((uint64_t)outIdx * A2_BLKSZ); gSdFile.write(gBufOut, A2_BLKSZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void generateAddresses(uint32_t pass, uint32_t slice) {
|
||||||
|
memset(gBufZero, 0, A2_BLKSZ);
|
||||||
|
uint8_t inputBlk[A2_BLKSZ]; memset(inputBlk, 0, A2_BLKSZ);
|
||||||
|
uint64_t *iv = (uint64_t *)inputBlk;
|
||||||
|
iv[0]=pass;iv[1]=0;iv[2]=slice;iv[3]=A2_M;iv[4]=A2_T;iv[5]=A2_TYPE;
|
||||||
|
uint32_t count = 0;
|
||||||
|
for (uint32_t b = 0; b < A2_SEG; b += 128) {
|
||||||
|
iv[6] = ++count;
|
||||||
|
{
|
||||||
|
uint64_t *R=(uint64_t*)gBufAddr,*Z=(uint64_t*)gBufZero,*I=iv;
|
||||||
|
for(int i=0;i<128;i++)R[i]=Z[i]^I[i];
|
||||||
|
uint64_t *Q=R,row[16];
|
||||||
|
for(int j=0;j<8;j++)A2P(&Q[16*j]);
|
||||||
|
for(int i=0;i<8;i++){
|
||||||
|
for(int k=0;k<8;k++){row[2*k]=Q[2*i+16*k];row[2*k+1]=Q[2*i+16*k+1];}
|
||||||
|
A2P(row);
|
||||||
|
for(int k=0;k<8;k++){Q[2*i+16*k]=row[2*k];Q[2*i+16*k+1]=row[2*k+1];}
|
||||||
|
}
|
||||||
|
for(int i=0;i<128;i++)R[i]=Q[i]^(Z[i]^I[i]);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
uint64_t *R=(uint64_t*)gBufPrev,*Z=(uint64_t*)gBufZero,*A=(uint64_t*)gBufAddr;
|
||||||
|
for(int i=0;i<128;i++)R[i]=Z[i]^A[i];
|
||||||
|
uint64_t *Q=R,row[16];
|
||||||
|
for(int j=0;j<8;j++)A2P(&Q[16*j]);
|
||||||
|
for(int i=0;i<8;i++){
|
||||||
|
for(int k=0;k<8;k++){row[2*k]=Q[2*i+16*k];row[2*k+1]=Q[2*i+16*k+1];}
|
||||||
|
A2P(row);
|
||||||
|
for(int k=0;k<8;k++){Q[2*i+16*k]=row[2*k];Q[2*i+16*k+1]=row[2*k+1];}
|
||||||
|
}
|
||||||
|
for(int i=0;i<128;i++)R[i]=Q[i]^(Z[i]^A[i]);
|
||||||
|
memcpy(gBufAddr,gBufPrev,A2_BLKSZ);
|
||||||
|
}
|
||||||
|
uint64_t *addr=(uint64_t*)gBufAddr;
|
||||||
|
uint32_t cnt=(b+128<=A2_SEG)?128:(A2_SEG-b);
|
||||||
|
for(uint32_t j=0;j<cnt;j++)gJ1Seg[b+j]=(uint32_t)(addr[j]&0xFFFFFFFFULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t indexAlpha(uint32_t pass, uint32_t slice, uint32_t posInSlice, uint32_t J1) {
|
||||||
|
uint32_t refAreaSize;
|
||||||
|
if (pass == 0) {
|
||||||
|
if (slice == 0) {
|
||||||
|
uint32_t pos = posInSlice;
|
||||||
|
refAreaSize = (pos < 2) ? 0 : pos - 1;
|
||||||
|
} else {
|
||||||
|
refAreaSize = slice * A2_SEG + posInSlice - 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
refAreaSize = A2_M - A2_SEG + posInSlice - 1;
|
||||||
|
}
|
||||||
|
if (refAreaSize == 0) refAreaSize = 1;
|
||||||
|
uint64_t relPos = (uint64_t)J1 * J1 >> 32;
|
||||||
|
relPos = refAreaSize - 1 - ((uint64_t)refAreaSize * relPos >> 32);
|
||||||
|
uint32_t startPos = 0;
|
||||||
|
if (pass != 0 && slice != A2_SYNC - 1) startPos = (slice + 1) * A2_SEG;
|
||||||
|
return (startPos + relPos) % A2_M;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sha256calc(const uint8_t *in, size_t len, uint8_t *out32) {
|
||||||
|
mbedtls_sha256_context ctx;
|
||||||
|
mbedtls_sha256_init(&ctx);
|
||||||
|
mbedtls_sha256_starts(&ctx, 0);
|
||||||
|
mbedtls_sha256_update(&ctx, in, len);
|
||||||
|
mbedtls_sha256_finish(&ctx, out32);
|
||||||
|
mbedtls_sha256_free(&ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void trimInPlace(char *text) {
|
||||||
|
if (!text) return;
|
||||||
|
size_t len = strlen(text);
|
||||||
|
size_t start = 0;
|
||||||
|
while (start < len && (text[start] == ' ' || text[start] == '\t' || text[start] == '\n' || text[start] == '\r')) start++;
|
||||||
|
size_t end = len;
|
||||||
|
while (end > start && (text[end - 1] == ' ' || text[end - 1] == '\t' || text[end - 1] == '\n' || text[end - 1] == '\r')) end--;
|
||||||
|
if (start > 0 && end > start) memmove(text, text + start, end - start);
|
||||||
|
if (end <= start) { text[0] = '\0'; return; }
|
||||||
|
text[end - start] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
static void lowercaseAsciiInPlace(char *text) {
|
||||||
|
if (!text) return;
|
||||||
|
for (int i = 0; text[i]; i++) if (text[i] >= 'A' && text[i] <= 'Z') text[i] += 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void normalizeLoginInPlace(char *text) {
|
||||||
|
trimInPlace(text);
|
||||||
|
lowercaseAsciiInPlace(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bytesToBase64Std(const uint8_t *data, size_t len, char *out, size_t outSz) {
|
||||||
|
size_t b64len = 0;
|
||||||
|
if (outSz == 0) return;
|
||||||
|
if (mbedtls_base64_encode((uint8_t*)out, outSz, &b64len, data, len) != 0) {
|
||||||
|
out[0] = '\0';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (b64len >= outSz) b64len = outSz - 1;
|
||||||
|
out[b64len] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
static void deriveLegacyMasterSecret(const char *password, uint8_t *out32) {
|
||||||
|
uint8_t baseHash[32];
|
||||||
|
sha256calc((const uint8_t*)password, strlen(password), baseHash);
|
||||||
|
char baseB64[64];
|
||||||
|
bytesToBase64Std(baseHash, 32, baseB64, sizeof(baseB64));
|
||||||
|
char material[96];
|
||||||
|
snprintf(material, sizeof(material), "%smaster.secret", baseB64);
|
||||||
|
sha256calc((const uint8_t*)material, strlen(material), out32);
|
||||||
|
}
|
||||||
|
|
||||||
|
void shineSecretBase58Encode(const uint8_t *data, size_t len, char *out, size_t outSz) {
|
||||||
|
int zeros = 0;
|
||||||
|
while (zeros < (int)len && data[zeros] == 0) zeros++;
|
||||||
|
uint8_t tmp[128] = {};
|
||||||
|
int tmpLen = 0;
|
||||||
|
for (int i = zeros; i < (int)len; i++) {
|
||||||
|
int carry = data[i];
|
||||||
|
for (int j = 0; j < tmpLen; j++) { carry += 256 * tmp[j]; tmp[j] = carry % 58; carry /= 58; }
|
||||||
|
while (carry) { tmp[tmpLen++] = carry % 58; carry /= 58; }
|
||||||
|
}
|
||||||
|
int n = 0;
|
||||||
|
for (int i = 0; i < zeros && n < (int)outSz - 1; i++) out[n++] = '1';
|
||||||
|
for (int i = tmpLen - 1; i >= 0 && n < (int)outSz - 1; i--) out[n++] = B58_ALPHA[(int)tmp[i]];
|
||||||
|
out[n] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool shineSecretBase58Decode(const char *input, uint8_t *out, size_t *outLen, size_t maxOutLen, String &error) {
|
||||||
|
error = "";
|
||||||
|
if (!input || !*input) {
|
||||||
|
error = "secret too short";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
size_t inLen = strlen(input);
|
||||||
|
uint8_t tmp[256] = {};
|
||||||
|
size_t tmpLen = 0;
|
||||||
|
size_t zeros = 0;
|
||||||
|
while (zeros < inLen && input[zeros] == '1') zeros++;
|
||||||
|
|
||||||
|
for (size_t i = zeros; i < inLen; ++i) {
|
||||||
|
const char *pos = strchr(B58_ALPHA, input[i]);
|
||||||
|
if (!pos) {
|
||||||
|
error = "invalid base58";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int carry = (int)(pos - B58_ALPHA);
|
||||||
|
for (size_t j = 0; j < tmpLen; ++j) {
|
||||||
|
carry += 58 * tmp[j];
|
||||||
|
tmp[j] = carry & 0xFF;
|
||||||
|
carry >>= 8;
|
||||||
|
}
|
||||||
|
while (carry > 0) {
|
||||||
|
tmp[tmpLen++] = carry & 0xFF;
|
||||||
|
carry >>= 8;
|
||||||
|
if (tmpLen >= sizeof(tmp)) {
|
||||||
|
error = "secret too long";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t decodedLen = zeros + tmpLen;
|
||||||
|
if (decodedLen > maxOutLen) {
|
||||||
|
error = "secret too long";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
memset(out, 0, maxOutLen);
|
||||||
|
size_t offset = 0;
|
||||||
|
for (size_t i = 0; i < zeros; ++i) out[offset++] = 0;
|
||||||
|
for (size_t i = 0; i < tmpLen; ++i) out[offset + i] = tmp[tmpLen - 1 - i];
|
||||||
|
*outLen = decodedLen;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void argon2Init(const char *login, const char *password) {
|
||||||
|
char saltSrc[256];
|
||||||
|
snprintf(saltSrc, sizeof(saltSrc), "shine-auth-v2|login=%s|suffix=master.secret", login);
|
||||||
|
uint8_t saltHash[32]; sha256calc((uint8_t*)saltSrc, strlen(saltSrc), saltHash);
|
||||||
|
uint8_t salt[16]; memcpy(salt, saltHash, 16);
|
||||||
|
|
||||||
|
char passBytes[128];
|
||||||
|
snprintf(passBytes, sizeof(passBytes), "%s\n%s", login, password);
|
||||||
|
uint32_t passLen = strlen(passBytes), saltLen = 16;
|
||||||
|
|
||||||
|
b2_init(&gB2S, 64);
|
||||||
|
uint8_t le4[4];
|
||||||
|
#define B2LE32(val) do{uint32_t _v=(val);le4[0]=_v;le4[1]=_v>>8;le4[2]=_v>>16;le4[3]=_v>>24;b2_update(&gB2S,le4,4);}while(0)
|
||||||
|
B2LE32(A2_P);B2LE32(A2_DKLEN);B2LE32(A2_M);B2LE32(A2_T);
|
||||||
|
B2LE32(A2_VERSION);B2LE32(A2_TYPE);
|
||||||
|
B2LE32(passLen);b2_update(&gB2S,(const uint8_t*)passBytes,passLen);
|
||||||
|
B2LE32(saltLen);b2_update(&gB2S,salt,saltLen);
|
||||||
|
B2LE32(0);B2LE32(0);
|
||||||
|
#undef B2LE32
|
||||||
|
b2_final(&gB2S, gH0);
|
||||||
|
|
||||||
|
uint8_t input[72]; memcpy(input, gH0, 64);
|
||||||
|
for (uint32_t i = 0; i < 2; i++) {
|
||||||
|
input[64]=i;input[65]=0;input[66]=0;input[67]=0;
|
||||||
|
input[68]=0;input[69]=0;input[70]=0;input[71]=0;
|
||||||
|
b2long(input, 72, gBufOut, A2_BLKSZ);
|
||||||
|
gSdFile.seek((uint64_t)i * A2_BLKSZ); gSdFile.write(gBufOut, A2_BLKSZ);
|
||||||
|
}
|
||||||
|
gCurPass = 0;
|
||||||
|
gCurBlock = 2;
|
||||||
|
gDoneBlocks = 0;
|
||||||
|
gStartMs = millis();
|
||||||
|
gInitDone = true;
|
||||||
|
generateAddresses(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool shineSecretInitSd(String &error) {
|
||||||
|
error = "";
|
||||||
|
if (gSdReady) return true;
|
||||||
|
gpio_reset_pin(GPIO_NUM_2);
|
||||||
|
pinMode(PIN_SD_CMD, INPUT_PULLUP);
|
||||||
|
pinMode(PIN_SD_D0, INPUT_PULLUP);
|
||||||
|
delay(20);
|
||||||
|
SD_MMC.setPins(PIN_SD_CLK, PIN_SD_CMD, PIN_SD_D0);
|
||||||
|
gSdReady = SD_MMC.begin("/sdcard", true);
|
||||||
|
if (!gSdReady) {
|
||||||
|
error = "SD card error";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!gBufPrev) {
|
||||||
|
gBufPrev=(uint8_t*)ps_malloc(A2_BLKSZ);
|
||||||
|
gBufRef=(uint8_t*)ps_malloc(A2_BLKSZ);
|
||||||
|
gBufOut=(uint8_t*)ps_malloc(A2_BLKSZ);
|
||||||
|
gBufZero=(uint8_t*)ps_calloc(1, A2_BLKSZ);
|
||||||
|
gBufAddr=(uint8_t*)ps_malloc(A2_BLKSZ);
|
||||||
|
gJ1Seg=(uint32_t*)ps_malloc(A2_SEG * sizeof(uint32_t));
|
||||||
|
}
|
||||||
|
if (!gBufPrev || !gBufRef || !gBufOut || !gBufZero || !gBufAddr || !gJ1Seg) {
|
||||||
|
error = "PSRAM alloc failed";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool shineSecretStart(const char *normalizedLogin, const char *password, String &error) {
|
||||||
|
error = "";
|
||||||
|
if (!shineSecretInitSd(error)) return false;
|
||||||
|
if (!normalizedLogin || !*normalizedLogin) {
|
||||||
|
error = "login not set";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
char loginBuf[64];
|
||||||
|
snprintf(loginBuf, sizeof(loginBuf), "%s", normalizedLogin);
|
||||||
|
normalizeLoginInPlace(loginBuf);
|
||||||
|
if (!loginBuf[0]) {
|
||||||
|
error = "login not set";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gSdFile) gSdFile.close();
|
||||||
|
SD_MMC.remove(SD_MEM_FILE);
|
||||||
|
gSdFile = SD_MMC.open(SD_MEM_FILE, "w+");
|
||||||
|
if (!gSdFile) {
|
||||||
|
error = "SD open failed";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(gSecret, 0, sizeof(gSecret));
|
||||||
|
gSecretB58[0] = '\0';
|
||||||
|
gDone = false;
|
||||||
|
gError = false;
|
||||||
|
gRunning = true;
|
||||||
|
gInitDone = false;
|
||||||
|
setMessage("Generating secret...");
|
||||||
|
|
||||||
|
if (!password || !password[0]) {
|
||||||
|
deriveLegacyMasterSecret(password ? password : "", gSecret);
|
||||||
|
shineSecretBase58Encode(gSecret, 32, gSecretB58, sizeof(gSecretB58));
|
||||||
|
gDone = true;
|
||||||
|
gRunning = false;
|
||||||
|
setMessage("Secret generated");
|
||||||
|
gSdFile.close();
|
||||||
|
SD_MMC.remove(SD_MEM_FILE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
argon2Init(loginBuf, password);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool shineSecretStep(uint32_t blocksPerTick) {
|
||||||
|
if (!gRunning || !gInitDone) return gDone;
|
||||||
|
bool done = false;
|
||||||
|
for (uint32_t i = 0; i < blocksPerTick && !done; i++) {
|
||||||
|
if (gCurPass >= A2_T) {
|
||||||
|
done = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
uint32_t blk = gCurBlock;
|
||||||
|
uint32_t slice = blk / A2_SEG;
|
||||||
|
uint32_t posInSlice = blk - slice * A2_SEG;
|
||||||
|
bool xorMode = (gCurPass > 0);
|
||||||
|
uint32_t J1;
|
||||||
|
bool useArgon2i = (gCurPass == 0 && slice < 2);
|
||||||
|
if (useArgon2i) {
|
||||||
|
J1 = gJ1Seg[posInSlice];
|
||||||
|
} else {
|
||||||
|
uint32_t prevIdx = (blk == 0) ? A2_M - 1 : blk - 1;
|
||||||
|
gSdFile.seek((uint64_t)prevIdx * A2_BLKSZ);
|
||||||
|
gSdFile.read(gBufRef, 8);
|
||||||
|
J1 = ((uint32_t*)gBufRef)[0];
|
||||||
|
}
|
||||||
|
uint32_t prevIdx = (blk == 0) ? A2_M - 1 : blk - 1;
|
||||||
|
uint32_t refIdx = indexAlpha(gCurPass, slice, posInSlice, J1);
|
||||||
|
fillBlock(prevIdx, refIdx, blk, xorMode);
|
||||||
|
gDoneBlocks++;
|
||||||
|
gCurBlock++;
|
||||||
|
if (gCurBlock >= A2_M) {
|
||||||
|
gCurBlock = 0;
|
||||||
|
gCurPass++;
|
||||||
|
} else {
|
||||||
|
uint32_t newSlice = gCurBlock / A2_SEG;
|
||||||
|
if (newSlice != slice && gCurPass == 0 && newSlice < 2) generateAddresses(gCurPass, newSlice);
|
||||||
|
}
|
||||||
|
done = (gCurPass >= A2_T);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (done) {
|
||||||
|
gSdFile.seek((uint64_t)(A2_M - 1) * A2_BLKSZ);
|
||||||
|
gSdFile.read(gBufOut, A2_BLKSZ);
|
||||||
|
b2long(gBufOut, A2_BLKSZ, gSecret, A2_DKLEN);
|
||||||
|
shineSecretBase58Encode(gSecret, 32, gSecretB58, sizeof(gSecretB58));
|
||||||
|
gDone = true;
|
||||||
|
gRunning = false;
|
||||||
|
setMessage("Secret generated");
|
||||||
|
gSdFile.close();
|
||||||
|
SD_MMC.remove(SD_MEM_FILE);
|
||||||
|
}
|
||||||
|
return gDone;
|
||||||
|
}
|
||||||
|
|
||||||
|
void shineSecretAbort() {
|
||||||
|
gRunning = false;
|
||||||
|
gDone = false;
|
||||||
|
gError = false;
|
||||||
|
gInitDone = false;
|
||||||
|
gDoneBlocks = 0;
|
||||||
|
if (gSdFile) gSdFile.close();
|
||||||
|
if (gSdReady) SD_MMC.remove(SD_MEM_FILE);
|
||||||
|
setMessage("Generation cancelled");
|
||||||
|
}
|
||||||
|
|
||||||
|
ShineSecretGenerationStatus shineSecretStatus() {
|
||||||
|
ShineSecretGenerationStatus status = {};
|
||||||
|
status.running = gRunning;
|
||||||
|
status.done = gDone;
|
||||||
|
status.error = gError;
|
||||||
|
status.doneBlocks = gDoneBlocks;
|
||||||
|
status.totalBlocks = TOTAL_FILLS;
|
||||||
|
status.elapsedSec = gStartMs == 0 ? 0 : (millis() - gStartMs) / 1000;
|
||||||
|
snprintf(status.message, sizeof(status.message), "%s", gMessage);
|
||||||
|
snprintf(status.secretBase58, sizeof(status.secretBase58), "%s", gSecretB58);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t *shineSecretBytes() {
|
||||||
|
return gSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *shineSecretBase58() {
|
||||||
|
return gSecretB58;
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
struct ShineSecretGenerationStatus {
|
||||||
|
bool running;
|
||||||
|
bool done;
|
||||||
|
bool error;
|
||||||
|
uint32_t doneBlocks;
|
||||||
|
uint32_t totalBlocks;
|
||||||
|
uint32_t elapsedSec;
|
||||||
|
char message[96];
|
||||||
|
char secretBase58[64];
|
||||||
|
};
|
||||||
|
|
||||||
|
bool shineSecretInitSd(String &error);
|
||||||
|
bool shineSecretStart(const char *normalizedLogin, const char *password, String &error);
|
||||||
|
bool shineSecretStep(uint32_t blocksPerTick = 4);
|
||||||
|
void shineSecretAbort();
|
||||||
|
ShineSecretGenerationStatus shineSecretStatus();
|
||||||
|
const uint8_t *shineSecretBytes();
|
||||||
|
const char *shineSecretBase58();
|
||||||
|
void shineSecretBase58Encode(const uint8_t *data, size_t len, char *out, size_t outSz);
|
||||||
|
bool shineSecretBase58Decode(const char *input, uint8_t *out, size_t *outLen, size_t maxOutLen, String &error);
|
||||||
@ -1,2 +1,2 @@
|
|||||||
client.version=1.2.144
|
client.version=1.2.145
|
||||||
server.version=1.2.136
|
server.version=1.2.137
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user