Reading the running packbeam out of flash.
The firmware NervesHub manages for an AtomVM device is the packbeam, and the packbeam is in a partition, so a device can report what it is actually running rather than a constant it was compiled with.
AtomVM's esp32init records where it booted from in NVS, under atomvm /
boot_path, defaulting to /dev/partition/by-name/main.avm. So unlike
ESP-IDF — where a device cannot ask which of ota_0/ota_1 is live, because
AtomVM exposes no esp_ota_get_running_partition() — an AtomVM device can
read the answer, and stays correct after an update that moved it.
boot_partition/0 reads it. An application may still name a partition
explicitly, but it no longer has to.
A partition is much larger than the archive written into it, and the digest NervesHub matches covers the archive alone, so this walks entry by entry to find where the archive ends rather than reading the partition whole.
The walk hashes as it goes, withcrypto:hash_init/1 and friends, and keeps
only the one entry it needs. Nothing here holds the archive in memory, which
matters on a device with a few hundred kilobytes of it.
| available/0 | Whether this platform can read flash at all. |
| boot_partition/0 | The partition label AtomVM booted this application from. |
| label_from_path/1 | Turn the boot path esp32init stores into a partition label. |
| read_metadata/0 | Equivalent to read_metadata(boot_partition()).
|
| read_metadata/1 | Read and describe the packbeam in the named partition. |
| signature_offset/1 | Where the signature entry begins, which is where the signed range ends. |
| verify_signature/2 | Check the signature of the archive in a partition. |
available() -> boolean()
Whether this platform can read flash at all.
False everywhere except AtomVM on an ESP32 — on a desktop there is noesp
module, which is what makes the rest of the library testable off-device.
boot_partition() -> binary()
The partition label AtomVM booted this application from.
Reads NVS rather than guessing, so it stays right after an update that switched slots. Falls back to the same defaultesp32init uses, which is
what a device that has never been switched is running.
label_from_path(Path::binary() | string()) -> binary()
Turn the boot path esp32init stores into a partition label.
/dev/partition/by-name/main.avm, while esp:partition_read/3
takes main.avm. The label is the last component.
read_metadata() -> {ok, map()} | {error, term()}
Equivalent to read_metadata(boot_partition()).
read_metadata(Partition::binary() | string()) -> {ok, map()} | {error, term()}
Read and describe the packbeam in the named partition.
Returns whatnh_metadata:join_params/1 turns into join parameters.
signature_offset(Partition::binary()) -> {ok, non_neg_integer()} | none | {error, term()}
Where the signature entry begins, which is where the signed range ends.
Exposed because it is the number both ends have to agree on, and a mismatch is otherwise invisible from either side.verify_signature(Partition::binary(), PublicKeys::[binary()]) -> {ok, binary()} | {error, term()}
Check the signature of the archive in a partition.
The signed range is every byte before the signature entry begins, and it is
mapped rather than read: esp:partition_mmap/3 hands back a binary that
points at flash through the MMU, costing a few words of heap instead of the
size of the archive.
That is the only way this works on a device. Ed25519 signs a message rather than a digest and there is no incremental verify, so the whole signed range has to be addressable at once -- and an archive can be most of a megabyte while the heap is a few hundred kilobytes.
{error, unsigned} when the archive carries no signature entry, which the
caller decides what to do with.
Generated by EDoc