Fix Wi-Fi credential save with plain NVS (encryption off).
Initialise NVS before integration startup, recover from encrypted-partition mismatches without re-enabling encryption, and improve provisioning logs and POST body handling. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -36,9 +36,9 @@ enum class NvsInitError {
|
||||
*
|
||||
* @dname initEncryptedStorage
|
||||
* @return Ok on success, or NvsInitError describing the failure.
|
||||
* @pubstate When CONFIG_NVS_ENCRYPTION is set, nvs_flash_init() uses the
|
||||
* nvs_keys partition and flash-encryption key protection per
|
||||
* ESP-IDF v5.5 security docs. Erases and retries on layout mismatch.
|
||||
* @pubstate Plain NVS by default (encryption off in sdkconfig.defaults).
|
||||
* Erases and retries on layout or encryption-format mismatch.
|
||||
* When CONFIG_NVS_ENCRYPTION is set, uses nvs_keys per ESP-IDF docs.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-07
|
||||
|
||||
@@ -22,6 +22,32 @@ namespace secure_store {
|
||||
namespace {
|
||||
constexpr char kTag[] = "NvsPlatformInit";
|
||||
|
||||
/**
|
||||
* @brief isRecoverableNvsInitError — true when erase + re-init may help.
|
||||
*
|
||||
* @dname isRecoverableNvsInitError
|
||||
* @param err Return code from nvs_flash_init().
|
||||
* @return true for layout/encryption mismatch errors.
|
||||
* @pubstate none
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-08-05
|
||||
*/
|
||||
[[nodiscard]] bool isRecoverableNvsInitError(esp_err_t err) noexcept
|
||||
{
|
||||
if (err == ESP_ERR_NVS_NO_FREE_PAGES
|
||||
|| err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
return true;
|
||||
}
|
||||
#if !CONFIG_NVS_ENCRYPTION
|
||||
// Plain-NVS build after encrypted-NVS flash: erase once to migrate.
|
||||
return err == ESP_ERR_NVS_WRONG_ENCRYPTION
|
||||
|| err == ESP_ERR_NVS_CORRUPT_KEY_PART;
|
||||
#else
|
||||
return err == ESP_ERR_NVS_CORRUPT_KEY_PART;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief logEncryptionMode — log active NVS security Kconfig (no secrets).
|
||||
*
|
||||
@@ -42,7 +68,7 @@ void logEncryptionMode() noexcept
|
||||
ESP_LOGW(kTag, "NVS encryption without flash encryption — check Kconfig");
|
||||
#endif
|
||||
#else
|
||||
ESP_LOGW(kTag, "NVS encryption disabled — not for production");
|
||||
ESP_LOGI(kTag, "NVS plain mode (encryption off — current bring-up default)");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -53,9 +79,15 @@ std::expected<void, NvsInitError> initEncryptedStorage() noexcept
|
||||
logEncryptionMode();
|
||||
|
||||
esp_err_t err = nvs_flash_init();
|
||||
if (err == ESP_ERR_NVS_NO_FREE_PAGES
|
||||
|| err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_LOGW(kTag, "NVS partition needs erase (err=0x%x)", static_cast<unsigned>(err));
|
||||
if (err == ESP_OK) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (isRecoverableNvsInitError(err)) {
|
||||
ESP_LOGW(kTag,
|
||||
"NVS partition incompatible (0x%x) — erasing (stored Wi-Fi "
|
||||
"and presets will be cleared)",
|
||||
static_cast<unsigned>(err));
|
||||
err = nvs_flash_erase();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "nvs_flash_erase failed (0x%x)", static_cast<unsigned>(err));
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include "secure_store/NvsSecureStore.hpp"
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
@@ -32,12 +33,16 @@ constexpr char kSsidKey[] = "wifi_ssid";
|
||||
constexpr char kPasswordKey[] = "wifi_pwd";
|
||||
constexpr char kStationListKey[] = "station_list";
|
||||
constexpr char kLastPresetKey[] = "last_preset";
|
||||
constexpr char kTag[] = "NvsSecureStore";
|
||||
} // namespace
|
||||
|
||||
bool NvsSecureStore::hasWifiCredentials() const
|
||||
{
|
||||
nvs_handle_t handle = 0;
|
||||
if (nvs_open(kNamespace, NVS_READONLY, &handle) != ESP_OK) {
|
||||
const esp_err_t openErr = nvs_open(kNamespace, NVS_READONLY, &handle);
|
||||
if (openErr != ESP_OK) {
|
||||
ESP_LOGW(kTag, "nvs_open failed in hasWifiCredentials (0x%x)",
|
||||
static_cast<unsigned>(openErr));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -53,7 +58,9 @@ std::expected<void, core::StoreError>
|
||||
NvsSecureStore::saveWifiCredentials(const core::WifiCredentials& creds)
|
||||
{
|
||||
nvs_handle_t handle = 0;
|
||||
if (nvs_open(kNamespace, NVS_READWRITE, &handle) != ESP_OK) {
|
||||
const esp_err_t openErr = nvs_open(kNamespace, NVS_READWRITE, &handle);
|
||||
if (openErr != ESP_OK) {
|
||||
ESP_LOGE(kTag, "nvs_open failed (0x%x)", static_cast<unsigned>(openErr));
|
||||
return std::unexpected(core::StoreError::IoFailed);
|
||||
}
|
||||
|
||||
@@ -76,8 +83,10 @@ NvsSecureStore::saveWifiCredentials(const core::WifiCredentials& creds)
|
||||
}
|
||||
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "save wifi credentials failed (0x%x)", static_cast<unsigned>(err));
|
||||
return std::unexpected(core::StoreError::IoFailed);
|
||||
}
|
||||
ESP_LOGI(kTag, "Wi-Fi credentials saved");
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -85,7 +94,10 @@ std::expected<core::WifiCredentials, core::StoreError>
|
||||
NvsSecureStore::loadWifiCredentials() const
|
||||
{
|
||||
nvs_handle_t handle = 0;
|
||||
if (nvs_open(kNamespace, NVS_READONLY, &handle) != ESP_OK) {
|
||||
const esp_err_t openErr = nvs_open(kNamespace, NVS_READONLY, &handle);
|
||||
if (openErr != ESP_OK) {
|
||||
ESP_LOGW(kTag, "nvs_open failed in loadWifiCredentials (0x%x)",
|
||||
static_cast<unsigned>(openErr));
|
||||
return std::unexpected(core::StoreError::NotFound);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user