Files
MeshCore/tools/mota/README.md
T

88 lines
5.2 KiB
Markdown

# `tools/mota/` - OTA Python reference library & build/test tooling
The Python side of MeshCore's `.mota` OTA system. It is the **reference implementation** of the wire
spec ([`docs/ota_protocol.md`](../../docs/ota_protocol.md)) and the **build + test infrastructure** - it is
no longer a user-facing CLI.
> **Want to build / verify / inspect / serve `.mota` from the command line?** Use the standalone
> [`motatool`](https://github.com/vk496/motatool) Rust CLI (its own repository). It supersedes the old
> `mota.py` / `mota_seeder.py` (now removed) and produces byte-identical containers. The files here are
> the spec oracle and the firmware build/test glue.
## Setup
Use one pipx-managed `detools` environment. `cryptography` (Ed25519) and
`intelhex` (nRF52 `.hex` handling) are injected into that same environment;
they are not separate pipx applications.
```bash
pipx install detools
pipx inject detools cryptography intelhex
DETOOLS_PYTHON="$(pipx environment --value PIPX_LOCAL_VENVS)/detools/bin/python"
```
## Files
| File | What |
|---|---|
| `motalib.py` | Core logic: multihash, EndF, merkle tree+proofs, manifest/container build/parse/verify. The **reference implementation** of the spec and the unit-test oracle. Imported by everything below. |
| `pio_endf.py` | **PlatformIO post-build hook** (wired in `platformio.ini`) that injects the `EndF` self-identity trailer into the flashed firmware (`-D ENABLE_OTA`). |
| `gen_vectors.py` | Generates `test/test_ota/mota_vectors.h` - the cross-check vectors the native C++ tests run against. |
| `gen_targets.py` | Generates `src/helpers/ota/OtaTargets.h` - the `target_id -> env-name` table (every resolved `ENABLE_OTA` env plus qualified build.sh-only release aliases). Shared by the firmware and `motatool` so a node can name a target seen over the air without sending the string. Regenerate when the OTA env or release-alias set changes. |
| `test_mota.py` | Unit tests for `motalib` (run directly or via pytest). |
## Tests
```bash
"$DETOOLS_PYTHON" tools/mota/test_mota.py # EndF, merkle+proofs, v2 apps/v3 nRF52 bootloader,
# signing, tamper detection, approval enforcement
"$DETOOLS_PYTHON" tools/mota/gen_vectors.py # regenerate the native-test cross-check vectors
"$DETOOLS_PYTHON" tools/mota/gen_targets.py # regenerate src/helpers/ota/OtaTargets.h (needs `pio`)
```
## `EndF` build integration
`EndF` must live in the **flashed** firmware (not just inside the `.mota`), because a node serves its own
firmware and matches a delta's `base_hash` against its own `EndF`. Wiring (handled by `pio_endf.py`):
- **ESP32 / RP2040** (emit `firmware.bin`): `post:tools/mota/pio_endf.py` in the env's `extra_scripts` plus
`-D ENABLE_OTA=1`. The hook appends the 56-byte `EndF` (with `target_id`/`fw_version`/`hw_id`) to the app
`.bin` before merge.
- **nRF52 / STM32** (emit `.hex` -> `.uf2`): the same hook rewrites the `.hex` with the trailer at the image
end. The byte logic is `motalib.ensure_endf`, used everywhere.
`target_id` = `sha2-256:4(pio_env_name)`, `hw_id` = explicit `-D MOTA_HW_ID` or a role-stripped hardware
family derived from the environment name, and `fw_version` = parsed from `FIRMWARE_VERSION`. A node (and
`motatool`, reading the firmware's `EndF`) therefore auto-discovers identity without relying on filenames.
## Privileged nRF52 bootloader reference builder
Ordinary calls to `build_manifest()` continue to emit v2 application packages.
New application packages default to 2048-byte blocks; pass 1024 explicitly when
targeting a deployed receiver that predates the extended transport descriptor.
Passing `bootloader=True` is a deliberately narrow reference-only path: it
requires a nonzero version, a signed exact 40 KiB OTAFIX region, 1024-byte
blocks, full codec, zero base hash, a sane vector table, an exact embedded
manifest/CRC identity, and an ABI-3 marker retaining both application codecs
(`FULL|INPLACE`, mask `0x0005`), the selected storage, and boot-update
capabilities. Deployed XIAO packages retain their
`XIAO_BL_28860044`/`XIAO_BL_28860045` IDs. Generic packages derive the padded
`NRF_BL_<BOARD_ID>_<DEVICE_NAME>` ID and collision-checked wire target from the
full embedded manifest pair. Parsing and `verify()` repeat those gates; magic
literals that are not complete valid structures are skipped.
Generic parsing remains available for candidate inspection, but the signing
builder accepts only identities in the qualified inventory. That inventory
currently covers the shared-internal nRF52840 targets, the two deployed XIAO
raw-QSPI identities, and the exact MeshTower V2 identity whose candidate
marker may select either its internal (`0x0A`) or microSD (`0x09`) application
layout. The test suite verifies those boot target IDs are unique and disjoint
from the generated application target table.
This library does not authorize a device update. A capable node will only arm
such a v3 package through the exact manual confirmation described in
[`docs/ota_nrf52_bootloader_update.md`](../../docs/ota_nrf52_bootloader_update.md).
The `tools/lora_ota` runner accepts verified bootloader packages only with
`--no-install`, checks the destination's bootloader-specific identity and
capabilities, and leaves installation to that explicit manual confirmation.