11#include " ota_release_check.h"
22#include " wled.h"
33
4- // Maximum size to scan in binary (we don't need to scan the entire file)
5- #define MAX_SCAN_SIZE 32768
6-
7- /* *
8- * Find a string in binary data
9- */
10- static int findStringInBinary (const uint8_t * data, size_t dataSize, const char * pattern) {
11- size_t patternLen = strlen (pattern);
12- if (patternLen == 0 || patternLen > dataSize) return -1 ;
13-
14- for (size_t i = 0 ; i <= dataSize - patternLen; i++) {
15- if (memcmp (data + i, pattern, patternLen) == 0 ) {
16- return i;
17- }
4+ bool extractMetadataHeader (uint8_t * binaryData, size_t dataSize, char * extractedRelease, size_t * actualBinarySize) {
5+ if (!binaryData || !extractedRelease || !actualBinarySize || dataSize < WLED_META_HEADER_SIZE) {
6+ *actualBinarySize = dataSize;
7+ return false ;
188 }
19- return -1 ;
20- }
219
22- /* *
23- * Check if a string looks like a valid WLED release name
24- */
25- static bool isValidReleaseNameFormat (const char * name, size_t len) {
26- if (len < 3 || len > 63 ) return false ;
27-
28- // Should contain at least one letter and be mostly alphanumeric with underscores/dashes
29- bool hasLetter = false ;
30- for (size_t i = 0 ; i < len; i++) {
31- char c = name[i];
32- if (isalpha (c)) {
33- hasLetter = true ;
34- } else if (!isdigit (c) && c != ' _' && c != ' -' ) {
35- return false ; // Invalid character
36- }
10+ // Check if the binary starts with our metadata header
11+ if (memcmp (binaryData, WLED_META_PREFIX, strlen (WLED_META_PREFIX)) != 0 ) {
12+ // No metadata header found, this is a legacy binary
13+ *actualBinarySize = dataSize;
14+ DEBUG_PRINTLN (F (" No WLED metadata header found - legacy binary" ));
15+ return false ;
3716 }
38-
39- return hasLetter; // Must have at least one letter
40- }
4117
42- /* *
43- * Extract release name by searching for any reasonable string that could be a release name
44- * This method is very permissive to handle custom builds and unknown release formats
45- */
46- static bool extractByGenericStringSearch (const uint8_t * data, size_t dataSize, char * extractedRelease) {
47- // Search for null-terminated strings that could be release names
48- char bestCandidate[64 ] = " " ;
49- int bestScore = -1 ;
50-
51- for (size_t i = 0 ; i < dataSize - 4 ; i++) {
52- // Look for potential start of a string (printable character)
53- if (isalpha (data[i])) {
54- // Find the end of this string (null terminator)
55- size_t j = i;
56- while (j < dataSize && data[j] != 0 ) {
57- j++;
58- }
59-
60- if (j < dataSize) { // Found null terminator
61- size_t len = j - i;
62- if (len >= 3 && len <= 63 ) { // reasonable length for a release name
63- char candidate[64 ];
64- strncpy (candidate, (const char *)(data + i), len);
65- candidate[len] = ' \0 ' ;
66-
67- // Check if this looks like a valid release name format
68- if (isValidReleaseNameFormat (candidate, len)) {
69- // Score candidates to find the most likely release name
70- int score = 0 ;
71-
72- // High score for common patterns
73- if (strstr (candidate, " ESP" ) != NULL ) score += 100 ;
74- if (strstr (candidate, " WLED" ) != NULL ) score += 100 ;
75- if (strstr (candidate, " Custom" ) != NULL ) score += 50 ;
76- if (strstr (candidate, " Build" ) != NULL ) score += 30 ;
77-
78- // Medium score for reasonable structure
79- if (len >= 5 && len <= 32 ) score += 20 ; // reasonable length
80- if (strchr (candidate, ' _' ) != NULL ) score += 10 ; // contains underscore (common in release names)
81- if (strchr (candidate, ' -' ) != NULL ) score += 10 ; // contains dash (common in release names)
82-
83- // Basic score for any valid format
84- if (score == 0 ) score = 5 ; // Any valid format gets minimum score
85-
86- if (score > bestScore) {
87- bestScore = score;
88- strcpy (bestCandidate, candidate);
89- }
90- }
91- }
92- }
93- }
94- }
95-
96- if (bestScore > 0 ) {
97- strcpy (extractedRelease, bestCandidate);
98- DEBUG_PRINTF_P (PSTR (" Found release name by generic search (score %d): %s\n " ), bestScore, extractedRelease);
99- return true ;
100- }
101-
102- return false ;
103- }
18+ DEBUG_PRINTLN (F (" Found WLED metadata header" ));
10419
105- bool extractReleaseNameFromBinary (const uint8_t * binaryData, size_t dataSize, char * extractedRelease) {
106- if (!binaryData || !extractedRelease || dataSize == 0 ) {
107- return false ;
108- }
20+ // Extract release name from header
21+ const char * releaseStart = (const char *)(binaryData + strlen (WLED_META_PREFIX));
22+ size_t maxReleaseLen = WLED_META_HEADER_SIZE - strlen (WLED_META_PREFIX) - 1 ;
10923
110- // Limit scan size to avoid performance issues with large binaries
111- size_t scanSize = (dataSize > MAX_SCAN_SIZE) ? MAX_SCAN_SIZE : dataSize;
112-
113- // First, try to find the exact current release string in the binary
114- // This is the most reliable method since we know what we're looking for
115- int pos = findStringInBinary (binaryData, scanSize, releaseString);
116- if (pos >= 0 ) {
117- // Verify it's properly null-terminated
118- size_t releaseLen = strlen (releaseString);
119- if (pos + releaseLen < scanSize && binaryData[pos + releaseLen] == 0 ) {
120- strcpy (extractedRelease, releaseString);
121- DEBUG_PRINTF_P (PSTR (" Found exact current release string in binary: %s\n " ), extractedRelease);
122- return true ;
123- }
124- }
125-
126- // Fallback: Search for any string that looks like a release name
127- // This handles the case where the binary has a different but valid release name
128- if (extractByGenericStringSearch (binaryData, scanSize, extractedRelease)) {
129- return true ;
130- }
131-
132- DEBUG_PRINTLN (F (" Could not extract release name from binary" ));
133- return false ;
24+ // Copy release name (it should be null-terminated within the header)
25+ strncpy (extractedRelease, releaseStart, maxReleaseLen);
26+ extractedRelease[maxReleaseLen] = ' \0 ' ; // Ensure null termination
27+
28+ // Remove metadata header by shifting binary data
29+ size_t firmwareSize = dataSize - WLED_META_HEADER_SIZE;
30+ memmove (binaryData, binaryData + WLED_META_HEADER_SIZE, firmwareSize);
31+ *actualBinarySize = firmwareSize;
32+
33+ DEBUG_PRINTF_P (PSTR (" Extracted release name from metadata: '%s', firmware size: %zu bytes\n " ),
34+ extractedRelease, firmwareSize);
35+
36+ return true ;
13437}
13538
13639bool validateReleaseCompatibility (const char * extractedRelease) {
@@ -147,29 +50,41 @@ bool validateReleaseCompatibility(const char* extractedRelease) {
14750 return match;
14851}
14952
150- bool shouldAllowOTA (const uint8_t * binaryData, size_t dataSize, bool ignoreReleaseCheck, char * errorMessage) {
53+ bool shouldAllowOTA (uint8_t * binaryData, size_t dataSize, bool ignoreReleaseCheck, char * errorMessage, size_t * actualBinarySize ) {
15154 // Clear error message
15255 if (errorMessage) {
15356 errorMessage[0 ] = ' \0 ' ;
15457 }
15558
59+ // Initialize actual binary size to full size by default
60+ if (actualBinarySize) {
61+ *actualBinarySize = dataSize;
62+ }
63+
15664 // If user chose to ignore release check, allow OTA
15765 if (ignoreReleaseCheck) {
15866 DEBUG_PRINTLN (F (" OTA release check bypassed by user" ));
67+ // Still need to extract metadata header if present to get clean binary
68+ char dummyRelease[64 ];
69+ extractMetadataHeader (binaryData, dataSize, dummyRelease, actualBinarySize);
15970 return true ;
16071 }
161-
162- // Try to extract release name from binary
72+
73+ // Try to extract metadata header
16374 char extractedRelease[64 ];
164- if (!extractReleaseNameFromBinary (binaryData, dataSize, extractedRelease)) {
75+ bool hasMetadata = extractMetadataHeader (binaryData, dataSize, extractedRelease, actualBinarySize);
76+
77+ if (!hasMetadata) {
78+ // No metadata header - this could be a legacy binary or a binary without our metadata
79+ // We cannot determine compatibility for such binaries
16580 if (errorMessage) {
166- strcpy (errorMessage, " Could not determine release type of uploaded file . Check 'Ignore release name check' to proceed." );
81+ strcpy (errorMessage, " Binary has no release compatibility metadata . Check 'Ignore release name check' to proceed." );
16782 }
168- DEBUG_PRINTLN (F (" OTA blocked: Could not extract release name " ));
83+ DEBUG_PRINTLN (F (" OTA blocked: No metadata header found " ));
16984 return false ;
17085 }
171-
172- // Validate compatibility
86+
87+ // Validate compatibility using extracted release name
17388 if (!validateReleaseCompatibility (extractedRelease)) {
17489 if (errorMessage) {
17590 snprintf (errorMessage, 127 , " Release mismatch: current='%s', uploaded='%s'. Check 'Ignore release name check' to proceed." ,
@@ -179,7 +94,7 @@ bool shouldAllowOTA(const uint8_t* binaryData, size_t dataSize, bool ignoreRelea
17994 releaseString, extractedRelease);
18095 return false ;
18196 }
182-
97+
18398 DEBUG_PRINTLN (F (" OTA allowed: Release names match" ));
18499 return true ;
185100}
0 commit comments