|
STAR
Cal STAR — firmware, DAQ server, and engine design
|
Every board puts a 32-byte firmware_hash in its BoardHeartbeatPacket, once a second. It comes from esp_partition_get_sha256() (see firmware_hash.h).
That value is the SHA-256 ESP-IDF appends to the app image — not a hash of firmware.bin as a file. Our images are built with the hash appended (hash_appended = 1 in the image header), and in that case IDF does this:
— bootloader_common_get_sha256_of_partition(), ESP-IDF v4.4
It returns the appended bytes, having recomputed them once to check. It does not hash the file.
That folds the appended digest into its own input. For a real PT image the two differ completely:
| what the board reports | 5f2885e2247f089279bd77af2b10e82722b1deb52b6d5c884cb1ceb0008ee017 |
| sha256(firmware.bin) | 6249151830f37675890b2bd2ffa3d1670d5161319ecea0a6af82027ae565a298 |
An earlier version of this document recommended the wrong one.
firmware_digest.py walks the image's segment table to find where the image really ends (a .bin may carry padding past it, in which case the digest is not the last 32 bytes of the file), then verifies the appended digest against the content it covers — the same check IDF makes — so it fails loudly rather than returning a plausible wrong hash.
CI records every image it builds on main to the firmware-registry branch, one file per distinct image:
The repo is public, so the records are also readable over plain HTTP: raw.githubusercontent.com/calstar/STAR/firmware-registry/<board>/<digest>.json
A digest that is not in the registry was never built by CI on main — a local build, a branch, or an image from before the registry existed.
ESP_ERROR_CHECK, assert and the ESP log macros expand __FILE__, and the compiler sees absolute paths, so the image used to carry strings like
inside the hashed bytes — the username, the home directory and the checkout path. The same commit built by two people produced two different digests, so the digest identified the machine rather than the firmware. firmware/tools/reproducible_build.py maps those prefixes away with -ffile-prefix-map, and firmware CI rebuilds every image from a second path and fails if the digests differ.
Still not reproducible across a toolchain change: platformio-common.ini pins no version for platform = espressif32, so a newer platform resolves to different bytes with no source change. Each record stores the platform, framework and toolchain versions it was built with, so that reads as a toolchain bump rather than a corrupt registry.