mirror of
https://github.com/wled/WLED.git
synced 2025-11-11 03:51:30 +00:00
Compare commits
17 Commits
coderabbit
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
196f5798a1 | ||
|
|
614984257c | ||
|
|
6676705428 | ||
|
|
71ad0d99aa | ||
|
|
70e8be6b4d | ||
|
|
f706c6cbed | ||
|
|
18dfc7081c | ||
|
|
5c0c84eb6b | ||
|
|
691c058ae8 | ||
|
|
7d550baf94 | ||
|
|
42ff73ffe7 | ||
|
|
2d8edfcb24 | ||
|
|
fb077ecadc | ||
|
|
caf3d900fd | ||
|
|
e920d2e101 | ||
|
|
54746c9730 | ||
|
|
8225a2a07c |
@@ -17,7 +17,27 @@
|
|||||||
}
|
}
|
||||||
window.open(getURL("/update?revert"),"_self");
|
window.open(getURL("/update?revert"),"_self");
|
||||||
}
|
}
|
||||||
function GetV() {/*injected values here*/}
|
function GetV() {
|
||||||
|
// Fetch device info via JSON API instead of compiling it in
|
||||||
|
fetch('/json/info')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.release) {
|
||||||
|
var releaseSpan = document.querySelector('.release-name');
|
||||||
|
if (releaseSpan) {
|
||||||
|
releaseSpan.textContent = data.release;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.log('Could not fetch device info:', error);
|
||||||
|
// Fallback to compiled-in value if API call fails
|
||||||
|
var releaseSpan = document.querySelector('.release-name');
|
||||||
|
if (releaseSpan && releaseSpan.textContent === 'Loading...') {
|
||||||
|
releaseSpan.textContent = 'Unknown';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
@import url("style.css");
|
@import url("style.css");
|
||||||
@@ -28,10 +48,13 @@
|
|||||||
<h2>WLED Software Update</h2>
|
<h2>WLED Software Update</h2>
|
||||||
<form method='POST' action='./update' id='upd' enctype='multipart/form-data' onsubmit="toggle('upd')">
|
<form method='POST' action='./update' id='upd' enctype='multipart/form-data' onsubmit="toggle('upd')">
|
||||||
Installed version: <span class="sip">WLED ##VERSION##</span><br>
|
Installed version: <span class="sip">WLED ##VERSION##</span><br>
|
||||||
|
Release: <span class="sip release-name">Loading...</span><br>
|
||||||
Download the latest binary: <a href="https://github.com/wled-dev/WLED/releases" target="_blank"
|
Download the latest binary: <a href="https://github.com/wled-dev/WLED/releases" target="_blank"
|
||||||
style="vertical-align: text-bottom; display: inline-flex;">
|
style="vertical-align: text-bottom; display: inline-flex;">
|
||||||
<img src="https://img.shields.io/github/release/wled-dev/WLED.svg?style=flat-square"></a><br>
|
<img src="https://img.shields.io/github/release/wled-dev/WLED.svg?style=flat-square"></a><br>
|
||||||
<input type='file' name='update' required><br> <!--should have accept='.bin', but it prevents file upload from android app-->
|
<input type='file' name='update' required><br> <!--should have accept='.bin', but it prevents file upload from android app-->
|
||||||
|
<input type='checkbox' name='skipValidation' id='skipValidation'>
|
||||||
|
<label for='skipValidation'>Ignore firmware validation</label><br>
|
||||||
<button type="submit">Update!</button><br>
|
<button type="submit">Update!</button><br>
|
||||||
<hr class="sml">
|
<hr class="sml">
|
||||||
<button id="rev" type="button" onclick="cR()">Revert update</button><br>
|
<button id="rev" type="button" onclick="cR()">Revert update</button><br>
|
||||||
|
|||||||
111
wled00/ota_release_check.cpp
Normal file
111
wled00/ota_release_check.cpp
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
#include "ota_release_check.h"
|
||||||
|
#include "wled.h"
|
||||||
|
|
||||||
|
#ifdef ESP32
|
||||||
|
#include <esp_app_format.h>
|
||||||
|
#include <esp_ota_ops.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool extractWledCustomDesc(const uint8_t* binaryData, size_t dataSize, wled_custom_desc_t* extractedDesc) {
|
||||||
|
if (!binaryData || !extractedDesc || dataSize < 64) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search in first 8KB only. This range was chosen because:
|
||||||
|
// - ESP32 .rodata.wled_desc sections appear early in the binary (typically within first 2-4KB)
|
||||||
|
// - ESP8266 .ver_number sections also appear early (typically within first 1-2KB)
|
||||||
|
// - 8KB provides ample coverage for metadata discovery while minimizing processing time
|
||||||
|
// - Larger firmware files (>1MB) would take significantly longer to process with full search
|
||||||
|
// - Analysis of typical WLED binary layouts shows metadata appears well within this range
|
||||||
|
const size_t search_limit = min(dataSize, (size_t)8192);
|
||||||
|
|
||||||
|
for (size_t offset = 0; offset <= search_limit - sizeof(wled_custom_desc_t); offset++) {
|
||||||
|
const wled_custom_desc_t* custom_desc = (const wled_custom_desc_t*)(binaryData + offset);
|
||||||
|
|
||||||
|
// Check for magic number
|
||||||
|
if (custom_desc->magic == WLED_CUSTOM_DESC_MAGIC) {
|
||||||
|
// Found potential match, validate version
|
||||||
|
if (custom_desc->version != WLED_CUSTOM_DESC_VERSION) {
|
||||||
|
DEBUG_PRINTF_P(PSTR("Found WLED structure at offset %u but version mismatch: %u\n"),
|
||||||
|
offset, custom_desc->version);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate hash using runtime function
|
||||||
|
uint32_t expected_hash = djb2_hash_runtime(custom_desc->release_name);
|
||||||
|
if (custom_desc->crc32 != expected_hash) {
|
||||||
|
DEBUG_PRINTF_P(PSTR("Found WLED structure at offset %u but hash mismatch\n"), offset);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid structure found - copy entire structure
|
||||||
|
memcpy(extractedDesc, custom_desc, sizeof(wled_custom_desc_t));
|
||||||
|
|
||||||
|
DEBUG_PRINTF_P(PSTR("Extracted WLED structure at offset %u: '%s'\n"),
|
||||||
|
offset, extractedDesc->release_name);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG_PRINTLN(F("No WLED custom description found in binary"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool validateReleaseCompatibility(const char* extractedRelease) {
|
||||||
|
if (!extractedRelease) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure extractedRelease is properly null terminated (guard against fixed-length buffer issues)
|
||||||
|
char safeRelease[WLED_RELEASE_NAME_MAX_LEN];
|
||||||
|
strncpy(safeRelease, extractedRelease, WLED_RELEASE_NAME_MAX_LEN - 1);
|
||||||
|
safeRelease[WLED_RELEASE_NAME_MAX_LEN - 1] = '\0';
|
||||||
|
|
||||||
|
if (strlen(safeRelease) == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple string comparison - releases must match exactly
|
||||||
|
bool match = strcmp(releaseString, safeRelease) == 0;
|
||||||
|
|
||||||
|
DEBUG_PRINTF_P(PSTR("Release compatibility check: current='%s', uploaded='%s', match=%s\n"),
|
||||||
|
releaseString, safeRelease, match ? "YES" : "NO");
|
||||||
|
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool shouldAllowOTA(const uint8_t* binaryData, size_t dataSize, char* errorMessage, size_t errorMessageLen) {
|
||||||
|
// Clear error message
|
||||||
|
if (errorMessage && errorMessageLen > 0) {
|
||||||
|
errorMessage[0] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure our custom description structure is referenced (prevents optimization)
|
||||||
|
const wled_custom_desc_t* local_desc = getWledCustomDesc();
|
||||||
|
(void)local_desc; // Suppress unused variable warning
|
||||||
|
|
||||||
|
// Try to extract WLED structure directly from binary data
|
||||||
|
wled_custom_desc_t extractedDesc;
|
||||||
|
bool hasCustomDesc = extractWledCustomDesc(binaryData, dataSize, &extractedDesc);
|
||||||
|
|
||||||
|
if (!hasCustomDesc) {
|
||||||
|
// No custom description - this could be a legacy binary
|
||||||
|
if (errorMessage && errorMessageLen > 0) {
|
||||||
|
strncpy_P(errorMessage, PSTR("This firmware file is missing compatibility metadata. Enable 'Ignore firmware validation' to proceed anyway."), errorMessageLen - 1);
|
||||||
|
errorMessage[errorMessageLen - 1] = '\0';
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate compatibility using extracted release name
|
||||||
|
if (!validateReleaseCompatibility(extractedDesc.release_name)) {
|
||||||
|
if (errorMessage && errorMessageLen > 0) {
|
||||||
|
snprintf_P(errorMessage, errorMessageLen, PSTR("Firmware compatibility mismatch: current='%s', uploaded='%s'. Enable 'Ignore firmware validation' to proceed anyway."),
|
||||||
|
releaseString, extractedDesc.release_name);
|
||||||
|
errorMessage[errorMessageLen - 1] = '\0'; // Ensure null termination
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
90
wled00/ota_release_check.h
Normal file
90
wled00/ota_release_check.h
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
#ifndef WLED_OTA_RELEASE_CHECK_H
|
||||||
|
#define WLED_OTA_RELEASE_CHECK_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* OTA Release Compatibility Checking using ESP-IDF Custom Description Section
|
||||||
|
* Functions to extract and validate release names from uploaded binary files using embedded metadata
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
#ifdef ESP32
|
||||||
|
#include <esp_app_format.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define WLED_CUSTOM_DESC_MAGIC 0x57535453 // "WSTS" (WLED System Tag Structure)
|
||||||
|
#define WLED_CUSTOM_DESC_VERSION 1
|
||||||
|
#define WLED_RELEASE_NAME_MAX_LEN 48
|
||||||
|
|
||||||
|
// Platform-specific metadata offset in binary file
|
||||||
|
#ifdef ESP32
|
||||||
|
#define METADATA_OFFSET 0 // ESP32: metadata appears at beginning
|
||||||
|
#elif defined(ESP8266)
|
||||||
|
#define METADATA_OFFSET 0x1000 // ESP8266: metadata appears at 4KB offset
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DJB2 hash function (C++11 compatible constexpr)
|
||||||
|
* Used for compile-time hash computation of release names
|
||||||
|
*/
|
||||||
|
constexpr uint32_t djb2_hash_constexpr(const char* str, uint32_t hash = 5381) {
|
||||||
|
return (*str == '\0') ? hash : djb2_hash_constexpr(str + 1, ((hash << 5) + hash) + *str);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runtime DJB2 hash function for validation
|
||||||
|
*/
|
||||||
|
inline uint32_t djb2_hash_runtime(const char* str) {
|
||||||
|
uint32_t hash = 5381;
|
||||||
|
while (*str) {
|
||||||
|
hash = ((hash << 5) + hash) + *str++;
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WLED Custom Description Structure
|
||||||
|
* This structure is embedded in platform-specific sections at a fixed offset
|
||||||
|
* in ESP32/ESP8266 binaries, allowing extraction without modifying the binary format
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
uint32_t magic; // Magic number to identify WLED custom description
|
||||||
|
uint32_t version; // Structure version for future compatibility
|
||||||
|
char release_name[WLED_RELEASE_NAME_MAX_LEN]; // Release name (null-terminated)
|
||||||
|
uint32_t crc32; // CRC32 of the above fields for integrity check
|
||||||
|
} __attribute__((packed)) wled_custom_desc_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract WLED custom description structure from binary
|
||||||
|
* @param binaryData Pointer to binary file data
|
||||||
|
* @param dataSize Size of binary data in bytes
|
||||||
|
* @param extractedDesc Buffer to store extracted custom description structure
|
||||||
|
* @return true if structure was found and extracted, false otherwise
|
||||||
|
*/
|
||||||
|
bool extractWledCustomDesc(const uint8_t* binaryData, size_t dataSize, wled_custom_desc_t* extractedDesc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate if extracted release name matches current release
|
||||||
|
* @param extractedRelease Release name from uploaded binary
|
||||||
|
* @return true if releases match (OTA should proceed), false if they don't match
|
||||||
|
*/
|
||||||
|
bool validateReleaseCompatibility(const char* extractedRelease);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if OTA should be allowed based on release compatibility using custom description
|
||||||
|
* @param binaryData Pointer to binary file data (not modified)
|
||||||
|
* @param dataSize Size of binary data in bytes
|
||||||
|
* @param errorMessage Buffer to store error message if validation fails
|
||||||
|
* @param errorMessageLen Maximum length of error message buffer
|
||||||
|
* @return true if OTA should proceed, false if it should be blocked
|
||||||
|
*/
|
||||||
|
bool shouldAllowOTA(const uint8_t* binaryData, size_t dataSize, char* errorMessage, size_t errorMessageLen);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get pointer to the embedded custom description structure
|
||||||
|
* This ensures the structure is referenced and not optimized out
|
||||||
|
* @return pointer to the custom description structure
|
||||||
|
*/
|
||||||
|
const wled_custom_desc_t* getWledCustomDesc();
|
||||||
|
|
||||||
|
#endif // WLED_OTA_RELEASE_CHECK_H
|
||||||
29
wled00/wled_custom_desc.cpp
Normal file
29
wled00/wled_custom_desc.cpp
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#include "ota_release_check.h"
|
||||||
|
#include "wled.h"
|
||||||
|
|
||||||
|
// Platform-specific section definition
|
||||||
|
#ifdef ESP32
|
||||||
|
#define WLED_CUSTOM_DESC_SECTION ".rodata.wled_desc"
|
||||||
|
#elif defined(ESP8266)
|
||||||
|
#define WLED_CUSTOM_DESC_SECTION ".ver_number"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Single structure definition for both platforms
|
||||||
|
const wled_custom_desc_t __attribute__((section(WLED_CUSTOM_DESC_SECTION))) wled_custom_description = {
|
||||||
|
WLED_CUSTOM_DESC_MAGIC, // magic
|
||||||
|
WLED_CUSTOM_DESC_VERSION, // version
|
||||||
|
WLED_RELEASE_NAME, // release_name
|
||||||
|
djb2_hash_constexpr(WLED_RELEASE_NAME) // crc32 - computed at compile time
|
||||||
|
};
|
||||||
|
|
||||||
|
// Compile-time validation that release name doesn't exceed maximum length
|
||||||
|
static_assert(sizeof(WLED_RELEASE_NAME) <= WLED_RELEASE_NAME_MAX_LEN,
|
||||||
|
"WLED_RELEASE_NAME exceeds maximum length of WLED_RELEASE_NAME_MAX_LEN characters");
|
||||||
|
|
||||||
|
// Single reference to ensure it's not optimized away
|
||||||
|
const wled_custom_desc_t* __attribute__((used)) wled_custom_desc_ref = &wled_custom_description;
|
||||||
|
|
||||||
|
// Function to ensure the structure is referenced by code
|
||||||
|
const wled_custom_desc_t* getWledCustomDesc() {
|
||||||
|
return &wled_custom_description;
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
#else
|
#else
|
||||||
#include <Update.h>
|
#include <Update.h>
|
||||||
#endif
|
#endif
|
||||||
|
#include "ota_release_check.h"
|
||||||
#endif
|
#endif
|
||||||
#include "html_ui.h"
|
#include "html_ui.h"
|
||||||
#include "html_settings.h"
|
#include "html_settings.h"
|
||||||
@@ -424,32 +425,186 @@ void initServer()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!correctPIN || otaLock) return;
|
if (!correctPIN || otaLock) return;
|
||||||
|
|
||||||
|
// Static variables to track release check status across chunks
|
||||||
|
static bool releaseCheckPassed = false;
|
||||||
|
static bool releaseCheckPending = true;
|
||||||
|
static size_t totalBytesReceived = 0;
|
||||||
|
static uint8_t* metadataBuffer = nullptr;
|
||||||
|
static size_t metadataBufferUsed = 0;
|
||||||
|
|
||||||
if(!index){
|
if(!index){
|
||||||
DEBUG_PRINTLN(F("OTA Update Start"));
|
DEBUG_PRINTLN(F("OTA Update Start"));
|
||||||
|
|
||||||
|
// Reset validation state for new update
|
||||||
|
releaseCheckPassed = false;
|
||||||
|
releaseCheckPending = true;
|
||||||
|
totalBytesReceived = 0;
|
||||||
|
|
||||||
|
// Free any existing metadata buffer
|
||||||
|
if (metadataBuffer) {
|
||||||
|
free(metadataBuffer);
|
||||||
|
metadataBuffer = nullptr;
|
||||||
|
metadataBufferUsed = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user wants to skip validation
|
||||||
|
bool skipValidation = request->hasParam("skipValidation", true);
|
||||||
|
|
||||||
|
// If user chose to skip validation, proceed without compatibility check
|
||||||
|
if (skipValidation) {
|
||||||
|
DEBUG_PRINTLN(F("OTA validation skipped by user"));
|
||||||
|
releaseCheckPassed = true;
|
||||||
|
releaseCheckPending = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG_PRINTLN(F("Release check passed, starting OTA update"));
|
||||||
|
|
||||||
#if WLED_WATCHDOG_TIMEOUT > 0
|
#if WLED_WATCHDOG_TIMEOUT > 0
|
||||||
WLED::instance().disableWatchdog();
|
WLED::instance().disableWatchdog();
|
||||||
#endif
|
#endif
|
||||||
UsermodManager::onUpdateBegin(true); // notify usermods that update is about to begin (some may require task de-init)
|
UsermodManager::onUpdateBegin(true); // notify usermods that update is about to begin (some may require task de-init)
|
||||||
lastEditTime = millis(); // make sure PIN does not lock during update
|
lastEditTime = millis(); // make sure PIN does not lock during update
|
||||||
|
|
||||||
|
// Start the actual OTA update
|
||||||
strip.suspend();
|
strip.suspend();
|
||||||
backupConfig(); // backup current config in case the update ends badly
|
backupConfig(); // backup current config in case the update ends badly
|
||||||
strip.resetSegments(); // free as much memory as you can
|
strip.resetSegments(); // free as much memory as you can
|
||||||
#ifdef ESP8266
|
#ifdef ESP8266
|
||||||
Update.runAsync(true);
|
Update.runAsync(true);
|
||||||
#endif
|
#endif
|
||||||
Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000);
|
|
||||||
}
|
// Begin update with the firmware size from content length
|
||||||
if(!Update.hasError()) Update.write(data, len);
|
size_t updateSize = request->contentLength() > 0 ? request->contentLength() : ((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000);
|
||||||
if(isFinal){
|
if (!Update.begin(updateSize)) {
|
||||||
if(Update.end(true)){
|
DEBUG_PRINTF_P(PSTR("OTA Failed to begin: %s\n"), Update.getErrorString().c_str());
|
||||||
DEBUG_PRINTLN(F("Update Success"));
|
|
||||||
} else {
|
|
||||||
DEBUG_PRINTLN(F("Update Failed"));
|
|
||||||
strip.resume();
|
strip.resume();
|
||||||
UsermodManager::onUpdateBegin(false); // notify usermods that update has failed (some may require task init)
|
UsermodManager::onUpdateBegin(false);
|
||||||
#if WLED_WATCHDOG_TIMEOUT > 0
|
#if WLED_WATCHDOG_TIMEOUT > 0
|
||||||
WLED::instance().enableWatchdog();
|
WLED::instance().enableWatchdog();
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef ESP32
|
||||||
|
request->send(500, FPSTR(CONTENT_TYPE_PLAIN), String("Update.begin failed: ") + Update.errorString());
|
||||||
|
#else
|
||||||
|
request->send(500, FPSTR(CONTENT_TYPE_PLAIN), String("Update.begin failed: ") + Update.getErrorString());
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track total bytes received across all chunks
|
||||||
|
size_t chunkStartOffset = totalBytesReceived;
|
||||||
|
totalBytesReceived += len;
|
||||||
|
|
||||||
|
// Perform validation if we haven't done it yet and we have reached the metadata offset
|
||||||
|
if (releaseCheckPending && totalBytesReceived > METADATA_OFFSET) {
|
||||||
|
|
||||||
|
if (chunkStartOffset <= METADATA_OFFSET) {
|
||||||
|
// Current chunk contains the metadata offset
|
||||||
|
size_t offsetInChunk = METADATA_OFFSET - chunkStartOffset;
|
||||||
|
size_t availableDataAfterOffset = len - offsetInChunk;
|
||||||
|
|
||||||
|
if (availableDataAfterOffset >= sizeof(wled_custom_desc_t)) {
|
||||||
|
// We have enough data in this chunk to validate immediately
|
||||||
|
char errorMessage[128];
|
||||||
|
releaseCheckPassed = shouldAllowOTA(data + offsetInChunk, availableDataAfterOffset,
|
||||||
|
errorMessage, sizeof(errorMessage));
|
||||||
|
releaseCheckPending = false;
|
||||||
|
|
||||||
|
if (!releaseCheckPassed) {
|
||||||
|
DEBUG_PRINTF_P(PSTR("OTA declined: %s\n"), errorMessage);
|
||||||
|
request->send(400, FPSTR(CONTENT_TYPE_PLAIN), errorMessage);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
DEBUG_PRINTLN(F("OTA allowed: Release compatibility check passed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Not enough data in this chunk, buffer it for the next chunk
|
||||||
|
metadataBuffer = (uint8_t*)malloc(len - offsetInChunk);
|
||||||
|
if (!metadataBuffer) {
|
||||||
|
DEBUG_PRINTLN(F("OTA failed: Could not allocate metadata buffer"));
|
||||||
|
request->send(500, FPSTR(CONTENT_TYPE_PLAIN), F("Out of memory for validation"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
memcpy(metadataBuffer, data + offsetInChunk, len - offsetInChunk);
|
||||||
|
metadataBufferUsed = len - offsetInChunk;
|
||||||
|
DEBUG_PRINTF_P(PSTR("Buffered %u bytes of metadata for next chunk\n"), metadataBufferUsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (metadataBuffer && metadataBufferUsed > 0) {
|
||||||
|
// We have buffered metadata from previous chunk, combine with current chunk
|
||||||
|
size_t totalMetadataSize = metadataBufferUsed + len;
|
||||||
|
|
||||||
|
if (totalMetadataSize >= sizeof(wled_custom_desc_t)) {
|
||||||
|
// Create combined buffer for validation
|
||||||
|
uint8_t* combinedBuffer = (uint8_t*)malloc(totalMetadataSize);
|
||||||
|
if (!combinedBuffer) {
|
||||||
|
DEBUG_PRINTLN(F("OTA failed: Could not allocate combined buffer"));
|
||||||
|
request->send(500, FPSTR(CONTENT_TYPE_PLAIN), F("Out of memory for validation"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(combinedBuffer, metadataBuffer, metadataBufferUsed);
|
||||||
|
memcpy(combinedBuffer + metadataBufferUsed, data, len);
|
||||||
|
|
||||||
|
char errorMessage[128];
|
||||||
|
releaseCheckPassed = shouldAllowOTA(combinedBuffer, totalMetadataSize,
|
||||||
|
errorMessage, sizeof(errorMessage));
|
||||||
|
releaseCheckPending = false;
|
||||||
|
|
||||||
|
// Clean up buffers
|
||||||
|
free(combinedBuffer);
|
||||||
|
free(metadataBuffer);
|
||||||
|
metadataBuffer = nullptr;
|
||||||
|
metadataBufferUsed = 0;
|
||||||
|
|
||||||
|
if (!releaseCheckPassed) {
|
||||||
|
DEBUG_PRINTF_P(PSTR("OTA declined: %s\n"), errorMessage);
|
||||||
|
request->send(400, FPSTR(CONTENT_TYPE_PLAIN), errorMessage);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
DEBUG_PRINTLN(F("OTA allowed: Release compatibility check passed"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write chunk data to OTA update (only if release check passed or still pending)
|
||||||
|
if ((releaseCheckPassed || releaseCheckPending) && !Update.hasError()) {
|
||||||
|
if (Update.write(data, len) != len) {
|
||||||
|
DEBUG_PRINTF_P(PSTR("OTA write failed on chunk %zu: %s\n"), index, Update.getErrorString().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isFinal){
|
||||||
|
DEBUG_PRINTLN(F("OTA Update End"));
|
||||||
|
|
||||||
|
// Clean up metadata buffer if still allocated
|
||||||
|
if (metadataBuffer) {
|
||||||
|
free(metadataBuffer);
|
||||||
|
metadataBuffer = nullptr;
|
||||||
|
metadataBufferUsed = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if validation was still pending (shouldn't happen normally)
|
||||||
|
if (releaseCheckPending) {
|
||||||
|
DEBUG_PRINTLN(F("OTA failed: Validation never completed"));
|
||||||
|
request->send(400, FPSTR(CONTENT_TYPE_PLAIN), F("Firmware validation incomplete"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (releaseCheckPassed) {
|
||||||
|
if(Update.end(true)){
|
||||||
|
DEBUG_PRINTLN(F("Update Success"));
|
||||||
|
} else {
|
||||||
|
DEBUG_PRINTLN(F("Update Failed"));
|
||||||
|
strip.resume();
|
||||||
|
UsermodManager::onUpdateBegin(false); // notify usermods that update has failed (some may require task init)
|
||||||
|
#if WLED_WATCHDOG_TIMEOUT > 0
|
||||||
|
WLED::instance().enableWatchdog();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user