diff --git a/scripts/generate-font-manifest.py b/scripts/generate-font-manifest.py index 695bb99b..e962c162 100755 --- a/scripts/generate-font-manifest.py +++ b/scripts/generate-font-manifest.py @@ -21,6 +21,7 @@ import json import os import struct import sys +import zlib from pathlib import Path # Import canonical version constants from the shared file in lib/EpdFont/scripts/ @@ -115,6 +116,15 @@ def parse_filename(filename: str) -> tuple[str, str] | None: return family, size_str +def compute_crc32(filepath: Path) -> int: + """Compute CRC32 of a file, matching esp_rom_crc32_le(0xFFFFFFFF, ...) ^ 0xFFFFFFFF.""" + crc = 0 + with open(filepath, "rb") as f: + for chunk in iter(lambda: f.read(65536), b""): + crc = zlib.crc32(chunk, crc) + return crc & 0xFFFFFFFF + + def scan_cpfont_files(input_dir: Path) -> dict[str, list[Path]]: """Scan input directory for .cpfont files, grouped by family name. @@ -164,6 +174,7 @@ def build_manifest( { "name": filepath.name, "size": filepath.stat().st_size, + "crc32": compute_crc32(filepath), } ) diff --git a/src/activities/settings/FontDownloadActivity.cpp b/src/activities/settings/FontDownloadActivity.cpp index e7419c0e..ce18ce67 100644 --- a/src/activities/settings/FontDownloadActivity.cpp +++ b/src/activities/settings/FontDownloadActivity.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "MappedInputManager.h" #include "SdCardFontGlobals.h" @@ -123,6 +124,14 @@ bool FontDownloadActivity::fetchAndParseManifest() { ManifestFile file; file.name = fileObj["name"] | ""; file.size = fileObj["size"] | 0; + + if (!fileObj["crc32"].is()) { + LOG_ERR("FONT", "Malformed manifest file entry: missing or invalid crc32 for %s", file.name.c_str()); + errorMessage_ = "Invalid font manifest"; + return false; + } + file.crc32 = fileObj["crc32"].as(); + family.totalSize += file.size; family.files.push_back(std::move(file)); } @@ -181,6 +190,24 @@ size_t FontDownloadActivity::totalUninstalledSize() const { return total; } +// Standard CRC32 matching zlib/Python zlib.crc32(). +bool FontDownloadActivity::computeFileCrc32(const char* path, uint32_t& outCrc) { + FsFile f; + if (!Storage.openFileForRead("FONT", path, f)) { + return false; + } + constexpr size_t BUF_SIZE = 128; + uint8_t buf[BUF_SIZE]; + uint32_t crc = 0; + while (f.available()) { + const int n = f.read(buf, BUF_SIZE); + if (n <= 0) break; + crc = esp_rom_crc32_le(crc, buf, static_cast(n)); + } + outCrc = crc; + return true; +} + void FontDownloadActivity::downloadFamily(ManifestFamily& family) { { RenderLock lock(*this); @@ -233,6 +260,29 @@ void FontDownloadActivity::downloadFamily(ManifestFamily& family) { return; } + uint32_t actualCrc = 0; + if (!computeFileCrc32(destPath, actualCrc)) { + LOG_ERR("FONT", "Failed to open file for CRC check: %s", destPath); + fontInstaller_.deleteFamily(family.name.c_str()); + family.installed = false; + family.hasUpdate = false; + RenderLock lock(*this); + state_ = ERROR; + errorMessage_ = "Failed to compute checksum: " + file.name; + return; + } + if (actualCrc != file.crc32) { + LOG_ERR("FONT", "CRC32 mismatch for %s: got %08x expected %08x", file.name.c_str(), actualCrc, file.crc32); + fontInstaller_.deleteFamily(family.name.c_str()); + family.installed = false; + family.hasUpdate = false; + RenderLock lock(*this); + state_ = ERROR; + errorMessage_ = "Checksum mismatch: " + file.name; + return; + } + LOG_DBG("FONT", "Downloaded %s (size=%zu crc32=%08x)", file.name.c_str(), file.size, actualCrc); + if (!fontInstaller_.validateCpfontFile(destPath)) { LOG_ERR("FONT", "Invalid .cpfont: %s", destPath); fontInstaller_.deleteFamily(family.name.c_str()); diff --git a/src/activities/settings/FontDownloadActivity.h b/src/activities/settings/FontDownloadActivity.h index 1b4373c4..16d52fa9 100644 --- a/src/activities/settings/FontDownloadActivity.h +++ b/src/activities/settings/FontDownloadActivity.h @@ -50,6 +50,7 @@ class FontDownloadActivity : public Activity { struct ManifestFile { std::string name; size_t size = 0; + uint32_t crc32 = 0; }; struct ManifestFamily { @@ -83,6 +84,7 @@ class FontDownloadActivity : public Activity { bool fetchAndParseManifest(); void downloadFamily(ManifestFamily& family); void downloadAll(); + static bool computeFileCrc32(const char* path, uint32_t& outCrc); bool isDownloadAllSelected() const { return selectedIndex_ == 0 && !families_.empty(); } int familyIndexFromList(int listIndex) const { return listIndex - 1; } int listItemCount() const { return families_.empty() ? 0 : static_cast(families_.size()) + 1; }