diff --git a/wled00/data/update.htm b/wled00/data/update.htm index d8b8876ef2..e93a113fae 100644 --- a/wled00/data/update.htm +++ b/wled00/data/update.htm @@ -29,6 +29,13 @@ if (data.arch == "esp8266") { toggle('rev'); } + const isESP32 = data.arch && (data.arch.toLowerCase() === 'esp32' || data.arch.toLowerCase() === 'esp32-s2'); + if (isESP32) { + gId('bootloader-section').style.display = 'block'; + if (data.bootloaderSHA256) { + gId('bootloader-hash').innerText = 'Current bootloader SHA256: ' + data.bootloaderSHA256; + } + } }) .catch(error => { console.log('Could not fetch device info:', error); @@ -42,8 +49,7 @@ @import url("style.css"); - - +

WLED Software Update

Installed version: Loading...
@@ -60,6 +66,16 @@

WLED Software Update


+
Updating...
Please do not close or refresh the page :)
\ No newline at end of file diff --git a/wled00/json.cpp b/wled00/json.cpp index 8204319425..a5ef74757d 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -820,6 +820,9 @@ void serializeInfo(JsonObject root) root[F("resetReason1")] = (int)rtc_get_reset_reason(1); #endif root[F("lwip")] = 0; //deprecated + #ifndef WLED_DISABLE_OTA + root[F("bootloaderSHA256")] = getBootloaderSHA256Hex(); + #endif #else root[F("arch")] = "esp8266"; root[F("core")] = ESP.getCoreVersion(); diff --git a/wled00/ota_update.cpp b/wled00/ota_update.cpp index afda59a942..6a5cf29cd3 100644 --- a/wled00/ota_update.cpp +++ b/wled00/ota_update.cpp @@ -4,12 +4,15 @@ #ifdef ESP32 #include #include +#include +#include #endif // Platform-specific metadata locations #ifdef ESP32 constexpr size_t METADATA_OFFSET = 256; // ESP32: metadata appears after Espressif metadata #define UPDATE_ERROR errorString +const size_t BOOTLOADER_OFFSET = 0x1000; #elif defined(ESP8266) constexpr size_t METADATA_OFFSET = 0x1000; // ESP8266: metadata appears at 4KB offset #define UPDATE_ERROR getErrorString @@ -254,4 +257,472 @@ void handleOTAData(AsyncWebServerRequest *request, size_t index, uint8_t *data, // Upload complete context->uploadComplete = true; } -} \ No newline at end of file +} + +#if defined(ARDUINO_ARCH_ESP32) && !defined(WLED_DISABLE_OTA) +// Cache for bootloader SHA256 digest as hex string +static String bootloaderSHA256HexCache = ""; + +// Calculate and cache the bootloader SHA256 digest as hex string +void calculateBootloaderSHA256() { + if (!bootloaderSHA256HexCache.isEmpty()) return; + + // Bootloader is at fixed offset 0x1000 (4KB) and is typically 32KB + const uint32_t bootloaderSize = 0x8000; // 32KB, typical bootloader size + + // Calculate SHA256 + uint8_t sha256[32]; + mbedtls_sha256_context ctx; + mbedtls_sha256_init(&ctx); + mbedtls_sha256_starts(&ctx, 0); // 0 = SHA256 (not SHA224) + + const size_t chunkSize = 256; + uint8_t buffer[chunkSize]; + + for (uint32_t offset = 0; offset < bootloaderSize; offset += chunkSize) { + size_t readSize = min((size_t)(bootloaderSize - offset), chunkSize); + if (esp_flash_read(NULL, buffer, BOOTLOADER_OFFSET + offset, readSize) == ESP_OK) { + mbedtls_sha256_update(&ctx, buffer, readSize); + } + } + + mbedtls_sha256_finish(&ctx, sha256); + mbedtls_sha256_free(&ctx); + + // Convert to hex string and cache it + char hex[65]; + for (int i = 0; i < 32; i++) { + sprintf(hex + (i * 2), "%02x", sha256[i]); + } + hex[64] = '\0'; + bootloaderSHA256HexCache = String(hex); +} + +// Get bootloader SHA256 as hex string +String getBootloaderSHA256Hex() { + calculateBootloaderSHA256(); + return bootloaderSHA256HexCache; +} + +// Invalidate cached bootloader SHA256 (call after bootloader update) +void invalidateBootloaderSHA256Cache() { + bootloaderSHA256HexCache = ""; +} + +// Verify complete buffered bootloader using ESP-IDF validation approach +// This matches the key validation steps from esp_image_verify() in ESP-IDF +// Returns the actual bootloader data pointer and length via the buffer and len parameters +bool verifyBootloaderImage(const uint8_t* &buffer, size_t &len, String* bootloaderErrorMsg) { + size_t availableLen = len; + if (!bootloaderErrorMsg) { + DEBUG_PRINTLN(F("bootloaderErrorMsg is null")); + return false; + } + // ESP32 image header structure (based on esp_image_format.h) + // Offset 0: magic (0xE9) + // Offset 1: segment_count + // Offset 2: spi_mode + // Offset 3: spi_speed (4 bits) + spi_size (4 bits) + // Offset 4-7: entry_addr (uint32_t) + // Offset 8: wp_pin + // Offset 9-11: spi_pin_drv[3] + // Offset 12-13: chip_id (uint16_t, little-endian) + // Offset 14: min_chip_rev + // Offset 15-22: reserved[8] + // Offset 23: hash_appended + + const size_t MIN_IMAGE_HEADER_SIZE = 24; + + // 1. Validate minimum size for header + if (len < MIN_IMAGE_HEADER_SIZE) { + *bootloaderErrorMsg = "Bootloader too small - invalid header"; + return false; + } + + // Check if the bootloader starts at offset 0x1000 (common in partition table dumps) + // This happens when someone uploads a complete flash dump instead of just the bootloader + if (len > BOOTLOADER_OFFSET + MIN_IMAGE_HEADER_SIZE && + buffer[BOOTLOADER_OFFSET] == 0xE9 && + buffer[0] != 0xE9) { + DEBUG_PRINTF_P(PSTR("Bootloader magic byte detected at offset 0x%04X - adjusting buffer\n"), BOOTLOADER_OFFSET); + // Adjust buffer pointer to start at the actual bootloader + buffer = buffer + BOOTLOADER_OFFSET; + len = len - BOOTLOADER_OFFSET; + + // Re-validate size after adjustment + if (len < MIN_IMAGE_HEADER_SIZE) { + *bootloaderErrorMsg = "Bootloader at offset 0x1000 too small - invalid header"; + return false; + } + } + + // 2. Magic byte check (matches esp_image_verify step 1) + if (buffer[0] != 0xE9) { + *bootloaderErrorMsg = "Invalid bootloader magic byte (expected 0xE9, got 0x" + String(buffer[0], HEX) + ")"; + return false; + } + + // 3. Segment count validation (matches esp_image_verify step 2) + uint8_t segmentCount = buffer[1]; + if (segmentCount == 0 || segmentCount > 16) { + *bootloaderErrorMsg = "Invalid segment count: " + String(segmentCount); + return false; + } + + // 4. SPI mode validation (basic sanity check) + uint8_t spiMode = buffer[2]; + if (spiMode > 3) { // Valid modes are 0-3 (QIO, QOUT, DIO, DOUT) + *bootloaderErrorMsg = "Invalid SPI mode: " + String(spiMode); + return false; + } + + // 5. Chip ID validation (matches esp_image_verify step 3) + uint16_t chipId = buffer[12] | (buffer[13] << 8); // Little-endian + + // Known ESP32 chip IDs from ESP-IDF: + // 0x0000 = ESP32 + // 0x0002 = ESP32-S2 + // 0x0005 = ESP32-C3 + // 0x0009 = ESP32-S3 + // 0x000C = ESP32-C2 + // 0x000D = ESP32-C6 + // 0x0010 = ESP32-H2 + + #if defined(CONFIG_IDF_TARGET_ESP32) + if (chipId != 0x0000) { + *bootloaderErrorMsg = "Chip ID mismatch - expected ESP32 (0x0000), got 0x" + String(chipId, HEX); + return false; + } + #elif defined(CONFIG_IDF_TARGET_ESP32S2) + if (chipId != 0x0002) { + *bootloaderErrorMsg = "Chip ID mismatch - expected ESP32-S2 (0x0002), got 0x" + String(chipId, HEX); + return false; + } + #elif defined(CONFIG_IDF_TARGET_ESP32C3) + if (chipId != 0x0005) { + *bootloaderErrorMsg = "Chip ID mismatch - expected ESP32-C3 (0x0005), got 0x" + String(chipId, HEX); + return false; + } + *bootloaderErrorMsg = "ESP32-C3 update not supported yet"; + return false; + #elif defined(CONFIG_IDF_TARGET_ESP32S3) + if (chipId != 0x0009) { + *bootloaderErrorMsg = "Chip ID mismatch - expected ESP32-S3 (0x0009), got 0x" + String(chipId, HEX); + return false; + } + *bootloaderErrorMsg = "ESP32-S3 update not supported yet"; + return false; + #elif defined(CONFIG_IDF_TARGET_ESP32C6) + if (chipId != 0x000D) { + *bootloaderErrorMsg = "Chip ID mismatch - expected ESP32-C6 (0x000D), got 0x" + String(chipId, HEX); + return false; + } + *bootloaderErrorMsg = "ESP32-C6 update not supported yet"; + return false; + #else + // Generic validation - chip ID should be valid + if (chipId > 0x00FF) { + *bootloaderErrorMsg = "Invalid chip ID: 0x" + String(chipId, HEX); + return false; + } + *bootloaderErrorMsg = "Unknown ESP32 target - bootloader update not supported"; + return false; + #endif + + // 6. Entry point validation (should be in valid memory range) + uint32_t entryAddr = buffer[4] | (buffer[5] << 8) | (buffer[6] << 16) | (buffer[7] << 24); + // ESP32 bootloader entry points are typically in IRAM range (0x40000000 - 0x40400000) + // or ROM range (0x40000000 and above) + if (entryAddr < 0x40000000 || entryAddr > 0x50000000) { + *bootloaderErrorMsg = "Invalid entry address: 0x" + String(entryAddr, HEX); + return false; + } + + // 7. Basic segment structure validation + // Each segment has a header: load_addr (4 bytes) + data_len (4 bytes) + size_t offset = MIN_IMAGE_HEADER_SIZE; + size_t actualBootloaderSize = MIN_IMAGE_HEADER_SIZE; + + for (uint8_t i = 0; i < segmentCount && offset + 8 <= len; i++) { + uint32_t segmentSize = buffer[offset + 4] | (buffer[offset + 5] << 8) | + (buffer[offset + 6] << 16) | (buffer[offset + 7] << 24); + + // Segment size sanity check + // ESP32 classic bootloader segments can be larger, C3 are smaller + if (segmentSize > 0x20000) { // 128KB max per segment (very generous) + *bootloaderErrorMsg = "Segment " + String(i) + " too large: " + String(segmentSize) + " bytes"; + return false; + } + + offset += 8 + segmentSize; // Skip segment header and data + } + + actualBootloaderSize = offset; + + // 8. Check for appended SHA256 hash (byte 23 in header) + // If hash_appended != 0, there's a 32-byte SHA256 hash after the segments + uint8_t hashAppended = buffer[23]; + if (hashAppended != 0) { + actualBootloaderSize += 32; + if (actualBootloaderSize > availableLen) { + *bootloaderErrorMsg = "Bootloader missing SHA256 trailer"; + return false; + } + DEBUG_PRINTF_P(PSTR("Bootloader has appended SHA256 hash\n")); + } + + // 9. The image may also have a 1-byte checksum after segments/hash + // Check if there's at least one more byte available + if (actualBootloaderSize + 1 <= availableLen) { + // There's likely a checksum byte + actualBootloaderSize += 1; + } else if (actualBootloaderSize > availableLen) { + *bootloaderErrorMsg = "Bootloader truncated before checksum"; + return false; + } + + // 10. Align to 16 bytes (ESP32 requirement for flash writes) + // The bootloader image must be 16-byte aligned + if (actualBootloaderSize % 16 != 0) { + size_t alignedSize = ((actualBootloaderSize + 15) / 16) * 16; + // Make sure we don't exceed available data + if (alignedSize <= len) { + actualBootloaderSize = alignedSize; + } + } + + DEBUG_PRINTF_P(PSTR("Bootloader validation: %d segments, actual size %d bytes (buffer size %d bytes, hash_appended=%d)\n"), + segmentCount, actualBootloaderSize, len, hashAppended); + + // 11. Verify we have enough data for all segments + hash + checksum + if (actualBootloaderSize > availableLen) { + *bootloaderErrorMsg = "Bootloader truncated - expected at least " + String(actualBootloaderSize) + " bytes, have " + String(availableLen) + " bytes"; + return false; + } + + if (offset > availableLen) { + *bootloaderErrorMsg = "Bootloader truncated - expected at least " + String(offset) + " bytes, have " + String(len) + " bytes"; + return false; + } + + // Update len to reflect actual bootloader size (including hash and checksum, with alignment) + // This is critical - we must write the complete image including checksums + len = actualBootloaderSize; + + return true; +} + +// Bootloader OTA context structure +struct BootloaderUpdateContext { + // State flags + bool replySent = false; + bool uploadComplete = false; + String errorMessage; + + // Buffer to hold bootloader data + uint8_t* buffer = nullptr; + size_t bytesBuffered = 0; + const uint32_t bootloaderOffset = 0x1000; + const uint32_t maxBootloaderSize = 0x10000; // 64KB buffer size +}; + +// Cleanup bootloader OTA context +static void endBootloaderOTA(AsyncWebServerRequest *request) { + BootloaderUpdateContext* context = reinterpret_cast(request->_tempObject); + request->_tempObject = nullptr; + + DEBUG_PRINTF_P(PSTR("EndBootloaderOTA %x --> %x\n"), (uintptr_t)request, (uintptr_t)context); + if (context) { + if (context->buffer) { + free(context->buffer); + context->buffer = nullptr; + } + + // If update failed, restore system state + if (!context->uploadComplete || !context->errorMessage.isEmpty()) { + strip.resume(); + #if WLED_WATCHDOG_TIMEOUT > 0 + WLED::instance().enableWatchdog(); + #endif + } + + delete context; + } +} + +// Initialize bootloader OTA context +bool initBootloaderOTA(AsyncWebServerRequest *request) { + if (request->_tempObject) { + return true; // Already initialized + } + + BootloaderUpdateContext* context = new BootloaderUpdateContext(); + if (!context) { + DEBUG_PRINTLN(F("Failed to allocate bootloader OTA context")); + return false; + } + + request->_tempObject = context; + request->onDisconnect([=]() { endBootloaderOTA(request); }); // ensures cleanup on disconnect + + DEBUG_PRINTLN(F("Bootloader Update Start - initializing buffer")); + #if WLED_WATCHDOG_TIMEOUT > 0 + WLED::instance().disableWatchdog(); + #endif + lastEditTime = millis(); // make sure PIN does not lock during update + strip.suspend(); + strip.resetSegments(); + + // Check available heap before attempting allocation + size_t freeHeap = getFreeHeapSize(); + DEBUG_PRINTF_P(PSTR("Free heap before bootloader buffer allocation: %d bytes (need %d bytes)\n"), freeHeap, context->maxBootloaderSize); + + context->buffer = (uint8_t*)malloc(context->maxBootloaderSize); + if (!context->buffer) { + size_t freeHeapNow = getFreeHeapSize(); + DEBUG_PRINTF_P(PSTR("Failed to allocate %d byte bootloader buffer! Free heap: %d bytes\n"), context->maxBootloaderSize, freeHeapNow); + context->errorMessage = "Out of memory! Free heap: " + String(freeHeapNow) + " bytes, need: " + String(context->maxBootloaderSize) + " bytes"; + strip.resume(); + #if WLED_WATCHDOG_TIMEOUT > 0 + WLED::instance().enableWatchdog(); + #endif + return false; + } + + context->bytesBuffered = 0; + return true; +} + +// Set bootloader OTA replied flag +void setBootloaderOTAReplied(AsyncWebServerRequest *request) { + BootloaderUpdateContext* context = reinterpret_cast(request->_tempObject); + if (context) { + context->replySent = true; + } +} + +// Get bootloader OTA result +std::pair getBootloaderOTAResult(AsyncWebServerRequest *request) { + BootloaderUpdateContext* context = reinterpret_cast(request->_tempObject); + + if (!context) { + return std::make_pair(true, String(F("Internal error: No bootloader OTA context"))); + } + + bool needsReply = !context->replySent; + String errorMsg = context->errorMessage; + + // If upload was successful, return empty string and trigger reboot + if (context->uploadComplete && errorMsg.isEmpty()) { + doReboot = true; + endBootloaderOTA(request); + return std::make_pair(needsReply, String()); + } + + // If there was an error, return it + if (!errorMsg.isEmpty()) { + endBootloaderOTA(request); + return std::make_pair(needsReply, errorMsg); + } + + // Should never happen + return std::make_pair(true, String(F("Internal software failure"))); +} + +// Handle bootloader OTA data +void handleBootloaderOTAData(AsyncWebServerRequest *request, size_t index, uint8_t *data, size_t len, bool isFinal) { + BootloaderUpdateContext* context = reinterpret_cast(request->_tempObject); + + if (!context) { + DEBUG_PRINTLN(F("No bootloader OTA context - ignoring data")); + return; + } + + if (!context->errorMessage.isEmpty()) { + return; + } + + // Buffer the incoming data + if (context->buffer && context->bytesBuffered + len <= context->maxBootloaderSize) { + memcpy(context->buffer + context->bytesBuffered, data, len); + context->bytesBuffered += len; + DEBUG_PRINTF_P(PSTR("Bootloader buffer progress: %d / %d bytes\n"), context->bytesBuffered, context->maxBootloaderSize); + } else if (!context->buffer) { + DEBUG_PRINTLN(F("Bootloader buffer not allocated!")); + context->errorMessage = "Internal error: Bootloader buffer not allocated"; + return; + } else { + size_t totalSize = context->bytesBuffered + len; + DEBUG_PRINTLN(F("Bootloader size exceeds maximum!")); + context->errorMessage = "Bootloader file too large: " + String(totalSize) + " bytes (max: " + String(context->maxBootloaderSize) + " bytes)"; + return; + } + + // Only write to flash when upload is complete + if (isFinal) { + DEBUG_PRINTLN(F("Bootloader Upload Complete - validating and flashing")); + + if (context->buffer && context->bytesBuffered > 0) { + // Prepare pointers for verification (may be adjusted if bootloader at offset) + const uint8_t* bootloaderData = context->buffer; + size_t bootloaderSize = context->bytesBuffered; + + // Verify the complete bootloader image before flashing + // Note: verifyBootloaderImage may adjust bootloaderData pointer and bootloaderSize + // for validation purposes only + if (!verifyBootloaderImage(bootloaderData, bootloaderSize, &context->errorMessage)) { + DEBUG_PRINTLN(F("Bootloader validation failed!")); + // Error message already set by verifyBootloaderImage + } else { + // Calculate offset to write to flash + // If bootloaderData was adjusted (partition table detected), we need to skip it in flash too + size_t flashOffset = context->bootloaderOffset; + const uint8_t* dataToWrite = context->buffer; + size_t bytesToWrite = context->bytesBuffered; + + // If validation adjusted the pointer, it means we have a partition table at the start + // In this case, we should skip writing the partition table and write bootloader at 0x1000 + if (bootloaderData != context->buffer) { + // bootloaderData was adjusted - skip partition table in our data + size_t partitionTableSize = bootloaderData - context->buffer; + dataToWrite = bootloaderData; + bytesToWrite = bootloaderSize; + DEBUG_PRINTF_P(PSTR("Skipping %d bytes of partition table data\n"), partitionTableSize); + } + + DEBUG_PRINTF_P(PSTR("Bootloader validation passed - writing %d bytes to flash at 0x%04X\n"), + bytesToWrite, flashOffset); + + // Calculate erase size (must be multiple of 4KB) + size_t eraseSize = ((bytesToWrite + 0xFFF) / 0x1000) * 0x1000; + if (eraseSize > context->maxBootloaderSize) { + eraseSize = context->maxBootloaderSize; + } + + // Erase bootloader region + DEBUG_PRINTF_P(PSTR("Erasing %d bytes at 0x%04X...\n"), eraseSize, flashOffset); + esp_err_t err = esp_flash_erase_region(NULL, flashOffset, eraseSize); + if (err != ESP_OK) { + DEBUG_PRINTF_P(PSTR("Bootloader erase error: %d\n"), err); + context->errorMessage = "Flash erase failed (error code: " + String(err) + ")"; + } else { + // Write the validated bootloader data to flash + err = esp_flash_write(NULL, dataToWrite, flashOffset, bytesToWrite); + if (err != ESP_OK) { + DEBUG_PRINTF_P(PSTR("Bootloader flash write error: %d\n"), err); + context->errorMessage = "Flash write failed (error code: " + String(err) + ")"; + } else { + DEBUG_PRINTF_P(PSTR("Bootloader Update Success - %d bytes written to 0x%04X\n"), + bytesToWrite, flashOffset); + // Invalidate cached bootloader hash + invalidateBootloaderSHA256Cache(); + context->uploadComplete = true; + } + } + } + } else if (context->bytesBuffered == 0) { + context->errorMessage = "No bootloader data received"; + } + } +} +#endif diff --git a/wled00/ota_update.h b/wled00/ota_update.h index c8fd702643..82d97d6ce4 100644 --- a/wled00/ota_update.h +++ b/wled00/ota_update.h @@ -50,3 +50,65 @@ std::pair getOTAResult(AsyncWebServerRequest *request); * @return bool indicating if a reply is necessary; string with error message if the update failed. */ void handleOTAData(AsyncWebServerRequest *request, size_t index, uint8_t *data, size_t len, bool isFinal); + +#if defined(ARDUINO_ARCH_ESP32) && !defined(WLED_DISABLE_OTA) +/** + * Calculate and cache the bootloader SHA256 digest + * Reads the bootloader from flash at offset 0x1000 and computes SHA256 hash + */ +void calculateBootloaderSHA256(); + +/** + * Get bootloader SHA256 as hex string + * @return String containing 64-character hex representation of SHA256 hash + */ +String getBootloaderSHA256Hex(); + +/** + * Invalidate cached bootloader SHA256 (call after bootloader update) + * Forces recalculation on next call to calculateBootloaderSHA256 or getBootloaderSHA256Hex + */ +void invalidateBootloaderSHA256Cache(); + +/** + * Verify complete buffered bootloader using ESP-IDF validation approach + * This matches the key validation steps from esp_image_verify() in ESP-IDF + * @param buffer Reference to pointer to bootloader binary data (will be adjusted if offset detected) + * @param len Reference to length of bootloader data (will be adjusted to actual size) + * @param bootloaderErrorMsg Pointer to String to store error message (must not be null) + * @return true if validation passed, false otherwise + */ +bool verifyBootloaderImage(const uint8_t* &buffer, size_t &len, String* bootloaderErrorMsg); + +/** + * Create a bootloader OTA context object on an AsyncWebServerRequest + * @param request Pointer to web request object + * @return true if allocation was successful, false if not + */ +bool initBootloaderOTA(AsyncWebServerRequest *request); + +/** + * Indicate to the bootloader OTA subsystem that a reply has already been generated + * @param request Pointer to web request object + */ +void setBootloaderOTAReplied(AsyncWebServerRequest *request); + +/** + * Retrieve the bootloader OTA result. + * @param request Pointer to web request object + * @return bool indicating if a reply is necessary; string with error message if the update failed. + */ +std::pair getBootloaderOTAResult(AsyncWebServerRequest *request); + +/** + * Process a block of bootloader OTA data. This is a passthrough of an ArUploadHandlerFunction. + * Requires that initBootloaderOTA be called on the handler object before any work will be done. + * @param request Pointer to web request object + * @param index Offset in to uploaded file + * @param data New data bytes + * @param len Length of new data bytes + * @param isFinal Indicates that this is the last block + */ +void handleBootloaderOTAData(AsyncWebServerRequest *request, size_t index, uint8_t *data, size_t len, bool isFinal); +#endif + diff --git a/wled00/wled.h b/wled00/wled.h index 7f3188bef9..d1cddd8fba 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -189,6 +189,9 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument; #include "FastLED.h" #include "const.h" #include "fcn_declare.h" +#ifndef WLED_DISABLE_OTA + #include "ota_update.h" +#endif #include "NodeStruct.h" #include "pin_manager.h" #include "colors.h" diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index 1039747746..4a833e1636 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -15,6 +15,7 @@ #include "html_cpal.h" #include "html_edit.h" + // define flash strings once (saves flash memory) static const char s_redirecting[] PROGMEM = "Redirecting..."; static const char s_content_enc[] PROGMEM = "Content-Encoding"; @@ -32,6 +33,7 @@ static const char s_no_store[] PROGMEM = "no-store"; static const char s_expires[] PROGMEM = "Expires"; static const char _common_js[] PROGMEM = "/common.js"; + //Is this an IP? static bool isIp(const String &str) { for (size_t i = 0; i < str.length(); i++) { @@ -180,6 +182,7 @@ static String msgProcessor(const String& var) return String(); } + static void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool isFinal) { if (!correctPIN) { if (isFinal) request->send(401, FPSTR(CONTENT_TYPE_PLAIN), FPSTR(s_unlock_cfg)); @@ -527,6 +530,53 @@ void initServer() server.on(_update, HTTP_POST, notSupported, [](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool isFinal){}); #endif +#if defined(ARDUINO_ARCH_ESP32) && !defined(WLED_DISABLE_OTA) + // ESP32 bootloader update endpoint + server.on(F("/updatebootloader"), HTTP_POST, [](AsyncWebServerRequest *request){ + if (request->_tempObject) { + auto bootloader_result = getBootloaderOTAResult(request); + if (bootloader_result.first) { + if (bootloader_result.second.length() > 0) { + serveMessage(request, 500, F("Bootloader update failed!"), bootloader_result.second, 254); + } else { + serveMessage(request, 200, F("Bootloader updated successfully!"), FPSTR(s_rebooting), 131); + } + } + } else { + // No context structure - something's gone horribly wrong + serveMessage(request, 500, F("Bootloader update failed!"), F("Internal server fault"), 254); + } + },[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool isFinal){ + if (index == 0) { + // Privilege checks + 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!")); + serveMessage(request, 401, FPSTR(s_accessdenied), F("Client is not on local subnet."), 254); + setBootloaderOTAReplied(request); + return; + } + if (!correctPIN) { + serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_cfg), 254); + setBootloaderOTAReplied(request); + return; + } + if (otaLock) { + serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_ota), 254); + setBootloaderOTAReplied(request); + return; + } + + // Allocate the context structure + if (!initBootloaderOTA(request)) { + return; // Error will be dealt with after upload in response handler, above + } + } + + handleBootloaderOTAData(request, index, data, len, isFinal); + }); +#endif + #ifdef WLED_ENABLE_DMX server.on(F("/dmxmap"), HTTP_GET, [](AsyncWebServerRequest *request){ request->send_P(200, FPSTR(CONTENT_TYPE_HTML), PAGE_dmxmap, dmxProcessor);