From 365b22d77870489bcb5e931accd5d40e9e4ecc669a7fc42e1e5daf96031b6910 Mon Sep 17 00:00:00 2001 From: AidarKC Date: Tue, 23 Jun 2026 12:06:24 +0400 Subject: [PATCH] =?UTF-8?q?ESP32:=20=D0=BA=D1=8D=D1=88=20=D0=BF=D0=BE?= =?UTF-8?q?=D1=81=D0=BB=D0=B5=D0=B4=D0=BD=D0=B8=D1=85=20=D0=B3=D0=B5=D0=BD?= =?UTF-8?q?=D0=B5=D1=80=D0=B0=D1=86=D0=B8=D0=B9=20=D1=81=D0=B5=D0=BA=D1=80?= =?UTF-8?q?=D0=B5=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-06-23_1325_esp32_secret_cache.md | 25 +++ .../shine_secret_generation.cpp | 151 +++++++++++++++++- VERSION.properties | 4 +- 3 files changed, 177 insertions(+), 3 deletions(-) create mode 100644 Dev_Docs/Pending_Features/2026-06-23_1325_esp32_secret_cache.md diff --git a/Dev_Docs/Pending_Features/2026-06-23_1325_esp32_secret_cache.md b/Dev_Docs/Pending_Features/2026-06-23_1325_esp32_secret_cache.md new file mode 100644 index 0000000..9b8fd63 --- /dev/null +++ b/Dev_Docs/Pending_Features/2026-06-23_1325_esp32_secret_cache.md @@ -0,0 +1,25 @@ +## Кратко + +Добавлен локальный кэш последних 5 успешных генераций мастер-секрета на ESP32. + +## Что сделано + +- Генерация секретов теперь сначала проверяет кэш по `SHA-256(login + 0x00 + password)`. +- При совпадении ESP32 сразу подставляет готовый секрет и не запускает долгую генерацию. +- Успешные результаты складываются в NVS как последние 5 записей. + +## Что проверять + +- Сгенерировать секрет для `login + password`. +- Повторить ту же пару ещё раз. +- Убедиться, что вторая попытка не запускает долгую генерацию и сразу показывает готовый секрет. +- Проверить, что после 6 различных успешных генераций самая старая запись вытесняется. + +## Ожидаемый результат + +- Повторная генерация той же пары `login + password` берётся из кэша. +- Кэш хранит только последние 5 успешных результатов. + +## Статус + +`pending` diff --git a/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/main-device/shine_homeserver_main/shine_secret_generation.cpp b/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/main-device/shine_homeserver_main/shine_secret_generation.cpp index 9e386fe..d2a7129 100644 --- a/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/main-device/shine_homeserver_main/shine_secret_generation.cpp +++ b/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/main-device/shine_homeserver_main/shine_secret_generation.cpp @@ -37,6 +37,10 @@ static bool gError = false; static char gMessage[96] = {}; static uint8_t gSecret[32] = {}; static char gSecretB58[64] = {}; +static Preferences gCachePrefs; +static bool gCachePrefsReady = false; +static char gCacheLogin[32] = {}; +static uint8_t gCacheLookupHash[32] = {}; static uint32_t gDoneBlocks = 0; static uint32_t gStartMs = 0; static uint32_t gCurPass = 0; @@ -80,6 +84,136 @@ static void setMessage(const char *message) { snprintf(gMessage, sizeof(gMessage), "%s", message ? message : ""); } +static void sha256calc(const uint8_t *in, size_t len, uint8_t *out32); + +static const char *CACHE_NS = "secret_cache"; +static const char *CACHE_VERSION_KEY = "ver"; +static const char *CACHE_HEAD_KEY = "head"; +static const char *CACHE_COUNT_KEY = "count"; +static const uint8_t CACHE_VERSION = 1; +static const uint8_t CACHE_MAX_ENTRIES = 5; + +struct SecretCacheEntry { + char login[32]; + uint8_t lookupHash[32]; + uint8_t secret[32]; +}; + +static void computeLookupHash(const char *normalizedLogin, const char *password, uint8_t out32[32]) { + std::vector material; + const char *login = normalizedLogin ? normalizedLogin : ""; + const char *pwd = password ? password : ""; + size_t loginLen = strlen(login); + size_t pwdLen = strlen(pwd); + material.reserve(loginLen + 1 + pwdLen); + material.insert(material.end(), login, login + loginLen); + material.push_back(0); + material.insert(material.end(), pwd, pwd + pwdLen); + sha256calc(material.data(), material.size(), out32); +} + +static bool cacheOpen(String &error) { + error = ""; + if (gCachePrefsReady) return true; + if (!gCachePrefs.begin(CACHE_NS, false)) { + error = "Cache NVS open failed"; + return false; + } + if (!gCachePrefs.isKey(CACHE_VERSION_KEY)) { + gCachePrefs.putUChar(CACHE_VERSION_KEY, CACHE_VERSION); + gCachePrefs.putUChar(CACHE_HEAD_KEY, 0); + gCachePrefs.putUChar(CACHE_COUNT_KEY, 0); + } + gCachePrefsReady = true; + return true; +} + +static void cacheClose() { + if (gCachePrefsReady) { + gCachePrefs.end(); + gCachePrefsReady = false; + } +} + +static String cacheSlotKey(uint8_t slot) { + return String("s") + String(slot); +} + +static bool loadCacheEntry(uint8_t slot, SecretCacheEntry &entry) { + memset(&entry, 0, sizeof(entry)); + String key = cacheSlotKey(slot); + if (!gCachePrefs.isKey(key.c_str())) { + return false; + } + size_t len = gCachePrefs.getBytesLength(key.c_str()); + if (len != sizeof(entry)) { + return false; + } + return gCachePrefs.getBytes(key.c_str(), &entry, sizeof(entry)) == sizeof(entry); +} + +static void storeCacheEntry(uint8_t slot, const SecretCacheEntry &entry) { + String key = cacheSlotKey(slot); + gCachePrefs.putBytes(key.c_str(), &entry, sizeof(entry)); +} + +static bool findCachedSecret(const char *normalizedLogin, const char *password, uint8_t secretOut[32]) { + String error; + if (!cacheOpen(error)) { + return false; + } + uint8_t lookupHash[32]; + computeLookupHash(normalizedLogin, password, lookupHash); + uint8_t head = gCachePrefs.getUChar(CACHE_HEAD_KEY, 0); + uint8_t count = gCachePrefs.getUChar(CACHE_COUNT_KEY, 0); + for (uint8_t i = 0; i < count && i < CACHE_MAX_ENTRIES; ++i) { + uint8_t slot = (uint8_t)((head + CACHE_MAX_ENTRIES - 1 - i) % CACHE_MAX_ENTRIES); + SecretCacheEntry entry; + if (!loadCacheEntry(slot, entry)) { + continue; + } + if (memcmp(entry.lookupHash, lookupHash, 32) == 0) { + memcpy(secretOut, entry.secret, 32); + snprintf(gCacheLogin, sizeof(gCacheLogin), "%s", entry.login); + memcpy(gCacheLookupHash, lookupHash, 32); + return true; + } + } + return false; +} + +static void rememberSecret(const char *normalizedLogin, const uint8_t secret32[32]) { + String error; + if (!cacheOpen(error)) { + return; + } + SecretCacheEntry entry = {}; + snprintf(entry.login, sizeof(entry.login), "%s", normalizedLogin ? normalizedLogin : ""); + memcpy(entry.lookupHash, gCacheLookupHash, 32); + memcpy(entry.secret, secret32, 32); + uint8_t head = gCachePrefs.getUChar(CACHE_HEAD_KEY, 0); + uint8_t count = gCachePrefs.getUChar(CACHE_COUNT_KEY, 0); + storeCacheEntry(head, entry); + head = (uint8_t)((head + 1) % CACHE_MAX_ENTRIES); + if (count < CACHE_MAX_ENTRIES) { + count++; + } + gCachePrefs.putUChar(CACHE_HEAD_KEY, head); + gCachePrefs.putUChar(CACHE_COUNT_KEY, count); + gCachePrefs.putUChar(CACHE_VERSION_KEY, CACHE_VERSION); +} + +static void finishSecretFromBytes(const uint8_t secret32[32], const char *message) { + memcpy(gSecret, secret32, 32); + shineSecretBase58Encode(gSecret, 32, gSecretB58, sizeof(gSecretB58)); + gDone = true; + gRunning = false; + gError = false; + gInitDone = false; + gDoneBlocks = TOTAL_FILLS; + setMessage(message ? message : "Secret generated"); +} + 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]; @@ -449,7 +583,6 @@ bool shineSecretInitSd(String &error) { 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; @@ -463,6 +596,20 @@ bool shineSecretStart(const char *normalizedLogin, const char *password, String return false; } + uint8_t cachedSecret[32] = {}; + if (findCachedSecret(loginBuf, password ? password : "", cachedSecret)) { + if (gSdFile) gSdFile.close(); + snprintf(gCacheLogin, sizeof(gCacheLogin), "%s", loginBuf); + computeLookupHash(loginBuf, password ? password : "", gCacheLookupHash); + finishSecretFromBytes(cachedSecret, "Secret loaded from cache"); + return true; + } + + snprintf(gCacheLogin, sizeof(gCacheLogin), "%s", loginBuf); + computeLookupHash(loginBuf, password ? password : "", gCacheLookupHash); + + if (!shineSecretInitSd(error)) return false; + if (gSdFile) gSdFile.close(); SD_MMC.remove(SD_MEM_FILE); gSdFile = SD_MMC.open(SD_MEM_FILE, "w+"); @@ -482,6 +629,7 @@ bool shineSecretStart(const char *normalizedLogin, const char *password, String if (!password || !password[0]) { deriveLegacyMasterSecret(password ? password : "", gSecret); shineSecretBase58Encode(gSecret, 32, gSecretB58, sizeof(gSecretB58)); + rememberSecret(loginBuf, gSecret); gDone = true; gRunning = false; setMessage("Secret generated"); @@ -536,6 +684,7 @@ bool shineSecretStep(uint32_t blocksPerTick) { gSdFile.read(gBufOut, A2_BLKSZ); b2long(gBufOut, A2_BLKSZ, gSecret, A2_DKLEN); shineSecretBase58Encode(gSecret, 32, gSecretB58, sizeof(gSecretB58)); + rememberSecret(gCacheLogin, gSecret); gDone = true; gRunning = false; setMessage("Secret generated"); diff --git a/VERSION.properties b/VERSION.properties index 54d8bf7..0bb807b 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.237 -server.version=1.2.223 +client.version=1.2.238 +server.version=1.2.224