Module nh_flash

Reading the running packbeam out of flash.

Description

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.

Knowing which partition

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.

Reading it

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, with crypto: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.

Function Index

available/0Whether this platform can read flash at all.
boot_partition/0The partition label AtomVM booted this application from.
label_from_path/1Turn the boot path esp32init stores into a partition label.
read_metadata/0Equivalent to read_metadata(boot_partition()).
read_metadata/1Read and describe the packbeam in the named partition.
signature_offset/1Where the signature entry begins, which is where the signed range ends.
verify_signature/2Check the signature of the archive in a partition.

Function Details

available/0

available() -> boolean()

Whether this platform can read flash at all.

False everywhere except AtomVM on an ESP32 — on a desktop there is no esp module, which is what makes the rest of the library testable off-device.

boot_partition/0

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 default esp32init uses, which is what a device that has never been switched is running.

label_from_path/1

label_from_path(Path::binary() | string()) -> binary()

Turn the boot path esp32init stores into a partition label.

It records /dev/partition/by-name/main.avm, while esp:partition_read/3 takes main.avm. The label is the last component.

read_metadata/0

read_metadata() -> {ok, map()} | {error, term()}

Equivalent to read_metadata(boot_partition()).

read_metadata/1

read_metadata(Partition::binary() | string()) -> {ok, map()} | {error, term()}

Read and describe the packbeam in the named partition.

Returns what nh_metadata:join_params/1 turns into join parameters.

signature_offset/1

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/2

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