feat(operation): verify X-Image-Sha256 before painting the panel

Pairs with the server-side header. After streaming the response body to
LittleFS, hash the file with mbedtls/sha256 (hardware-accelerated on
ESP32-S3) and compare against the server's claim. On mismatch:

- Don't update NVS_KEY_IMG_ID, so the next poll reports the old id and
  the server sends 200 again with fresh bytes (natural retry, no extra
  HTTP round-trip in this cycle).
- Don't draw — panel keeps whatever was up before, no garbage on the
  e-ink.
- Raise NVS_KEY_ERR_BORDER so the next healthy 304 paints a clean
  recovery frame with the sync-fail border.

Verification is skipped when the header is absent, so the firmware
stays compatible with any server that hasn't deployed the matching
header yet. mbedtls compiles into a native-test no-op stub (returns
empty hex), so existing native tests don't need a SHA implementation.

Two new tests: FW-17a (mismatch path) and FW-17b (missing header
backward compat). Mock String now has equalsIgnoreCase so the new
comparison compiles in native-test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-06 19:43:02 -04:00
parent 21871179bd
commit 27d01057e4
3 changed files with 133 additions and 21 deletions
+8
View File
@@ -46,6 +46,14 @@ struct String {
for (char& c : _s) c = (char)toupper((unsigned char)c);
}
bool equalsIgnoreCase(const String& o) const {
if (_s.size() != o._s.size()) return false;
for (size_t i = 0; i < _s.size(); i++) {
if (tolower((unsigned char)_s[i]) != tolower((unsigned char)o._s[i])) return false;
}
return true;
}
bool operator==(const String& o) const { return _s == o._s; }
bool operator==(const char* o) const { return _s == o; }
bool operator!=(const String& o) const { return _s != o._s; }