Skip to content

Commit 70e8be6

Browse files
Copilotwillmmiles
andcommitted
Address review feedback: unify structures, fix C++11 compatibility, improve user messages
Co-authored-by: willmmiles <6540455+willmmiles@users.noreply.github.com>
1 parent f706c6c commit 70e8be6

File tree

5 files changed

+79
-64
lines changed

5 files changed

+79
-64
lines changed

pio-scripts/build_ui.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
env.Execute("npm ci")
1515

1616
# Extract the release name from build flags
17-
release_name = "Custom"
17+
release_name = None # Let cdata.js provide the default if not found
1818
build_flags = env.get("BUILD_FLAGS", [])
1919
for flag in build_flags:
2020
if 'WLED_RELEASE_NAME=' in flag:
@@ -24,9 +24,12 @@
2424
release_name = parts[1].split()[0].strip('\"\\')
2525
break
2626

27-
# Set environment variable for cdata.js to use
28-
os.environ['WLED_RELEASE_NAME'] = release_name
29-
print(f'Building web UI with release name: {release_name}')
27+
# Set environment variable for cdata.js to use (only if found)
28+
if release_name:
29+
os.environ['WLED_RELEASE_NAME'] = release_name
30+
print(f'Building web UI with release name: {release_name}')
31+
else:
32+
print('Building web UI with default release name (from cdata.js)')
3033

3134
# Call the bundling script
3235
exitCode = env.Execute("npm run build")

wled00/ota_release_check.cpp

Lines changed: 27 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,17 @@
66
#include <esp_ota_ops.h>
77
#endif
88

9-
// Same hash function used at compile time (must match wled_custom_desc.cpp)
10-
static uint32_t djb2_hash(const char* str) {
11-
uint32_t hash = 5381;
12-
while (*str) {
13-
hash = ((hash << 5) + hash) + *str++;
14-
}
15-
return hash;
16-
}
17-
18-
bool extractReleaseFromCustomDesc(const uint8_t* binaryData, size_t dataSize, char* extractedRelease) {
19-
if (!binaryData || !extractedRelease || dataSize < 64) {
9+
bool extractWledCustomDesc(const uint8_t* binaryData, size_t dataSize, wled_custom_desc_t* extractedDesc) {
10+
if (!binaryData || !extractedDesc || dataSize < 64) {
2011
return false;
2112
}
2213

23-
// Search in first 8KB only - ESP32 .rodata.wled_desc and ESP8266 .ver_number
24-
// sections appear early in binary. 8KB should be sufficient for metadata discovery
25-
// while minimizing processing time for large firmware files.
14+
// Search in first 8KB only. This range was chosen because:
15+
// - ESP32 .rodata.wled_desc sections appear early in the binary (typically within first 2-4KB)
16+
// - ESP8266 .ver_number sections also appear early (typically within first 1-2KB)
17+
// - 8KB provides ample coverage for metadata discovery while minimizing processing time
18+
// - Larger firmware files (>1MB) would take significantly longer to process with full search
19+
// - Real-world testing shows all valid metadata appears well within this range
2620
const size_t search_limit = min(dataSize, (size_t)8192);
2721

2822
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
3731
continue;
3832
}
3933

40-
// Validate hash using same algorithm as compile-time
41-
uint32_t expected_hash = djb2_hash(custom_desc->release_name);
34+
// Validate hash using runtime function
35+
uint32_t expected_hash = djb2_hash_runtime(custom_desc->release_name);
4236
if (custom_desc->crc32 != expected_hash) {
4337
DEBUG_PRINTF_P(PSTR("Found WLED structure at offset %u but hash mismatch\n"), offset);
4438
continue;
4539
}
4640

47-
// Valid structure found
48-
strncpy(extractedRelease, custom_desc->release_name, WLED_RELEASE_NAME_MAX_LEN - 1);
49-
extractedRelease[WLED_RELEASE_NAME_MAX_LEN - 1] = '\0';
41+
// Valid structure found - copy entire structure
42+
memcpy(extractedDesc, custom_desc, sizeof(wled_custom_desc_t));
5043

5144
#ifdef ESP32
52-
DEBUG_PRINTF_P(PSTR("Extracted ESP32 release name from .rodata.wled_desc section at offset %u: '%s'\n"),
53-
offset, extractedRelease);
45+
DEBUG_PRINTF_P(PSTR("Extracted ESP32 WLED structure from .rodata.wled_desc section at offset %u: '%s'\n"),
46+
offset, extractedDesc->release_name);
5447
#else
55-
DEBUG_PRINTF_P(PSTR("Extracted ESP8266 release name from .ver_number section at offset %u: '%s'\n"),
56-
offset, extractedRelease);
48+
DEBUG_PRINTF_P(PSTR("Extracted ESP8266 WLED structure from .ver_number section at offset %u: '%s'\n"),
49+
offset, extractedDesc->release_name);
5750
#endif
5851
return true;
5952
}
@@ -87,33 +80,33 @@ bool shouldAllowOTA(const uint8_t* binaryData, size_t dataSize, bool skipValidat
8780
const wled_custom_desc_t* local_desc = getWledCustomDesc();
8881
(void)local_desc; // Suppress unused variable warning
8982

90-
// If user chose to ignore release check, allow OTA
83+
// If user chose to skip validation, allow OTA immediately
9184
if (skipValidation) {
9285
DEBUG_PRINTLN(F("OTA release check bypassed by user"));
9386
return true;
9487
}
9588

96-
// Try to extract release name directly from binary data
97-
char extractedRelease[WLED_RELEASE_NAME_MAX_LEN];
98-
bool hasCustomDesc = extractReleaseFromCustomDesc(binaryData, dataSize, extractedRelease);
89+
// Try to extract WLED structure directly from binary data
90+
wled_custom_desc_t extractedDesc;
91+
bool hasCustomDesc = extractWledCustomDesc(binaryData, dataSize, &extractedDesc);
9992

10093
if (!hasCustomDesc) {
10194
// No custom description - this could be a legacy binary
10295
if (errorMessage) {
103-
strcpy(errorMessage, "Binary has no release compatibility metadata. Check 'Ignore validation' to proceed.");
96+
strcpy(errorMessage, "This firmware file is missing compatibility metadata. Enable 'Ignore firmware validation' to proceed anyway.");
10497
}
105-
DEBUG_PRINTLN(F("OTA blocked: No custom description found"));
98+
DEBUG_PRINTLN(F("OTA declined: No custom description found"));
10699
return false;
107100
}
108101

109102
// Validate compatibility using extracted release name
110-
if (!validateReleaseCompatibility(extractedRelease)) {
103+
if (!validateReleaseCompatibility(extractedDesc.release_name)) {
111104
if (errorMessage) {
112-
snprintf(errorMessage, 127, "Release mismatch: current='%s', uploaded='%s'. Check 'Ignore validation' to proceed.",
113-
releaseString, extractedRelease);
105+
snprintf(errorMessage, 127, "Firmware compatibility mismatch: current='%s', uploaded='%s'. Enable 'Ignore firmware validation' to proceed anyway.",
106+
releaseString, extractedDesc.release_name);
114107
}
115-
DEBUG_PRINTF_P(PSTR("OTA blocked: Release mismatch current='%s', uploaded='%s'\n"),
116-
releaseString, extractedRelease);
108+
DEBUG_PRINTF_P(PSTR("OTA declined: Release mismatch current='%s', uploaded='%s'\n"),
109+
releaseString, extractedDesc.release_name);
117110
return false;
118111
}
119112

wled00/ota_release_check.h

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,25 @@
1616
#define WLED_CUSTOM_DESC_VERSION 1
1717
#define WLED_RELEASE_NAME_MAX_LEN 48
1818

19+
/**
20+
* DJB2 hash function (C++11 compatible constexpr)
21+
* Used for compile-time hash computation of release names
22+
*/
23+
constexpr uint32_t djb2_hash_constexpr(const char* str, uint32_t hash = 5381) {
24+
return (*str == '\0') ? hash : djb2_hash_constexpr(str + 1, ((hash << 5) + hash) + *str);
25+
}
26+
27+
/**
28+
* Runtime DJB2 hash function for validation
29+
*/
30+
inline uint32_t djb2_hash_runtime(const char* str) {
31+
uint32_t hash = 5381;
32+
while (*str) {
33+
hash = ((hash << 5) + hash) + *str++;
34+
}
35+
return hash;
36+
}
37+
1938
/**
2039
* WLED Custom Description Structure
2140
* This structure is embedded in platform-specific sections at a fixed offset
@@ -29,13 +48,13 @@ typedef struct {
2948
} __attribute__((packed)) wled_custom_desc_t;
3049

3150
/**
32-
* Extract release name from binary using ESP-IDF custom description section
51+
* Extract WLED custom description structure from binary
3352
* @param binaryData Pointer to binary file data
3453
* @param dataSize Size of binary data in bytes
35-
* @param extractedRelease Buffer to store extracted release name (should be at least WLED_RELEASE_NAME_MAX_LEN bytes)
36-
* @return true if release name was found and extracted, false otherwise
54+
* @param extractedDesc Buffer to store extracted custom description structure
55+
* @return true if structure was found and extracted, false otherwise
3756
*/
38-
bool extractReleaseFromCustomDesc(const uint8_t* binaryData, size_t dataSize, char* extractedRelease);
57+
bool extractWledCustomDesc(const uint8_t* binaryData, size_t dataSize, wled_custom_desc_t* extractedDesc);
3958

4059
/**
4160
* Validate if extracted release name matches current release

wled00/wled_custom_desc.cpp

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,19 @@
11
#include "ota_release_check.h"
22
#include "wled.h"
33

4-
// Simple compile-time hash function for release name validation
5-
constexpr uint32_t djb2_hash(const char* str) {
6-
uint32_t hash = 5381;
7-
while (*str) {
8-
hash = ((hash << 5) + hash) + *str++;
9-
}
10-
return hash;
11-
}
12-
13-
// Single structure definition for both platforms
4+
// Platform-specific section definition
145
#ifdef ESP32
15-
const wled_custom_desc_t __attribute__((section(".rodata.wled_desc"))) wled_custom_description = {
6+
#define WLED_CUSTOM_DESC_SECTION ".rodata.wled_desc"
167
#elif defined(ESP8266)
17-
const wled_custom_desc_t __attribute__((section(".ver_number"))) wled_custom_description = {
8+
#define WLED_CUSTOM_DESC_SECTION ".ver_number"
189
#endif
10+
11+
// Single structure definition for both platforms
12+
const wled_custom_desc_t __attribute__((section(WLED_CUSTOM_DESC_SECTION))) wled_custom_description = {
1913
WLED_CUSTOM_DESC_MAGIC, // magic
2014
WLED_CUSTOM_DESC_VERSION, // version
2115
WLED_RELEASE_NAME, // release_name
22-
djb2_hash(WLED_RELEASE_NAME) // crc32 - computed at compile time
16+
djb2_hash_constexpr(WLED_RELEASE_NAME) // crc32 - computed at compile time
2317
};
2418

2519
// Single reference to ensure it's not optimized away

wled00/wled_server.cpp

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -432,17 +432,23 @@ void initServer()
432432
if(!index){
433433
DEBUG_PRINTLN(F("OTA Update Start"));
434434

435-
// Check if user wants to ignore release check
435+
// Check if user wants to skip validation
436436
bool skipValidation = request->hasParam("skipValidation", true);
437437

438-
// Validate OTA release compatibility using the first chunk data directly
439-
char errorMessage[128];
440-
releaseCheckPassed = shouldAllowOTA(data, len, skipValidation, errorMessage);
441-
442-
if (!releaseCheckPassed) {
443-
DEBUG_PRINTF_P(PSTR("OTA blocked: %s\n"), errorMessage);
444-
request->send(400, FPSTR(CONTENT_TYPE_PLAIN), errorMessage);
445-
return;
438+
// If user chose to skip validation, proceed without compatibility check
439+
if (skipValidation) {
440+
DEBUG_PRINTLN(F("OTA validation skipped by user"));
441+
releaseCheckPassed = true;
442+
} else {
443+
// Validate OTA release compatibility using the first chunk data directly
444+
char errorMessage[128];
445+
releaseCheckPassed = shouldAllowOTA(data, len, false, errorMessage);
446+
447+
if (!releaseCheckPassed) {
448+
DEBUG_PRINTF_P(PSTR("OTA declined: %s\n"), errorMessage);
449+
request->send(400, FPSTR(CONTENT_TYPE_PLAIN), errorMessage);
450+
return;
451+
}
446452
}
447453

448454
DEBUG_PRINTLN(F("Release check passed, starting OTA update"));

0 commit comments

Comments
 (0)