diff --git a/wled00/data/update.htm b/wled00/data/update.htm index 5c392802..ecadfcfd 100644 --- a/wled00/data/update.htm +++ b/wled00/data/update.htm @@ -45,12 +45,26 @@ }); } + - +

WLED Software Update

Installed version: Loading...
diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index f6b259fe..e5aa26b1 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -24,6 +24,15 @@ #include #endif +#if defined(ARDUINO_ARCH_ESP32) && !defined(WLED_DISABLE_OTA) + #include + #include + #include + #include + #include + #include +#endif + // define flash strings once (saves flash memory) static const char s_redirecting[] PROGMEM = "Redirecting..."; static const char s_content_enc[] PROGMEM = "Content-Encoding"; @@ -865,6 +874,140 @@ void initServer() }); #endif +#if defined(ARDUINO_ARCH_ESP32) && !defined(WLED_DISABLE_OTA) + // ESP32 bootloader update endpoint + server.on(F("/updatebootloader"), HTTP_POST, [](AsyncWebServerRequest *request){ + if (!correctPIN) { + serveSettings(request, true); // handle PIN page POST request + return; + } + if (otaLock) { + serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_ota), 254); + return; + } + if (Update.hasError()) { + serveMessage(request, 500, F("Bootloader update failed!"), F("Please check your file and retry!"), 254); + } else { + serveMessage(request, 200, F("Bootloader updated successfully!"), FPSTR(s_rebooting), 131); + doReboot = true; + } + },[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool isFinal){ + IPAddress client = request->client()->remoteIP(); + if (((otaSameSubnet && !inSameSubnet(client)) && !strlen(settingsPIN)) || (!otaSameSubnet && !inLocalSubnet(client))) { + DEBUG_PRINTLN(F("Attempted bootloader update from different/non-local subnet!")); + request->send(401, FPSTR(CONTENT_TYPE_PLAIN), FPSTR(s_accessdenied)); + return; + } + if (!correctPIN || otaLock) return; + + static uint8_t* bootloaderBuffer = nullptr; + static size_t bootloaderBytesBuffered = 0; + const uint32_t bootloaderOffset = 0x1000; + const uint32_t maxBootloaderSize = 0x8000; // 32KB max + + if (!index) { + DEBUG_PRINTLN(F("Bootloader Update Start - buffering data")); + #if WLED_WATCHDOG_TIMEOUT > 0 + WLED::instance().disableWatchdog(); + #endif + lastEditTime = millis(); // make sure PIN does not lock during update + strip.suspend(); + strip.resetSegments(); + + // Allocate buffer for entire bootloader + if (bootloaderBuffer) { + free(bootloaderBuffer); + bootloaderBuffer = nullptr; + } + bootloaderBuffer = (uint8_t*)malloc(maxBootloaderSize); + if (!bootloaderBuffer) { + DEBUG_PRINTLN(F("Failed to allocate bootloader buffer!")); + strip.resume(); + #if WLED_WATCHDOG_TIMEOUT > 0 + WLED::instance().enableWatchdog(); + #endif + Update.abort(); + return; + } + bootloaderBytesBuffered = 0; + + // Verify bootloader magic on first chunk + if (!isValidBootloader(data, len)) { + DEBUG_PRINTLN(F("Invalid bootloader file!")); + free(bootloaderBuffer); + bootloaderBuffer = nullptr; + strip.resume(); + #if WLED_WATCHDOG_TIMEOUT > 0 + WLED::instance().enableWatchdog(); + #endif + Update.abort(); + return; + } + } + + // Buffer the incoming data + if (bootloaderBuffer && bootloaderBytesBuffered + len <= maxBootloaderSize) { + memcpy(bootloaderBuffer + bootloaderBytesBuffered, data, len); + bootloaderBytesBuffered += len; + } else if (!bootloaderBuffer) { + DEBUG_PRINTLN(F("Bootloader buffer not allocated!")); + Update.abort(); + } else { + DEBUG_PRINTLN(F("Bootloader size exceeds maximum!")); + if (bootloaderBuffer) { + free(bootloaderBuffer); + bootloaderBuffer = nullptr; + } + Update.abort(); + } + + // Only write to flash when upload is complete + if (isFinal) { + bool success = false; + if (!Update.hasError() && bootloaderBuffer && bootloaderBytesBuffered > 0) { + DEBUG_PRINTF_P(PSTR("Bootloader buffered (%d bytes) - validating\n"), bootloaderBytesBuffered); + + // Verify the complete bootloader image before flashing + if (!verifyBootloaderImage(bootloaderBuffer, bootloaderBytesBuffered)) { + DEBUG_PRINTLN(F("Bootloader validation failed!")); + } else { + // Erase bootloader region + DEBUG_PRINTLN(F("Erasing bootloader region...")); + esp_err_t err = esp_flash_erase_region(NULL, bootloaderOffset, maxBootloaderSize); + if (err != ESP_OK) { + DEBUG_PRINTF_P(PSTR("Bootloader erase error: %d\n"), err); + } else { + // Write buffered data to flash + err = esp_flash_write(NULL, bootloaderBuffer, bootloaderOffset, bootloaderBytesBuffered); + if (err != ESP_OK) { + DEBUG_PRINTF_P(PSTR("Bootloader flash write error: %d\n"), err); + } else { + DEBUG_PRINTF_P(PSTR("Bootloader Update Success - %d bytes written\n"), bootloaderBytesBuffered); + bootloaderSHA256Cached = false; // Invalidate cached bootloader hash + success = true; + } + } + } + } + + // Cleanup + if (bootloaderBuffer) { + free(bootloaderBuffer); + bootloaderBuffer = nullptr; + } + bootloaderBytesBuffered = 0; + + if (!success) { + DEBUG_PRINTLN(F("Bootloader Update Failed")); + strip.resume(); + #if WLED_WATCHDOG_TIMEOUT > 0 + WLED::instance().enableWatchdog(); + #endif + } + } + }); +#endif + #ifdef WLED_ENABLE_DMX server.on(F("/dmxmap"), HTTP_GET, [](AsyncWebServerRequest *request){ request->send_P(200, FPSTR(CONTENT_TYPE_HTML), PAGE_dmxmap, dmxProcessor);