mirror of
https://github.com/wled/WLED.git
synced 2025-11-09 02:59:03 +00:00
Address review feedback: unify structures, fix C++11 compatibility, improve user messages
Co-authored-by: willmmiles <6540455+willmmiles@users.noreply.github.com>
This commit is contained in:
@@ -14,7 +14,7 @@ else:
|
|||||||
env.Execute("npm ci")
|
env.Execute("npm ci")
|
||||||
|
|
||||||
# Extract the release name from build flags
|
# Extract the release name from build flags
|
||||||
release_name = "Custom"
|
release_name = None # Let cdata.js provide the default if not found
|
||||||
build_flags = env.get("BUILD_FLAGS", [])
|
build_flags = env.get("BUILD_FLAGS", [])
|
||||||
for flag in build_flags:
|
for flag in build_flags:
|
||||||
if 'WLED_RELEASE_NAME=' in flag:
|
if 'WLED_RELEASE_NAME=' in flag:
|
||||||
@@ -24,9 +24,12 @@ else:
|
|||||||
release_name = parts[1].split()[0].strip('\"\\')
|
release_name = parts[1].split()[0].strip('\"\\')
|
||||||
break
|
break
|
||||||
|
|
||||||
# Set environment variable for cdata.js to use
|
# Set environment variable for cdata.js to use (only if found)
|
||||||
os.environ['WLED_RELEASE_NAME'] = release_name
|
if release_name:
|
||||||
print(f'Building web UI with release name: {release_name}')
|
os.environ['WLED_RELEASE_NAME'] = release_name
|
||||||
|
print(f'Building web UI with release name: {release_name}')
|
||||||
|
else:
|
||||||
|
print('Building web UI with default release name (from cdata.js)')
|
||||||
|
|
||||||
# Call the bundling script
|
# Call the bundling script
|
||||||
exitCode = env.Execute("npm run build")
|
exitCode = env.Execute("npm run build")
|
||||||
|
|||||||
@@ -6,23 +6,17 @@
|
|||||||
#include <esp_ota_ops.h>
|
#include <esp_ota_ops.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Same hash function used at compile time (must match wled_custom_desc.cpp)
|
bool extractWledCustomDesc(const uint8_t* binaryData, size_t dataSize, wled_custom_desc_t* extractedDesc) {
|
||||||
static uint32_t djb2_hash(const char* str) {
|
if (!binaryData || !extractedDesc || dataSize < 64) {
|
||||||
uint32_t hash = 5381;
|
|
||||||
while (*str) {
|
|
||||||
hash = ((hash << 5) + hash) + *str++;
|
|
||||||
}
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool extractReleaseFromCustomDesc(const uint8_t* binaryData, size_t dataSize, char* extractedRelease) {
|
|
||||||
if (!binaryData || !extractedRelease || dataSize < 64) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search in first 8KB only - ESP32 .rodata.wled_desc and ESP8266 .ver_number
|
// Search in first 8KB only. This range was chosen because:
|
||||||
// sections appear early in binary. 8KB should be sufficient for metadata discovery
|
// - ESP32 .rodata.wled_desc sections appear early in the binary (typically within first 2-4KB)
|
||||||
// while minimizing processing time for large firmware files.
|
// - 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
|
||||||
|
// - Real-world testing shows all valid metadata appears well within this range
|
||||||
const size_t search_limit = min(dataSize, (size_t)8192);
|
const size_t search_limit = min(dataSize, (size_t)8192);
|
||||||
|
|
||||||
for (size_t offset = 0; offset <= search_limit - sizeof(wled_custom_desc_t); offset++) {
|
for (size_t offset = 0; offset <= search_limit - sizeof(wled_custom_desc_t); offset++) {
|
||||||
@@ -37,23 +31,22 @@ bool extractReleaseFromCustomDesc(const uint8_t* binaryData, size_t dataSize, ch
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate hash using same algorithm as compile-time
|
// Validate hash using runtime function
|
||||||
uint32_t expected_hash = djb2_hash(custom_desc->release_name);
|
uint32_t expected_hash = djb2_hash_runtime(custom_desc->release_name);
|
||||||
if (custom_desc->crc32 != expected_hash) {
|
if (custom_desc->crc32 != expected_hash) {
|
||||||
DEBUG_PRINTF_P(PSTR("Found WLED structure at offset %u but hash mismatch\n"), offset);
|
DEBUG_PRINTF_P(PSTR("Found WLED structure at offset %u but hash mismatch\n"), offset);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Valid structure found
|
// Valid structure found - copy entire structure
|
||||||
strncpy(extractedRelease, custom_desc->release_name, WLED_RELEASE_NAME_MAX_LEN - 1);
|
memcpy(extractedDesc, custom_desc, sizeof(wled_custom_desc_t));
|
||||||
extractedRelease[WLED_RELEASE_NAME_MAX_LEN - 1] = '\0';
|
|
||||||
|
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
DEBUG_PRINTF_P(PSTR("Extracted ESP32 release name from .rodata.wled_desc section at offset %u: '%s'\n"),
|
DEBUG_PRINTF_P(PSTR("Extracted ESP32 WLED structure from .rodata.wled_desc section at offset %u: '%s'\n"),
|
||||||
offset, extractedRelease);
|
offset, extractedDesc->release_name);
|
||||||
#else
|
#else
|
||||||
DEBUG_PRINTF_P(PSTR("Extracted ESP8266 release name from .ver_number section at offset %u: '%s'\n"),
|
DEBUG_PRINTF_P(PSTR("Extracted ESP8266 WLED structure from .ver_number section at offset %u: '%s'\n"),
|
||||||
offset, extractedRelease);
|
offset, extractedDesc->release_name);
|
||||||
#endif
|
#endif
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -87,33 +80,33 @@ bool shouldAllowOTA(const uint8_t* binaryData, size_t dataSize, bool skipValidat
|
|||||||
const wled_custom_desc_t* local_desc = getWledCustomDesc();
|
const wled_custom_desc_t* local_desc = getWledCustomDesc();
|
||||||
(void)local_desc; // Suppress unused variable warning
|
(void)local_desc; // Suppress unused variable warning
|
||||||
|
|
||||||
// If user chose to ignore release check, allow OTA
|
// If user chose to skip validation, allow OTA immediately
|
||||||
if (skipValidation) {
|
if (skipValidation) {
|
||||||
DEBUG_PRINTLN(F("OTA release check bypassed by user"));
|
DEBUG_PRINTLN(F("OTA release check bypassed by user"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to extract release name directly from binary data
|
// Try to extract WLED structure directly from binary data
|
||||||
char extractedRelease[WLED_RELEASE_NAME_MAX_LEN];
|
wled_custom_desc_t extractedDesc;
|
||||||
bool hasCustomDesc = extractReleaseFromCustomDesc(binaryData, dataSize, extractedRelease);
|
bool hasCustomDesc = extractWledCustomDesc(binaryData, dataSize, &extractedDesc);
|
||||||
|
|
||||||
if (!hasCustomDesc) {
|
if (!hasCustomDesc) {
|
||||||
// No custom description - this could be a legacy binary
|
// No custom description - this could be a legacy binary
|
||||||
if (errorMessage) {
|
if (errorMessage) {
|
||||||
strcpy(errorMessage, "Binary has no release compatibility metadata. Check 'Ignore validation' to proceed.");
|
strcpy(errorMessage, "This firmware file is missing compatibility metadata. Enable 'Ignore firmware validation' to proceed anyway.");
|
||||||
}
|
}
|
||||||
DEBUG_PRINTLN(F("OTA blocked: No custom description found"));
|
DEBUG_PRINTLN(F("OTA declined: No custom description found"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate compatibility using extracted release name
|
// Validate compatibility using extracted release name
|
||||||
if (!validateReleaseCompatibility(extractedRelease)) {
|
if (!validateReleaseCompatibility(extractedDesc.release_name)) {
|
||||||
if (errorMessage) {
|
if (errorMessage) {
|
||||||
snprintf(errorMessage, 127, "Release mismatch: current='%s', uploaded='%s'. Check 'Ignore validation' to proceed.",
|
snprintf(errorMessage, 127, "Firmware compatibility mismatch: current='%s', uploaded='%s'. Enable 'Ignore firmware validation' to proceed anyway.",
|
||||||
releaseString, extractedRelease);
|
releaseString, extractedDesc.release_name);
|
||||||
}
|
}
|
||||||
DEBUG_PRINTF_P(PSTR("OTA blocked: Release mismatch current='%s', uploaded='%s'\n"),
|
DEBUG_PRINTF_P(PSTR("OTA declined: Release mismatch current='%s', uploaded='%s'\n"),
|
||||||
releaseString, extractedRelease);
|
releaseString, extractedDesc.release_name);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,25 @@
|
|||||||
#define WLED_CUSTOM_DESC_VERSION 1
|
#define WLED_CUSTOM_DESC_VERSION 1
|
||||||
#define WLED_RELEASE_NAME_MAX_LEN 48
|
#define WLED_RELEASE_NAME_MAX_LEN 48
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
* WLED Custom Description Structure
|
||||||
* This structure is embedded in platform-specific sections at a fixed offset
|
* This structure is embedded in platform-specific sections at a fixed offset
|
||||||
@@ -29,13 +48,13 @@ typedef struct {
|
|||||||
} __attribute__((packed)) wled_custom_desc_t;
|
} __attribute__((packed)) wled_custom_desc_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract release name from binary using ESP-IDF custom description section
|
* Extract WLED custom description structure from binary
|
||||||
* @param binaryData Pointer to binary file data
|
* @param binaryData Pointer to binary file data
|
||||||
* @param dataSize Size of binary data in bytes
|
* @param dataSize Size of binary data in bytes
|
||||||
* @param extractedRelease Buffer to store extracted release name (should be at least WLED_RELEASE_NAME_MAX_LEN bytes)
|
* @param extractedDesc Buffer to store extracted custom description structure
|
||||||
* @return true if release name was found and extracted, false otherwise
|
* @return true if structure was found and extracted, false otherwise
|
||||||
*/
|
*/
|
||||||
bool extractReleaseFromCustomDesc(const uint8_t* binaryData, size_t dataSize, char* extractedRelease);
|
bool extractWledCustomDesc(const uint8_t* binaryData, size_t dataSize, wled_custom_desc_t* extractedDesc);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate if extracted release name matches current release
|
* Validate if extracted release name matches current release
|
||||||
|
|||||||
@@ -1,25 +1,19 @@
|
|||||||
#include "ota_release_check.h"
|
#include "ota_release_check.h"
|
||||||
#include "wled.h"
|
#include "wled.h"
|
||||||
|
|
||||||
// Simple compile-time hash function for release name validation
|
// Platform-specific section definition
|
||||||
constexpr uint32_t djb2_hash(const char* str) {
|
#ifdef ESP32
|
||||||
uint32_t hash = 5381;
|
#define WLED_CUSTOM_DESC_SECTION ".rodata.wled_desc"
|
||||||
while (*str) {
|
#elif defined(ESP8266)
|
||||||
hash = ((hash << 5) + hash) + *str++;
|
#define WLED_CUSTOM_DESC_SECTION ".ver_number"
|
||||||
}
|
#endif
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Single structure definition for both platforms
|
// Single structure definition for both platforms
|
||||||
#ifdef ESP32
|
const wled_custom_desc_t __attribute__((section(WLED_CUSTOM_DESC_SECTION))) wled_custom_description = {
|
||||||
const wled_custom_desc_t __attribute__((section(".rodata.wled_desc"))) wled_custom_description = {
|
|
||||||
#elif defined(ESP8266)
|
|
||||||
const wled_custom_desc_t __attribute__((section(".ver_number"))) wled_custom_description = {
|
|
||||||
#endif
|
|
||||||
WLED_CUSTOM_DESC_MAGIC, // magic
|
WLED_CUSTOM_DESC_MAGIC, // magic
|
||||||
WLED_CUSTOM_DESC_VERSION, // version
|
WLED_CUSTOM_DESC_VERSION, // version
|
||||||
WLED_RELEASE_NAME, // release_name
|
WLED_RELEASE_NAME, // release_name
|
||||||
djb2_hash(WLED_RELEASE_NAME) // crc32 - computed at compile time
|
djb2_hash_constexpr(WLED_RELEASE_NAME) // crc32 - computed at compile time
|
||||||
};
|
};
|
||||||
|
|
||||||
// Single reference to ensure it's not optimized away
|
// Single reference to ensure it's not optimized away
|
||||||
|
|||||||
@@ -432,17 +432,23 @@ void initServer()
|
|||||||
if(!index){
|
if(!index){
|
||||||
DEBUG_PRINTLN(F("OTA Update Start"));
|
DEBUG_PRINTLN(F("OTA Update Start"));
|
||||||
|
|
||||||
// Check if user wants to ignore release check
|
// Check if user wants to skip validation
|
||||||
bool skipValidation = request->hasParam("skipValidation", true);
|
bool skipValidation = request->hasParam("skipValidation", true);
|
||||||
|
|
||||||
// Validate OTA release compatibility using the first chunk data directly
|
// If user chose to skip validation, proceed without compatibility check
|
||||||
char errorMessage[128];
|
if (skipValidation) {
|
||||||
releaseCheckPassed = shouldAllowOTA(data, len, skipValidation, errorMessage);
|
DEBUG_PRINTLN(F("OTA validation skipped by user"));
|
||||||
|
releaseCheckPassed = true;
|
||||||
|
} else {
|
||||||
|
// Validate OTA release compatibility using the first chunk data directly
|
||||||
|
char errorMessage[128];
|
||||||
|
releaseCheckPassed = shouldAllowOTA(data, len, false, errorMessage);
|
||||||
|
|
||||||
if (!releaseCheckPassed) {
|
if (!releaseCheckPassed) {
|
||||||
DEBUG_PRINTF_P(PSTR("OTA blocked: %s\n"), errorMessage);
|
DEBUG_PRINTF_P(PSTR("OTA declined: %s\n"), errorMessage);
|
||||||
request->send(400, FPSTR(CONTENT_TYPE_PLAIN), errorMessage);
|
request->send(400, FPSTR(CONTENT_TYPE_PLAIN), errorMessage);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DEBUG_PRINTLN(F("Release check passed, starting OTA update"));
|
DEBUG_PRINTLN(F("Release check passed, starting OTA update"));
|
||||||
|
|||||||
Reference in New Issue
Block a user