1616#endif
1717
1818constexpr uint32_t WLED_CUSTOM_DESC_MAGIC = 0x57535453 ; // "WSTS" (WLED System Tag Structure)
19- constexpr uint32_t WLED_CUSTOM_DESC_VERSION = 1 ;
19+ constexpr uint32_t WLED_CUSTOM_DESC_VERSION = 2 ; // v1 - original PR; v2 - "safe to update from" version
2020
2121// Compile-time validation that release name doesn't exceed maximum length
2222static_assert (sizeof (WLED_RELEASE_NAME) <= WLED_RELEASE_NAME_MAX_LEN,
@@ -59,6 +59,7 @@ const wled_metadata_t __attribute__((section(BUILD_METADATA_SECTION))) WLED_BUIL
5959 TOSTRING (WLED_VERSION),
6060 WLED_RELEASE_NAME, // release_name
6161 std::integral_constant<uint32_t , djb2_hash_constexpr (WLED_RELEASE_NAME)>::value, // hash - computed at compile time; integral_constant enforces this
62+ { 0 , 0 , 0 }, // All other platforms can update safely
6263};
6364
6465static const char repoString_s[] PROGMEM = WLED_REPO;
@@ -80,40 +81,47 @@ const __FlashStringHelper* brandString = FPSTR(brandString_s);
8081 * @return true if structure was found and extracted, false otherwise
8182 */
8283bool findWledMetadata (const uint8_t * binaryData, size_t dataSize, wled_metadata_t * extractedDesc) {
83- if (!binaryData || !extractedDesc || dataSize < sizeof (wled_metadata_t )) {
84- return false ;
85- }
84+ if (!binaryData || !extractedDesc || dataSize < sizeof (wled_metadata_t )) {
85+ return false ;
86+ }
8687
87- for (size_t offset = 0 ; offset <= dataSize - sizeof (wled_metadata_t ); offset++) {
88- const wled_metadata_t * custom_desc = (const wled_metadata_t *)(binaryData + offset);
88+ for (size_t offset = 0 ; offset <= dataSize - sizeof (wled_metadata_t ); offset++) {
89+ if ((binaryData[offset]) == static_cast <char >(WLED_CUSTOM_DESC_MAGIC)) {
90+ // First byte matched; check next in an alignment-safe way
91+ uint32_t data_magic;
92+ memcpy (&data_magic, binaryData + offset, sizeof (data_magic));
93+
94+ // Check for magic number
95+ if (data_magic == WLED_CUSTOM_DESC_MAGIC) {
96+ wled_metadata_t candidate;
97+ memcpy (&candidate, binaryData + offset, sizeof (candidate));
98+
99+ // Found potential match, validate version
100+ if (candidate.desc_version > WLED_CUSTOM_DESC_VERSION) {
101+ DEBUG_PRINTF_P (PSTR (" Found WLED structure at offset %u but version mismatch: %u\n " ),
102+ offset, candidate.desc_version );
103+ continue ;
104+ }
89105
90- // Check for magic number
91- if (custom_desc->magic == WLED_CUSTOM_DESC_MAGIC) {
92- // Found potential match, validate version
93- if (custom_desc->desc_version != WLED_CUSTOM_DESC_VERSION) {
94- DEBUG_PRINTF_P (PSTR (" Found WLED structure at offset %u but version mismatch: %u\n " ),
95- offset, custom_desc->desc_version );
96- continue ;
97- }
98-
99- // Validate hash using runtime function
100- uint32_t expected_hash = djb2_hash_runtime (custom_desc->release_name );
101- if (custom_desc->hash != expected_hash) {
102- DEBUG_PRINTF_P (PSTR (" Found WLED structure at offset %u but hash mismatch\n " ), offset);
103- continue ;
104- }
105-
106- // Valid structure found - copy entire structure
107- memcpy (extractedDesc, custom_desc, sizeof (wled_metadata_t ));
108-
109- DEBUG_PRINTF_P (PSTR (" Extracted WLED structure at offset %u: '%s'\n " ),
110- offset, extractedDesc->release_name );
111- return true ;
106+ // Validate hash using runtime function
107+ uint32_t expected_hash = djb2_hash_runtime (candidate.release_name );
108+ if (candidate.hash != expected_hash) {
109+ DEBUG_PRINTF_P (PSTR (" Found WLED structure at offset %u but hash mismatch\n " ), offset);
110+ continue ;
112111 }
112+
113+ // Valid structure found - copy entire structure
114+ *extractedDesc = candidate;
115+
116+ DEBUG_PRINTF_P (PSTR (" Extracted WLED structure at offset %u: '%s'\n " ),
117+ offset, extractedDesc->release_name );
118+ return true ;
119+ }
113120 }
114-
115- DEBUG_PRINTLN (F (" No WLED custom description found in binary" ));
116- return false ;
121+ }
122+
123+ DEBUG_PRINTLN (F (" No WLED custom description found in binary" ));
124+ return false ;
117125}
118126
119127
@@ -144,13 +152,43 @@ bool shouldAllowOTA(const wled_metadata_t& firmwareDescription, char* errorMessa
144152
145153 if (strncmp_P (safeFirmwareRelease, releaseString, WLED_RELEASE_NAME_MAX_LEN) != 0 ) {
146154 if (errorMessage && errorMessageLen > 0 ) {
147- snprintf_P (errorMessage, errorMessageLen, PSTR (" Firmware compatibility mismatch: current='%s', uploaded='%s'." ),
155+ snprintf_P (errorMessage, errorMessageLen, PSTR (" Firmware release name mismatch: current='%s', uploaded='%s'." ),
148156 releaseString, safeFirmwareRelease);
149157 errorMessage[errorMessageLen - 1 ] = ' \0 ' ; // Ensure null termination
150158 }
151159 return false ;
152160 }
153161
162+ if (firmwareDescription.desc_version > 1 ) {
163+ // Add safe version check
164+ // Parse our version (x.y.z) and compare it to the "safe version" array
165+ const char * our_version = versionString;
166+ for (unsigned v_index = 0 ; v_index < 3 ; ++v_index) {
167+ char * our_version_end = nullptr ;
168+ long our_v_parsed = strtol (our_version, &our_version_end, 10 );
169+ if (!our_version_end || (our_version_end == our_version)) {
170+ // We were built with a malformed version string
171+ // We blame the integrator and attempt the update anyways - nothing the user can do to fix this
172+ break ;
173+ }
174+
175+ if (firmwareDescription.safe_update_version [v_index] > our_v_parsed) {
176+ if (errorMessage && errorMessageLen > 0 ) {
177+ snprintf_P (errorMessage, errorMessageLen, PSTR (" Cannot update from this version: requires at least %d.%d.%d, current='%s'." ),
178+ firmwareDescription.safe_update_version [0 ], firmwareDescription.safe_update_version [1 ], firmwareDescription.safe_update_version [2 ],
179+ versionString);
180+ errorMessage[errorMessageLen - 1 ] = ' \0 ' ; // Ensure null termination
181+ }
182+ return false ;
183+ } else if (firmwareDescription.safe_update_version [v_index] < our_v_parsed) {
184+ break ; // no need to check the other components
185+ }
186+
187+ if (*our_version_end == ' .' ) ++our_version_end;
188+ our_version = our_version_end;
189+ }
190+ }
191+
154192 // TODO: additional checks go here
155193
156194 return true ;
0 commit comments