-
-
Notifications
You must be signed in to change notification settings - Fork 4.1k
Add new feature: mirrored and crc protected configuration files #5421
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,8 @@ | ||
| #include "wled.h" | ||
| #include "wled_ethernet.h" | ||
|
|
||
| #ifdef WLED_ENABLE_CRC_FOR_CONFIG | ||
| #include "cfg_crc.h" | ||
| #endif | ||
| /* | ||
| * Serializes and parses the cfg.json and wsec.json settings files, stored in internal FS. | ||
| * The structure of the JSON is not to be considered an official API and may change without notice. | ||
|
|
@@ -22,6 +24,7 @@ | |
| #define DEFAULT_LED_COLOR_ORDER COL_ORDER_GRB //default to GRB | ||
| #endif | ||
|
|
||
|
|
||
| static constexpr unsigned sumPinsRequired(const unsigned* current, size_t count) { | ||
| return (count > 0) ? (Bus::getNumberOfPins(*current) + sumPinsRequired(current+1,count-1)) : 0; | ||
| } | ||
|
|
@@ -768,13 +771,26 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { | |
| } | ||
|
|
||
| static const char s_cfg_json[] PROGMEM = "/cfg.json"; | ||
| static const char s_wsec_json[] PROGMEM = "/wsec.json"; | ||
| #ifdef WLED_ENABLE_CRC_FOR_CONFIG | ||
| static const char s_cfg_backup_json[] PROGMEM = "/cfg_backup.json"; | ||
| static const char s_cfg_crc_json[] PROGMEM = "/cfg.json.crc"; | ||
| static const char s_cfg_crc_backup_json[] PROGMEM = "/cfg_backup.json.crc"; | ||
| static const char s_wsec_backup_json[] PROGMEM = "/wsec_backup.json"; | ||
| static const char s_wsec_crc_json[] PROGMEM = "/wsec.json.crc"; | ||
| static const char s_wsec_crc_backup_json[] PROGMEM = "/wsec_backup.json.crc"; | ||
| #endif | ||
|
|
||
| bool backupConfig() { | ||
| return backupFile(s_cfg_json); | ||
| } | ||
|
|
||
| bool restoreConfig() { | ||
| return restoreFile(s_cfg_json); | ||
| #ifdef WLED_ENABLE_CRC_FOR_CONFIG | ||
| return false; | ||
| #else | ||
| return restoreFile(s_cfg_json); | ||
| #endif | ||
| } | ||
|
|
||
| bool verifyConfig() { | ||
|
|
@@ -785,53 +801,141 @@ bool configBackupExists() { | |
| return checkBackupExists(s_cfg_json); | ||
| } | ||
|
|
||
|
|
||
| // rename config file and reboot | ||
| // if the cfg file doesn't exist, such as after a reset, do nothing | ||
| void resetConfig() { | ||
| #ifdef WLED_ENABLE_CRC_FOR_CONFIG | ||
| if (!WLED_FS.exists(s_cfg_json) && !WLED_FS.exists(s_cfg_backup_json)) | ||
| return; | ||
| #else | ||
| if (!WLED_FS.exists(s_cfg_json)) | ||
| return; | ||
| #endif | ||
| DEBUG_PRINTLN(F("Reset config")); | ||
| char backupname[32]; | ||
| // rename main config | ||
| if (WLED_FS.exists(s_cfg_json)) { | ||
| DEBUG_PRINTLN(F("Reset config")); | ||
| char backupname[32]; | ||
| snprintf_P(backupname, sizeof(backupname), PSTR("/rst.%s"), &s_cfg_json[1]); | ||
| WLED_FS.rename(s_cfg_json, backupname); | ||
| doReboot = true; | ||
| } | ||
| if (WLED_FS.exists(s_wsec_json)) | ||
| WLED_FS.remove(s_wsec_json); | ||
|
|
||
| #ifdef WLED_ENABLE_CRC_FOR_CONFIG | ||
| //rename backup config | ||
| if (WLED_FS.exists(s_cfg_backup_json)) { | ||
| snprintf_P(backupname, sizeof(backupname), PSTR("/rst.%s"), &s_cfg_backup_json[1]); | ||
| WLED_FS.rename(s_cfg_backup_json, backupname); | ||
| } | ||
| //remove CRC files | ||
| if (WLED_FS.exists(s_cfg_crc_json)) | ||
| WLED_FS.remove(s_cfg_crc_json); | ||
|
|
||
| if (WLED_FS.exists(s_cfg_crc_backup_json)) | ||
| WLED_FS.remove(s_cfg_crc_backup_json); | ||
|
|
||
| if (WLED_FS.exists(s_wsec_backup_json)) | ||
| WLED_FS.remove(s_wsec_backup_json); | ||
|
|
||
| if (WLED_FS.exists(s_wsec_crc_json)) | ||
| WLED_FS.remove(s_wsec_crc_json); | ||
|
|
||
| if (WLED_FS.exists(s_wsec_crc_backup_json)) | ||
| WLED_FS.remove(s_wsec_crc_backup_json); | ||
|
|
||
| #endif | ||
| doReboot = true; | ||
| } | ||
|
|
||
| bool deserializeConfigFromFS() { | ||
|
|
||
| [[maybe_unused]] bool success = deserializeConfigSec(); | ||
|
|
||
| if (!requestJSONBufferLock(JSON_LOCK_CFG_DES)) return false; | ||
|
|
||
| DEBUG_PRINTLN(F("Reading settings from /cfg.json...")); | ||
|
|
||
| success = readObjectFromFile(s_cfg_json, nullptr, pDoc); | ||
|
|
||
| // NOTE: This routine deserializes *and* applies the configuration | ||
| // Therefore, must also initialize ethernet from this function | ||
| const char* loadFile = nullptr; | ||
|
|
||
| #ifdef WLED_ENABLE_CRC_FOR_CONFIG | ||
| uint32_t storedCRCMain = 0; | ||
| uint32_t storedCRCBackup = 0; | ||
|
|
||
| bool haveMainCRC = loadCRC(s_cfg_crc_json, storedCRCMain); | ||
| bool haveBackupCRC = loadCRC(s_cfg_crc_backup_json, storedCRCBackup); | ||
|
|
||
| uint32_t crcMain = crc32_file(s_cfg_json); | ||
| uint32_t crcBackup = crc32_file(s_cfg_backup_json); | ||
|
|
||
| bool validMain = haveMainCRC && (crcMain == storedCRCMain); | ||
| bool validBackup = haveBackupCRC && (crcBackup == storedCRCBackup); | ||
|
|
||
| if (validMain && validBackup) { | ||
| loadFile = s_cfg_json; | ||
| } else if (validMain && !validBackup) { | ||
| DEBUG_PRINTLN(F("Backup config invalid, restoring...")); | ||
| copyFile(s_cfg_json, s_cfg_backup_json); | ||
| saveCRC(s_cfg_crc_backup_json, crcMain); | ||
| loadFile = s_cfg_json; | ||
| } else if (!validMain && validBackup) { | ||
| DEBUG_PRINTLN(F("Main config invalid, restoring from backup...")); | ||
| copyFile(s_cfg_backup_json, s_cfg_json); | ||
| saveCRC(s_cfg_crc_json, crcBackup); | ||
| loadFile = s_cfg_json; | ||
| } else { | ||
| DEBUG_PRINTLN(F("Both config files invalid! Using defaults.")); | ||
| loadFile = nullptr; // signal no file | ||
| } | ||
| #else | ||
| loadFile = s_cfg_json; | ||
| #endif | ||
| if (loadFile) | ||
| success = readObjectFromFile(loadFile, nullptr, pDoc); | ||
| else | ||
| pDoc->clear(); | ||
| JsonObject root = pDoc->as<JsonObject>(); | ||
| bool needsSave = deserializeConfig(root, true); | ||
| releaseJSONBufferLock(); | ||
|
|
||
| return needsSave; | ||
|
Comment on lines
+891
to
898
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Persist defaults when no valid config copy exists. When 💾 Minimal fix JsonObject root = pDoc->as<JsonObject>();
bool needsSave = deserializeConfig(root, true);
+ if (!loadFile) needsSave = true;
releaseJSONBufferLock();
return needsSave;🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| void serializeConfigToFS() { | ||
| serializeConfigSec(); | ||
| backupConfig(); // backup before writing new config | ||
|
|
||
| DEBUG_PRINTLN(F("Writing settings to /cfg.json...")); | ||
|
|
||
| if (!requestJSONBufferLock(JSON_LOCK_CFG_SER)) return; | ||
|
|
||
| JsonObject root = pDoc->to<JsonObject>(); | ||
|
|
||
| serializeConfig(root); | ||
|
|
||
| /* | ||
| * STEP 1 | ||
| * write main config | ||
| */ | ||
| File f = WLED_FS.open(FPSTR(s_cfg_json), "w"); | ||
| if (f) serializeJson(root, f); | ||
| f.close(); | ||
| releaseJSONBufferLock(); | ||
|
|
||
| if (f) { | ||
| serializeJson(root, f); | ||
| f.close(); | ||
| #ifdef WLED_ENABLE_CRC_FOR_CONFIG | ||
| uint32_t crcMain = crc32_file(s_cfg_json); | ||
| saveCRC(s_cfg_crc_json, crcMain); | ||
| #endif | ||
| } | ||
| #ifdef WLED_ENABLE_CRC_FOR_CONFIG | ||
| /* | ||
| * STEP 2 | ||
| * write backup config | ||
| */ | ||
| f = WLED_FS.open(FPSTR(s_cfg_backup_json), "w"); | ||
|
|
||
| if (f) { | ||
| serializeJson(root, f); | ||
| f.close(); | ||
| uint32_t crcBackup = crc32_file(s_cfg_backup_json); | ||
| saveCRC(s_cfg_crc_backup_json, crcBackup); | ||
| } | ||
| #endif | ||
| releaseJSONBufferLock(); | ||
| configNeedsWrite = false; | ||
| } | ||
|
|
||
|
|
@@ -1263,16 +1367,48 @@ void serializeConfig(JsonObject root) { | |
| UsermodManager::addToConfig(usermods_settings); | ||
| } | ||
|
|
||
|
|
||
| static const char s_wsec_json[] PROGMEM = "/wsec.json"; | ||
|
|
||
| //settings in /wsec.json, not accessible via webserver, for passwords and tokens | ||
| bool deserializeConfigSec() { | ||
| DEBUG_PRINTLN(F("Reading settings from /wsec.json...")); | ||
|
|
||
| if (!requestJSONBufferLock(JSON_LOCK_CFG_SEC_DES)) return false; | ||
| const char* loadFile = nullptr; | ||
|
|
||
| #ifdef WLED_ENABLE_CRC_FOR_CONFIG | ||
| uint32_t storedCRCMain = 0; | ||
| uint32_t storedCRCBackup = 0; | ||
|
|
||
| bool haveMainCRC = loadCRC(s_wsec_crc_json, storedCRCMain); | ||
| bool haveBackupCRC = loadCRC(s_wsec_crc_backup_json, storedCRCBackup); | ||
|
|
||
| uint32_t crcMain = crc32_file(s_wsec_json); | ||
| uint32_t crcBackup = crc32_file(s_wsec_backup_json); | ||
|
|
||
| bool validMain = haveMainCRC && (crcMain == storedCRCMain); | ||
| bool validBackup = haveBackupCRC && (crcBackup == storedCRCBackup); | ||
|
|
||
| bool success = readObjectFromFile(s_wsec_json, nullptr, pDoc); | ||
| if (validMain && validBackup) { | ||
| loadFile = s_wsec_json; | ||
| } | ||
| else if (validMain && !validBackup) { | ||
| copyFile(s_wsec_json, s_wsec_backup_json); | ||
| saveCRC(s_wsec_crc_backup_json, crcMain); | ||
| loadFile = s_wsec_json; | ||
| } | ||
| else if (!validMain && validBackup) { | ||
| copyFile(s_wsec_backup_json, s_wsec_json); | ||
| saveCRC(s_wsec_crc_json, crcBackup); | ||
| loadFile = s_wsec_json; | ||
| } | ||
| else { | ||
| DEBUG_PRINTLN(F("Both wsec files invalid")); | ||
| releaseJSONBufferLock(); | ||
| return false; | ||
| } | ||
| #else | ||
| loadFile = s_wsec_json; | ||
| #endif | ||
|
|
||
| bool success = readObjectFromFile(loadFile, nullptr, pDoc); | ||
| if (!success) { | ||
| releaseJSONBufferLock(); | ||
| return false; | ||
|
|
@@ -1360,7 +1496,24 @@ void serializeConfigSec() { | |
| #endif | ||
|
|
||
| File f = WLED_FS.open(FPSTR(s_wsec_json), "w"); | ||
| if (f) serializeJson(root, f); | ||
| f.close(); | ||
|
|
||
| if (f) { | ||
| serializeJson(root, f); | ||
| f.close(); | ||
|
|
||
| #ifdef WLED_ENABLE_CRC_FOR_CONFIG | ||
| uint32_t crcMain = crc32_file(s_wsec_json); | ||
| saveCRC(s_wsec_crc_json, crcMain); | ||
| #endif | ||
| } | ||
| #ifdef WLED_ENABLE_CRC_FOR_CONFIG | ||
| f = WLED_FS.open(FPSTR(s_wsec_backup_json), "w"); | ||
| if (f) { | ||
| serializeJson(root, f); | ||
| f.close(); | ||
| uint32_t crcBackup = crc32_file(s_wsec_backup_json); | ||
| saveCRC(s_wsec_crc_backup_json, crcBackup); | ||
| } | ||
| #endif | ||
| releaseJSONBufferLock(); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| #include "wled.h" | ||
|
|
||
| #ifdef WLED_ENABLE_CRC_FOR_CONFIG | ||
|
|
||
| static uint32_t crc32_update(uint32_t crc, uint8_t data) | ||
| { | ||
| crc ^= data; | ||
| for (uint8_t i = 0; i < 8; i++) { | ||
| if (crc & 1) crc = (crc >> 1) ^ 0xEDB88320; | ||
| else crc >>= 1; | ||
| } | ||
| return crc; | ||
| } | ||
|
|
||
| uint32_t crc32_file(const char* path) | ||
| { | ||
| File f = WLED_FS.open(path, "r"); | ||
| if (!f) return UINT32_MAX; | ||
|
|
||
| uint32_t crc = 0xFFFFFFFF; | ||
|
|
||
| while (f.available()) | ||
| crc = crc32_update(crc, f.read()); | ||
|
|
||
| f.close(); | ||
| return ~crc; | ||
| } | ||
|
|
||
| bool saveCRC(const char* path, uint32_t crc) | ||
| { | ||
| File f = WLED_FS.open(path, "w"); | ||
| if (!f) return false; | ||
|
|
||
| f.printf("%lu", (unsigned long)crc); | ||
| f.close(); | ||
| return true; | ||
| } | ||
|
|
||
| bool loadCRC(const char* path, uint32_t &crc) | ||
| { | ||
| if (!WLED_FS.exists(path)) | ||
| return false; | ||
|
|
||
| File f = WLED_FS.open(path, "r"); | ||
| if (!f) | ||
| return false; | ||
|
|
||
| crc = f.parseInt(); | ||
| f.close(); | ||
| return true; | ||
|
Comment on lines
+39
to
+50
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result:
If you need to parse full-range Citations:
Don't parse CRC32 values with
🤖 Prompt for AI Agents
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as the value is written with
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The three practical alternatives for this use case are: Option 1: This is the safest drop-in fix for the current decimal text format while avoiding bool loadCRC(const char* path, uint32_t &crc)
{
if (!WLED_FS.exists(path))
return false;
File f = WLED_FS.open(path, "r");
if (!f)
return false;
String s = f.readString();
f.close();
if (s.length() == 0)
return false;
char *endptr = nullptr;
unsigned long val = strtoul(s.c_str(), &endptr, 10);
if (endptr == s.c_str()) // no digits consumed
return false;
crc = (uint32_t)val;
return true;
}Option 2: Store/read as binary (4 bytes) — most compact and robust Avoids text parsing entirely. Write side becomes bool loadCRC(const char* path, uint32_t &crc)
{
if (!WLED_FS.exists(path))
return false;
File f = WLED_FS.open(path, "r");
if (!f)
return false;
bool ok = (f.read((uint8_t*)&crc, sizeof(crc)) == sizeof(crc));
f.close();
return ok;
}Option 3: Store as fixed-width hex, parse with
Summary: No 🧠 Learnings used |
||
| } | ||
|
|
||
| #endif | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,8 @@ | ||||||||||||||||||||
| #pragma once | ||||||||||||||||||||
|
|
||||||||||||||||||||
| #ifdef WLED_ENABLE_CRC_FOR_CONFIG | ||||||||||||||||||||
| static uint32_t crc32_update(uint32_t crc, uint8_t data); | ||||||||||||||||||||
| uint32_t crc32_file(const char* path); | ||||||||||||||||||||
| bool saveCRC(const char* path, uint32_t crc); | ||||||||||||||||||||
| bool loadCRC(const char* path, uint32_t &crc); | ||||||||||||||||||||
|
Comment on lines
+5
to
+7
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Return CRC read success separately from the checksum value.
🔧 API shape that avoids value-sentinel bugs-uint32_t crc32_file(const char* path);
+bool crc32_file(const char* path, uint32_t& crc);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I agree with the bunny - all values of uint32_t are possible CRC results. You could use a dedicated bool (return value) to flag errors in all CRC related functions. Another possibility (hack alert) is to cut out the highest bit of the CRC (i.e.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The bool-return approach is clearly the right fix here. It:
The two hack alternatives have real downsides:
The recommended signature (already in the suggestion above) aligns the whole API cleanly: bool crc32_file(const char* path, uint32_t& outCrc);
bool saveCRC(const char* path, uint32_t crc);
bool loadCRC(const char* path, uint32_t& crc);Callers in 🧠 Learnings used |
||||||||||||||||||||
| #endif | ||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keep booting from the good mirror if the repair write fails.
On the
!validMain && validBackuppath, you switchloadFileback to the primary and overwrite its CRC sidecar before checking whethercopyFile()actually repaired it. If that write fails, boot falls straight back to the corrupted main file instead of using the known-good backup. The same problem exists in thewsecbranch at Lines 1397-1400.🩹 Safer restore pattern
} else if (!validMain && validBackup) { DEBUG_PRINTLN(F("Main config invalid, restoring from backup...")); - copyFile(s_cfg_backup_json, s_cfg_json); - saveCRC(s_cfg_crc_json, crcBackup); - loadFile = s_cfg_json; + if (copyFile(s_cfg_backup_json, s_cfg_json) && saveCRC(s_cfg_crc_json, crcBackup)) { + loadFile = s_cfg_json; + } else { + loadFile = s_cfg_backup_json; + } }Also applies to: 1397-1400
🤖 Prompt for AI Agents