diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 4f131cab96..c400741175 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -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(); bool needsSave = deserializeConfig(root, true); releaseJSONBufferLock(); - return needsSave; } 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(); - 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(); } diff --git a/wled00/cfg_crc.cpp b/wled00/cfg_crc.cpp new file mode 100644 index 0000000000..4dd0cf2671 --- /dev/null +++ b/wled00/cfg_crc.cpp @@ -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; +} + +#endif \ No newline at end of file diff --git a/wled00/cfg_crc.h b/wled00/cfg_crc.h new file mode 100644 index 0000000000..cc020e1108 --- /dev/null +++ b/wled00/cfg_crc.h @@ -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); +#endif \ No newline at end of file diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index 79e71af54e..d543861015 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -20,6 +20,10 @@ #include "html_cpal.h" #include "html_edit.h" +#ifdef WLED_ENABLE_CRC_FOR_CONFIG + #include "cfg_crc.h" +#endif + // forward declarations static void createEditHandler(); @@ -43,6 +47,11 @@ static const char s_expires[] PROGMEM = "Expires"; static const char _common_js[] PROGMEM = "/common.js"; static const char _iro_js[] PROGMEM = "/iro.js"; static const char _omggif_js[] PROGMEM = "/omggif.js"; +#ifdef WLED_ENABLE_CRC_FOR_CONFIG +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 //Is this an IP? static bool isIp(const String &str) { @@ -213,6 +222,24 @@ static void handleUpload(AsyncWebServerRequest *request, const String& filename, } if (isFinal) { request->_tempFile.close(); + + #ifdef WLED_ENABLE_CRC_FOR_CONFIG + if (filename.indexOf("cfg.json") >= 0) { + uint32_t crcMain = crc32_file("/cfg.json"); + saveCRC("/cfg.json.crc", crcMain); + copyFile("/cfg.json", "/cfg_backup.json"); + uint32_t crcBackup = crc32_file("/cfg_backup.json"); + saveCRC("/cfg_backup.json.crc", crcBackup); + } + if (filename.indexOf("wsec.json") >= 0) { + uint32_t crcMain = crc32_file("/wsec.json"); + saveCRC("/wsec.json.crc", crcMain); + copyFile("/wsec.json", "/wsec_backup.json"); + uint32_t crcBackup = crc32_file("/wsec_backup.json"); + saveCRC("/wsec_backup.json.crc", crcBackup); + } + #endif + if (filename.indexOf(F("cfg.json")) >= 0) { // check for filename with or without slash doReboot = true; request->send(200, FPSTR(CONTENT_TYPE_PLAIN), F("Config restore ok.\nRebooting...")); @@ -262,6 +289,12 @@ static void createEditHandler() { rootfile = rootdir.openNextFile(); // skip wsec.json continue; } + #ifdef WLED_ENABLE_CRC_FOR_CONFIG + if (name.indexOf(FPSTR(s_wsec_backup_json)) >= 0 || name.indexOf(FPSTR(s_wsec_crc_json)) >= 0 || name.indexOf(FPSTR(s_wsec_crc_backup_json)) >= 0) { + rootfile = rootdir.openNextFile(); // skip wsec_*.json + continue; + } + #endif if (!first) response->write(','); first = false; response->printf_P(PSTR("{\"name\":\"%s\",\"type\":\"file\",\"size\":%u}"), name.c_str(), rootfile.size()); @@ -294,6 +327,12 @@ static void createEditHandler() { request->send(403, FPSTR(CONTENT_TYPE_PLAIN), FPSTR(s_accessdenied)); // skip wsec.json return; } + #ifdef WLED_ENABLE_CRC_FOR_CONFIG + if (path.indexOf(FPSTR(s_wsec_backup_json)) >= 0 || path.indexOf(FPSTR(s_wsec_crc_json)) >= 0 || path.indexOf(FPSTR(s_wsec_crc_backup_json)) >= 0) { + request->send(403, FPSTR(CONTENT_TYPE_PLAIN), FPSTR(s_accessdenied)); // skip wsec_*.json + return; + } + #endif if (func == "edit") { request->send(WLED_FS, path);