diff --git a/.gitignore b/.gitignore index a0ad5f6e..20cb0368 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ compile_commands.json .venv/ venv/ platformio.local.ini +meshcore +__pycache__/ \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 8057bc70..5c5735c6 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,4 +1,6 @@ { + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format "recommendations": [ "pioarduino.pioarduino-ide", "platformio.platformio-ide" diff --git a/OTA_STATUS.md b/OTA_STATUS.md new file mode 100644 index 00000000..65f1ccf0 --- /dev/null +++ b/OTA_STATUS.md @@ -0,0 +1,131 @@ +# OTA-over-LoRa — Implementation Status + +Working record of what's built, what's validated (and how), and what remains. Companion to the design +docs: `OTA.md` (spec), `OTA_PLAN.md` (plan), `docs/ota_protocol.md` (wire format). + +Everything is gated behind `-D ENABLE_OTA=1` and is byte-for-byte inert when off. Nothing is committed +to git yet — all changes are in the working tree. + +## Validation summary + +| Phase | Component | Validated how | Status | +|---|---|---|---| +| P0 | `.mota` format + host packager (`tools/mota/`): build, delta (detools), Ed25519 sign, inspect, verify; `EndF` injector | `tools/mota/test_mota.py` (12/12); CLI end-to-end | ✅ | +| P1 | Portable C++ core: `Multihash`, `MerkleTree` (+proofs, gen+verify), `MotaContainer` parse, `BlockBitmap` | `pio test -e native` (21/21), cross-checked vs Python oracle (proofs byte-identical) | ✅ | +| P2 | `EndF` self-scan, `target_id` / `getOtaTargetId()`, `build.sh` injection | **on Heltec v3**: `ota status` reports exact size/hash/target_id matching the build hook | ✅ | +| P3 | `SignerAllowlist`, `OtaStore`, full verify (parse + merkle root + image_hash + **Ed25519** + allowlist) | **on Heltec v3 AND RAK4631**: `verify` → `ok=1 auto=1` with key, `auto=0` without | ✅ cross-platform | +| P4a | `OtaProtocol` message codec (ADV/QUERY/HAVE/GET_MANIFEST/MANIFEST/REQ/DATA) + server proof-gen | native (21/21), proof-gen matches Python | ✅ | +| P4b | `OtaManager` serve+fetch state machine | native: **two-manager full transfer simulation → byte-identical** reassembly | ✅ | +| P4c | Mesh integration: `PAYLOAD_TYPE_OTA` dispatch, lowest-priority hop-capped flood, wired into `simple_repeater` | **full on-air transfer RAK4631→Heltec COMPLETE + VERIFIED** (see below) | ✅ | +| P5 | **Delta apply (ESP32 A/B) via detools 0.53.0**: vendored detools embeddable C decoder (`src/helpers/ota/detools/`, NONE+CRLE) decodes a `--codec sequential --compression crle` patch against the running slot into the inactive slot, hashing→`image_hash` (`ota applydelta`) | **full LoRa run RAK→Heltec**: 129-byte delta (0.01% of a 1.18 MB image) → `detools decoded 1179184 B, hash OK, armed` → reboot → **booted v1.16.9** | ✅ | +| P6 | **Apply (ESP32 A/B)**: verify inactive-slot image vs signed manifest (image_hash + Ed25519 + allowlist) → `esp_ota_set_boot_partition` → reboot | **real role switch on Heltec: repeater → companion, booted correctly** (`ota apply manifest/verify/commit`; after reboot it speaks the companion frame protocol) | ✅ | +| — | Build + flash both platforms | esptool (Heltec/ESP32) + adafruit-nrfutil **DFU** (RAK4631/nRF52) both confirmed | ✅ | + +## On-air status (real LoRa, RAK4631 → Heltec v3) — ✅ COMPLETE + VERIFIED + +A full signed `.mota` was transferred **over real LoRa from the RAK4631 (nRF52) to the Heltec v3 +(ESP32)** — cross-platform — and **completed + verified end-to-end**: +``` +fetch=F 1/6 → 3/6 → 4/6 → 5/6 → 6/6 → fetch=C (~26 s) +verify: parsed=1 root=1 img=1 signed=1 sig=1 trust=1 | ok=1 auto=1 +``` +announce → get-manifest → manifest → windowed request → data; every block **merkle-verified against the +signed root** before storage; the reassembled container then **fully verified** (root + image_hash + +Ed25519 signature + allowlist → auto-appliable). Three real bugs were found and fixed via on-device +testing (none caught by the host sim, which doesn't model the mesh): +- **Windowed requests** (`OTA_REQ_WINDOW`) — was requesting the whole image at once → server TX/pool + congestion. Now paced to the link. +- **Manifest + request retry** in `OtaManager::loop()` — was unrecoverable if a reply dropped. +- **Dedup vs. retries (the key one):** the mesh `hasSeen()` dedup suppressed identical retried requests, + so a single lost reply stalled forever. Fixed: OTA packets are **always processed** (handlers are + idempotent); `hasSeen()` now only gates re-flooding. This makes it genuinely *eventually reliable* — + lossy RF just means more time, exactly as intended. + +## Source map (`src/helpers/ota/`) + +| File | Role | Portable (native) | +|---|---|---| +| `OtaFormat.h` | wire constants (magics, flags, codecs, msg types) | yes | +| `Multihash.h` | sha2-256 truncations via `Utils::sha256` | yes | +| `MerkleTree.{h,cpp}` | leaf/root (O(log n)), verify, gen proof | yes | +| `MotaContainer.{h,cpp}` | `.mota` parse + root/image-hash checks | yes | +| `BlockBitmap.h` | availability from `leaves[]` (erased = missing) | yes | +| `FirmwareInfo.{h,cpp}` | `EndF` self-scan over a region | yes | +| `OtaStore.h` | staging interface + `OtaStoreRam` | yes | +| `OtaProtocol.{h,cpp}` | message encode/decode | yes | +| `OtaManager.{h,cpp}` | serve+fetch state machine | yes | +| `SignerAllowlist.h` | trusted Ed25519 signer keys | yes | +| `OtaVerify.{h,cpp}` | full verify incl. Ed25519 (uses `Identity`) | device-only | +| `OtaSelf.{h,cpp}` | running-firmware region (ESP32 `esp_partition_read`) | device-only | +| `OtaContext.{h,cpp}` | per-device singleton (manager + stores + allowlist) | device-only | +| `OtaCli.{h,cpp}` | `ota …` CLI commands | device-only | + +Core edits (gated): `Packet.h` (`PAYLOAD_TYPE_OTA=0x0C`), `Mesh.{h,cpp}` (dispatch + `createOtaPacket`/ +`sendOtaFlood` + hop limit), `MeshCore.h` (`getOtaTargetId`), `CommonCLI.cpp` (`ota` command), +`examples/simple_repeater/MyMesh.{h,cpp}` (`onOtaRecv` + adapter + begin/loop wiring), `build.sh` +(`MOTA_TARGET_ID`), `test/mocks/SHA256.h` (real host SHA-256). Env wiring: `variants/heltec_v3` and +`variants/rak4631` repeater envs (`ENABLE_OTA` + ota sources [+ `EndF` hook on ESP32]). + +## `ota` CLI (serial console; also remote-admin over LoRa) + +``` +ota status target_id, self-fw size/hash, serve/fetch state, key count +ota key add|list|rm signer allowlist +ota stage prepare serve buffer +ota recv write a chunk into the serve buffer (host streams the .mota) +ota serve parse+verify the staged .mota and make it servable +ota announce broadcast OTA_ADV for the served .mota +ota verify full verify of the staged/served (or fetched) .mota +ota want |auto manual cross-target override (deliberate role switch, e.g. companion->repeater) +ota clear reset buffers +``` +Host harness: `tools/mota/` packager + the scratch `onair*.py` orchestration scripts. + +## Variant coverage (which platforms have OTA, which need special treatment) + +OTA is enabled at the platform base so every variant inherits it; only the apply path differs by HW. + +| Platform | OTA build | Apply path | Special treatment | +|---|---|---|---| +| **ESP32** (all chips) | ✅ enabled in `[esp32_base]` (`ENABLE_OTA`, `helpers/ota/*.cpp`, `detools.c`, `pio_endf`) | A/B via `esp_ota` + detools-**sequential** decode into the inactive slot | `applydelta` only runs on a **dual-app/OTA partition table** (2 app slots + otadata). `min_spiffs.csv` boards already qualify (1.875 MB slots); `huge_app`/single-app boards (most esp32/S3 defaults, 3.19 MB) build fine but refuse apply (`ERR no A/B slot`) until repartitioned. | +| **nRF52 — RAK4631 hardware** (rak4631 + gat562_30s / evb_pro / tracker_pro / watch13, muziworks_r1_neo, rak_wismesh_tag) | ✅ `[rak4631]` (inline) + `[rak4631_hw]` (shared, the other 6) | single-slot **in-place** detools, applied by the custom OTAFIX bootloader after reboot | Device must run the **OTAFIX bootloader** fork. `detools.c` is NOT built into the app (only the bootloader decodes). | +| **nRF52 — non-RAK** (heltec_t1/t096/t114/mesh_solar/mesh_pocket, lilygo techo*/t_impulse_plus, thinknode_m1/m3/m6, t1000-e, nano_g2_ultra, promicro, xiao_nrf52, ikoka_*, wio*, sensecap_solar, rak3401, keepteen_lt1, meshtiny, minewsemi_me25ls01) | ❌ not enabled | none | **Needs its own bootloader fork** (single-slot, like RAK) before OTA is safe. No A/B slot, and the stock Adafruit/SoftDevice bootloader can't apply in place. Out of scope until per-board bootloaders exist. | +| **RP2040 / STM32** | ❌ not enabled | none | No A/B apply path implemented yet. | + +Build-verified this pass (OTA on): ESP32 across all 4 chip families — esp32 `Heltec_v2` (34.6%), S3 `Heltec_v3` (35.3%), C6 `Xiao_C6` (27.9%), and the tight C3 default-partition class up to the fattest config `Heltec_ct62_companion_radio_ble` **96.2%** / `Xiao_C3_companion_radio_ble` 94.6% (the global flash worst case — fits). All 6 RAK4631-hw nRF52 variants build (Flash 55–65%, RAM ≤ 74%). `native` test suite green. (Pre-existing, OTA-unrelated, fail on clean `main` too: `tenstar_c3` stale `helpers/XiaoC3Board.h` include; `generic_espnow` undefined `P_LORA_DIO_1`.) + +## Remaining (clearly scoped) + +1. **Device-side full-image delivery to the slot** — the apply path is done + validated, but the role- + switch test delivered the 631 KB image to the inactive slot via esptool (simulating the transfer, + which is separately proven on-air). The device writing the slot itself during a *full-image* OTA + needs `esp_ota_write`/`esp_partition_write` streaming + a bulk transfer (the RAM `OtaStore` is for + delta-sized images / bring-up). +2. **Multi-fragment blocks** — v1 uses ≤128-byte single-packet blocks; 1 KB blocks need fragment + reassembly in `OtaManager` (`OTA_DATA` already carries `frag_idx`/`frag_total`). +3. **nRF52 apply** — write the `approval` field + reboot-to-DFU for the bootloader fork (external repo). +4. **nRF52 `EndF` `.hex` build wiring** — currently only the ESP32 `.bin` hook is implemented. +5. **P7 auto-propagation + retention** — 24 h announce, finish-current on supersession, 30-day stale GC. +6. **Companion app frames** (`CMD_OTA_*`) + relay/web-seed ingress for the home-node case. +7. **`hw_id` brick-safety** for cross-target (see plan §6.1) — manifest format change, awaiting confirm. + +> Device state: the **Heltec now runs companion_radio_usb** (from the role-switch test); reflash the +> repeater env to continue OTA work. RAK4631 runs the OTA repeater. +8. **`hw_id` brick-safety** for cross-target (`ota want`) — manifest field = `sha2-256:4(manufacturer)`; + allows same-HW role switches but refuses incompatible-HW firmware. Manifest format change → see + `OTA_PLAN.md §6.1` (awaiting confirmation since the format was frozen). The manual override + itself is **done + native-tested**. + +## Reproduce + +```bash +# host tests +./meshcore/bin/python tools/mota/test_mota.py +./meshcore/bin/pio test -e native -f test_ota + +# build + flash (OTA repeater) +./meshcore/bin/pio run -e Heltec_v3_repeater -t upload --upload-port /dev/ttyUSB0 +./meshcore/bin/pio run -e RAK_4631_repeater -t upload --upload-port /dev/ttyACM0 # DFU + +# on-device verify / on-air transfer: see tools/mota/ + scratchpad onair*.py +``` diff --git a/build.sh b/build.sh index 313c4c47..acda76c3 100755 --- a/build.sh +++ b/build.sh @@ -140,8 +140,15 @@ build_firmware() { # e.g: RAK_4631_Repeater-v1.0.0-SHA FIRMWARE_FILENAME="$1-${FIRMWARE_VERSION_STRING}" + # OTA target id = sha2-256:4(env_name) as a little-endian uint32 (matches tools/mota target_id_for_env + # and the device's MainBoard::getOtaTargetId()). Harmless when OTA is disabled. + MOTA_TARGET_ID=$(python3 -c "import hashlib,sys;print('0x%08x'%int.from_bytes(hashlib.sha256(sys.argv[1].encode()).digest()[:4],'little'))" "$1" 2>/dev/null || echo "") + # add firmware version info to end of existing platformio build flags in environment vars export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DFIRMWARE_BUILD_DATE='\"${FIRMWARE_BUILD_DATE}\"' -DFIRMWARE_VERSION='\"${FIRMWARE_VERSION_STRING}\"'" + if [ -n "$MOTA_TARGET_ID" ]; then + export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DMOTA_TARGET_ID=${MOTA_TARGET_ID}" + fi # disable debug flags if requested disable_debug_flags diff --git a/docs/ota_protocol.md b/docs/ota_protocol.md new file mode 100644 index 00000000..94712871 --- /dev/null +++ b/docs/ota_protocol.md @@ -0,0 +1,288 @@ +# MeshCore OTA — `.mota` container & LoRa protocol (v1 draft) + +Goals: distribute firmware over LoRa as a self-verifying, resumable, BitTorrent-v2-style block +transfer that survives reboots, never auto-applies without consent, and is portable enough for other +projects (e.g. Meshtastic) to adopt. + +--- + +## 1. Conventions + +- **Endianness:** all multi-byte integers are little-endian unless stated. +- **Hashes (multihash):** the hash family is declared once per manifest via `hash_algo`. v1 uses + `0x12` = **SHA-256** (the [multihash](https://github.com/multiformats/multihash) code for sha2-256). + Truncations used: + - `sha2-256:4` — first 4 bytes of the SHA-256 digest. Merkle leaves, internal nodes, root, proofs. + - `sha2-256:8` — first 8 bytes. Base-firmware identity (`base_hash`, `EndF`). + - `sha2-256:32` — full digest. The image security anchor (`image_hash`). + Digests are stored **bare** (just the truncated bytes); the family is implied by `hash_algo`. +- **Signatures:** Ed25519 (RFC 8032), 64-byte detached signature, 32-byte public key. + +Reference constants: + +| Name | Bytes (hex) | ASCII | +|---|---|---| +| Container `MAGIC` | `6D 4F 54 41` | `mOTA` | +| Container `TRAILER` | `76 6B 34 39 36` | `vk496` | +| `EndF` marker | `45 6E 64 46` | `EndF` | +| `hash_algo` (sha2-256) | `12` | — | +| `approval` = not approved | `FF FF FF FF` | (erased) | +| `approval` = approved | `41 50 52 56` | `APRV` | +| `format_ver` | `01` | — | + +--- + +## 2. Firmware image & the `EndF` trailer + +Every OTA-capable firmware build appends a 16-byte `EndF` trailer to its flashed image so a running +node can discover its own size/identity on any MCU (no linker symbols needed). + +``` +flashed image = BODY (image bytes) || EndF trailer +EndF trailer (16 bytes): + off 0 4 "EndF" (45 6E 64 46) + off 4 4 body_len uint32 LE — length of BODY (excludes this 16-byte trailer) + off 12 8 body_hash sha2-256:8 of BODY +``` + +- **Size discovery:** scan flash from the partition top downward for the `EndF` marker; the byte + before it is the last BODY byte. (Same technique as `NRF52Board::getBootloaderVersion`.) +- **Self-identity / delta base matching:** a node's `body_hash` is read directly from its own `EndF`; + a delta's `base_hash` (§5) must equal it. No self-hashing pass required at match time. +- **No circularity:** `EndF` hashes only the BODY, never itself. + +The "reconstructed image" referenced by the manifest is the full `BODY || EndF` (what gets flashed). + +--- + +## 3. The `.mota` container + +This is the **distributed** form (host-built, wire-transferred). + +``` +off size field +0 4 MAGIC = 6D 4F 54 41 +4 4 MOTA_TOTAL_SIZE uint32 LE — total container bytes (incl. manifest, leaves[], + payload, trailer). Lets a node pre-reserve staging and compute + write_start = staging_region_end − MOTA_TOTAL_SIZE. +8 M MANIFEST (§4; self-delimited, no length field) +8 + M P PAYLOAD (payload_size bytes; delta or full image) +8 + M + P 5 TRAILER = 76 6B 34 39 36 +``` + +`MOTA_TOTAL_SIZE = 4 + 4 + M + P + 5`. + +**Staged (in-flash) form.** Written bottom-aligned so `TRAILER` ends at `staging_region_end`. Identical +bytes, except the device mutates two regions in place (both NOR-safe, no re-erase): the `leaves[]` +slots (filled as blocks arrive) and the 4-byte `approval` field (on user approval). Everything else is +immutable. + +--- + +## 4. The manifest + +Fields are serialized in this exact order. Conditional fields are present per `flags`. + +``` +off size field notes +0 1 format_ver = 0x01 +1 1 flags bit0 FULL (0=delta/partial, 1=full image) + bit1 SIGNED + bits2-7 reserved (0) +2 1 hash_algo 0x12 = sha2-256 +3 4 target_id device/arch/role discriminator (§9) +7 4 fw_version MAJOR<<24 | MINOR<<16 | PATCH<<8 | pre (comparable uint32) +11 4 image_size size of the reconstructed image (BODY||EndF) +15 4 payload_size PAYLOAD bytes in this container +19 1 block_size_log2 e.g. 0x0A = 1024 +20 4 merkle_root sha2-256:4 over PAYLOAD blocks (§6) +24 32 image_hash sha2-256:32 of the reconstructed image — SECURITY anchor +56 1 codec_id 0=full/raw, 1=detools-sequential, 2=detools-in-place +57 8 base_hash [present iff !FULL] sha2-256:8 of the BASE image's BODY (matches EndF.body_hash) +. 32 signer_pubkey [present iff SIGNED] Ed25519 public key +. 64 signature [present iff SIGNED] Ed25519 over all bytes from off 0 up to here (exclusive) +. 4 approval ALWAYS present. FF FF FF FF = not approved; 41 50 52 56 ("APRV") = approved +. 4*BC leaves[] ALWAYS present. BC = ceil(payload_size / 2^block_size_log2). sha2-256:4 each +``` + +Self-delimiting: a parser knows every offset from `format_ver`/`flags` + `payload_size` (→ `BC`); no +explicit length field is stored. + +Manifest size (excluding `leaves[]`): unsigned-full 57+4=61, signed-full 161, unsigned-delta 69, +**signed-delta 165**. + +### 4.1 Signed region + +`signature` covers manifest bytes `[0, signature_offset)` — i.e. everything before it, including +`signer_pubkey` and (for deltas) `base_hash`. It does **not** cover `approval` or `leaves[]`: +- `leaves[]` are verified against the signed `merkle_root` (§6), so they need no separate signature. +- `approval` is device-local consent (§7), deliberately outside the signature. + +### 4.2 The `approval` field + +- Distributed and **forced on ingest** to `FF FF FF FF` (a peer can never pre-approve). +- The local user's `ota apply` writes `41 50 52 56` (`"APRV"`) — a single NOR-safe write (only clears + bits from the erased word). Any partial/other value reads as not-approved (fail-safe). +- Auto-bound to this image: it lives in this `.mota`'s manifest and is re-erased when a new `.mota` is + staged. +- It is a **consent** marker, not a security primitive. Authenticity = `signature` + `image_hash`. + +--- + +## 5. Payload, codecs & delta base + +`PAYLOAD` is either the full reconstructed image (`FULL`) or a delta (`!FULL`). + +| `codec_id` | Meaning | Notes | +|---|---|---| +| 0 | full / raw | PAYLOAD = reconstructed image (`BODY||EndF`). Typical for ESP32 (A/B). | +| 1 | detools sequential | needs random read of base + sequential write of result (e.g. ESP32 A→B). | +| 2 | detools in-place | bounded scratch; rewrites the app region in place (nRF52 single-slot). | + +For deltas, `base_hash` = the base image's `EndF.body_hash` (sha2-256:8 of its BODY). A node applies a +delta only if `base_hash` matches its own `EndF.body_hash`. After applying, the result MUST hash +(sha2-256:32) to `image_hash` before it is flashed — this is the hard security gate. + +Compression is internal to the detools patch; the chosen scheme must be supported by the applier +(bootloader contract, §12). Patches are produced by detools 0.53.0 (`tools/mota` → `detools.create_patch`) +and decoded on-device by detools' own embeddable C decoder, vendored verbatim at +`src/helpers/ota/detools/` (see its `README.meshcore.txt`). That build enables only the self-contained +`NONE` + `CRLE` compressions (no malloc / liblzma / heatshrink), so MeshCore deltas use +`--codec sequential --compression crle`. The ESP32 applier (`OtaApply.cpp::ota_apply_detools_mota`) +wires the decoder's callbacks to: read base ← running OTA slot, stream patch ← fetched bytes in RAM, +write output → inactive slot, hashing the output and checking it against `image_hash` before arming. + +--- + +## 6. Merkle tree (sha2-256:4) + +Purpose: verify each PAYLOAD block against the signed `merkle_root` **before** the whole payload +exists, so corruption/forgery is localized to a block. + +- **Blocks:** PAYLOAD is split into `BC = ceil(payload_size / B)` blocks, `B = 2^block_size_log2` + (default 1024). The last block is its real length (**no zero padding**). +- **Leaf:** `leaves[i] = sha2-256:4( block_i_bytes )`. +- **Internal node:** `node = sha2-256:4( left || right )` (4+4 = 8 input bytes). +- **Odd level:** if a level has an odd number of nodes, the **last node is promoted unchanged** to the + next level (no duplication). +- **Root:** reduce until one node remains. `BC == 1` → root = `leaves[0]`. `BC == 0` is invalid. + +### 6.1 Proofs + +A proof for block `i` is the ordered list of sibling digests from leaf to root, each tagged +left/right. Promoted levels contribute **no** element. Verification (needs `BC` to know the shape): + +``` +h = leaf_i ; idx = i ; n = BC ; p = 0 +while n > 1: + if (n is odd) and (idx == n-1): # this node was promoted + pass + else: + sib, side = proof[p] ; p += 1 + h = sha2-256:4( sib || h ) if side==left else sha2-256:4( h || sib ) + idx //= 2 ; n = (n + 1) // 2 +accept iff h == merkle_root and p == len(proof) +``` + +Over LoRa, `leaves[]` are **omitted** from the manifest transfer; a serving node computes a block's +proof on demand from its stored `leaves[]`, and the fetcher fills its own `leaves[i]` as each verified +block lands. + +--- + +## 7. Block availability (persistent, derived from `leaves[]`) + +There is no separate availability structure. **Block `i` is present ⟺ `leaves[i]` is non-erased** +(`!= FF FF FF FF`). Because `leaves[]` live in the staged flash region, availability **survives +reboot**. Commit order per block (crash-safe): (1) verify proof, (2) write block payload to its +offset, (3) write `leaves[i]` **last**. A power loss before step 3 leaves the slot erased → the block +is simply re-fetched (idempotent). On boot a node rebuilds a small in-RAM bitmap (`ceil(BC/8)` bytes) +by scanning `leaves[]`. + +A node holding the complete payload (or relaying/serving its own firmware) advertises `have_all` +instead of a bitmap. + +--- + +## 8. LoRa OTA protocol + +Carried in MeshCore packets with **`PAYLOAD_TYPE_OTA = 0x0C`** (subject to change if core devs prefer +reusing `RAW_CUSTOM 0x0F` + subtype). Every OTA packet payload: + +``` +[0] ota_msg_type +[1..] body +``` + +- **Routing:** `OTA_ADV`/`OTA_QUERY` flood; `OTA_HAVE`/`OTA_MANIFEST`/`OTA_REQ`/`OTA_DATA` direct. +- **Hop cap:** OTA refuses to retransmit when `getPathHashCount() >= ota_hop_limit` (default **3**, + configurable). No change to core routing. +- **Priority:** enqueued at the lowest TX priority (~250) and only when the duty-cycle/airtime budget + has spare headroom, so OTA never competes with mesh traffic. +- **`manifest_id`** = the manifest's `merkle_root` (4 bytes) — a compact content id. + +| `ota_msg_type` | val | dir | body | +|---|---|---|---| +| `OTA_ADV` | 0x01 | flood | `target_id(4) fw_version(4) image_size(4) block_size_log2(1) merkle_root(4) image_hash8(8) flags(1) [base_hash(8) if delta] have_all(1)` | +| `OTA_QUERY` | 0x02 | flood | `target_id(4) min_version(4) caps(1)` (caps bit0 want_delta, bit1 want_full) | +| `OTA_HAVE` | 0x03 | direct | `manifest_id(4) bitmap_off(2) bitmap[]` | +| `OTA_GET_MANIFEST` | 0x04 | direct | `manifest_id(4)` | +| `OTA_MANIFEST` | 0x05 | direct | `manifest_id(4) frag_idx(1) frag_total(1) bytes[]` (omits `leaves[]`) | +| `OTA_REQ` | 0x06 | direct | `manifest_id(4) want_off(2) want_bitmap[]` | +| `OTA_DATA` | 0x07 | direct | `manifest_id(4) block_idx(2) frag_idx(1) frag_total(1) [proof in frag0] bytes[]` | + +Sizing against the 184-byte `MAX_PACKET_PAYLOAD`: `OTA_DATA` fixed overhead ≈ 9 B → ~175 B/fragment → +**6 fragments per 1 KB block**; a proof for ≤512 blocks ≤ 9×4 = 36 B (carried in `frag0`); an +availability bitmap for 500 blocks ≈ 63 B (one packet). + +Reliability is *eventual*: the fetcher re-requests un-acked blocks after a timeout, possibly from a +different peer. No hard ACKs, no ordering. + +### 8.1 Relay seeding (companion frames) + +A node need not store a foreign-target `.mota` to serve it: a relay advertises a manifest on behalf of +an external source and **pulls blocks on demand**. Companion-app frames: `CMD_OTA_PROVIDE_MANIFEST` +(app→node, starts advertising), event `PUSH_OTA_BLOCK_REQ(manifest_id, block_idx)` (node→app), reply +`CMD_OTA_PROVIDE_BLOCK(manifest_id, block_idx, bytes)`. + +--- + +## 9. Identity, trust & versioning + +- **`target_id`** (4 B): compile-time `sha2-256:4(pio_env_name + radio_class + ldscript/partition + + platform)`, injected by `build.sh`, read via `MainBoard::getOtaTargetId()`. A node only fetches/serves + matching `target_id`. (The PlatformIO env name uniquely captures hardware AND role/partition.) +- **`fw_version`:** packed comparable uint32 (`MAJOR<<24|MINOR<<16|PATCH<<8|pre`). +- **Signing & allowlist:** a node keeps a runtime-managed allowlist of trusted Ed25519 signer pubkeys + (none embedded in firmware). A `.mota` is eligible for **auto-apply** only if signed by an allowlisted + key, the signature verifies, and `image_hash` matches; otherwise it is manual-apply only with explicit + confirmation. **Transfer needs no trust** — blocks are content-addressed against the signed root, so + any (untrusted) neighbor may relay them. + +### 9.1 Supersession & retention + +- **Finish-current:** a newer version announced mid-download does not abort the in-progress transfer. +- **Stale GC:** a staged `.mota` carries a persistent `staged_at` epoch; it is discarded after + `ota_stale_ttl` (default **30 days**) unless pinned (`ota keep`) or applying — reclaiming flash from + superseded-complete and stalled-partial images alike. `ota discard` frees the slot immediately. + +--- + +## 10. Apply & bootloader contract (summary) + +- **ESP32:** in-firmware via `Update`/`esp_ota_*` into the inactive A/B slot, then set boot + reboot + (power-safe, rollback-capable). No bootloader changes. +- **nRF52:** running firmware **never** flashes the app. `ota apply` verifies fully, writes the + `approval` field (`"APRV"`), then reboots into DFU. The modified bootloader + (`Adafruit_nRF52_Bootloader_OTAFIX`) locates the staged `.mota` by scanning for `MAGIC`, re-checks + `TRAILER` + signature + `image_hash` + `approval == "APRV"`, applies the codec (delta in-place over + the app region), then clears state and boots. The signature proves author authenticity; `approval` + proves local owner consent — both required. + +--- + +## 11. Versioning of this spec + +`format_ver = 1`. Future changes bump `format_ver`; the multihash `hash_algo` allows changing the +digest family without a format bump. Unknown `format_ver`/`codec_id`/`ota_msg_type` values are ignored +(forward-compatible). diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 43d3950b..fc2bb324 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -12,7 +12,7 @@ #endif #ifndef FIRMWARE_VERSION -#define FIRMWARE_VERSION "v1.16.0" +#define FIRMWARE_VERSION "v1.17.0" #endif #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 09690749..37169efe 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -924,8 +924,10 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc memset(default_scope.key, 0, sizeof(default_scope.key)); } +// OTA mesh-integration (receive/begin/loop) is centralized in mesh::Mesh — no per-example wiring. + void MyMesh::begin(FILESYSTEM *fs) { - mesh::Mesh::begin(); + mesh::Mesh::begin(); // also starts OTA (ota_ctx().begin) for all roles _fs = fs; // load persisted prefs _cli.loadPrefs(_fs); @@ -1267,7 +1269,7 @@ void MyMesh::loop() { bridge.loop(); #endif - mesh::Mesh::loop(); + mesh::Mesh::loop(); // also drives the OTA fetch loop (centralized in mesh::Mesh) if (next_flood_advert && millisHasNowPassed(next_flood_advert)) { mesh::Packet *pkt = createSelfAdvert(); diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 7597c6c6..79253371 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -2,6 +2,9 @@ #include #include +#if defined(ENABLE_OTA) + #include +#endif #include #include @@ -73,7 +76,7 @@ struct NeighbourInfo { #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.16.0" + #define FIRMWARE_VERSION "v1.17.0" #endif #define FIRMWARE_ROLE "repeater" @@ -172,6 +175,7 @@ protected: void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override; bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override; void onControlDataRecv(mesh::Packet* packet) override; + // OTA mesh-integration is centralized in mesh::Mesh (no per-example onOtaRecv / send adapter / tick). void sendFloodReply(mesh::Packet* packet, unsigned long delay_millis, uint8_t path_hash_size); diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index 5277ddad..4f464ed6 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -31,7 +31,7 @@ #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.16.0" + #define FIRMWARE_VERSION "v1.17.0" #endif #ifndef LORA_FREQ diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index c9f135f6..8e8ed8a5 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -38,7 +38,7 @@ #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.16.0" + #define FIRMWARE_VERSION "v1.17.0" #endif #define FIRMWARE_ROLE "sensor" diff --git a/platformio.ini b/platformio.ini index e16f7b83..d3e25576 100644 --- a/platformio.ini +++ b/platformio.ini @@ -58,11 +58,20 @@ build_src_filter = extends = arduino_base platform = platformio/espressif32@6.11.0 monitor_filters = esp32_exception_decoder +; OTA is available on every ESP32 variant (A/B via esp_ota; the detools-sequential apply decodes into the +; inactive slot). The build always includes it; for `applydelta` to actually run, the board must use a +; dual-app/OTA partition table (board_build.partitions) with two app slots + otadata. pio_endf appends +; the EndF self-identity trailer. (nRF52 single-slot OTA is enabled per-board, not here — it needs a +; custom bootloader; RP2040/STM32 have no A/B path yet.) extra_scripts = merge-bin.py + post:tools/mota/pio_endf.py build_flags = ${arduino_base.build_flags} -D ESP32_PLATFORM + -D ENABLE_OTA=1 ; -D ESP32_CPU_FREQ=80 ; change it to your need build_src_filter = ${arduino_base.build_src_filter} + + + + [esp32_ota] lib_deps = @@ -93,6 +102,26 @@ build_flags = ${arduino_base.build_flags} lib_deps = ${arduino_base.lib_deps} https://github.com/oltaco/CustomLFS#0.2.2 + +; Shared OTA recipe for board=rak4631-hardware nRF52 variants. nRF52840 has no A/B slot, so the update is +; applied in place by the custom OTAFIX bootloader (in-place detools); the app only stages + verifies + +; approves it. Reuses the RAK4631 flash layout (src/helpers/ota/OtaFlashLayout_nrf52.h; FS_START 0xD4000 +; is the safe staging ceiling for every RAK4631 role/ldscript). The device must run the matching OTAFIX +; bootloader for `applydelta` to actually apply. (detools.c is intentionally NOT built into the app — +; only the bootloader decodes.) The [rak4631] base defines this same recipe inline because it carries +; board-specific extras (fix_bsec_lib.py, BSEC lib) and a fixed post-script order; the simpler RAK4631 +; variants below just extend this section. +[rak4631_hw] +extends = nrf52_base +extra_scripts = ${nrf52_base.extra_scripts} + post:tools/mota/pio_endf.py +build_flags = ${nrf52_base.build_flags} + -D ENABLE_OTA=1 + -D OTA_FLASH_STORE=1 +build_src_filter = ${nrf52_base.build_src_filter} + + +lib_deps = ${nrf52_base.lib_deps} + ; ----------------- RP2040 --------------------- [rp2040_base] @@ -164,5 +193,11 @@ test_build_src = yes build_src_filter = -<*> +<../src/Utils.cpp> + +<../src/helpers/ota/MerkleTree.cpp> + +<../src/helpers/ota/MotaContainer.cpp> + +<../src/helpers/ota/FirmwareInfo.cpp> + +<../src/helpers/ota/OtaProtocol.cpp> + +<../src/helpers/ota/OtaManager.cpp> + +<../src/helpers/ota/detools/detools.c> lib_deps = google/googletest @ 1.17.0 diff --git a/src/Mesh.cpp b/src/Mesh.cpp index 87ad61af..013b2a32 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -1,14 +1,56 @@ #include "Mesh.h" //#include +#if defined(ENABLE_OTA) +#include "helpers/ota/OtaContext.h" // OTA mesh-integration is centralized here so every role gets it +#endif namespace mesh { +#if defined(ENABLE_OTA) +// Adapter so the portable OtaManager can emit packets through the mesh (lowest priority, hop-capped). +void Mesh::otaSendAdapter(void* ctx, const uint8_t* msg, uint16_t len, bool /*flood*/) { + Mesh* m = (Mesh*)ctx; + Packet* p = m->createOtaPacket(msg, len); + if (p) m->sendOtaFlood(p); +} +#endif + void Mesh::begin() { Dispatcher::begin(); +#if defined(ENABLE_OTA) + uint32_t my_tid = 0; + #ifdef MOTA_TARGET_ID + my_tid = (uint32_t)(MOTA_TARGET_ID); // sha2-256:4(env name), injected by build.sh + #endif + ota::ota_ctx().begin(my_tid, Mesh::otaSendAdapter, this); // also sets the platform apply codec +#endif } void Mesh::loop() { Dispatcher::loop(); +#if defined(ENABLE_OTA) + // Deferred apply-reboot: a verified `ota applydelta` approves the update but does NOT reboot inline, + // so its "verified; applying" reply can be delivered first (over LoRa that reply is the operator's + // only confirmation the apply started). Reboot once that reply has actually been transmitted (the + // outbound queue drains) after a short grace to let it be queued, with a hard cap for a busy node + // whose queue never idles. + { + ota::OtaContext& oc = ota::ota_ctx(); + if (oc.apply_pending) { + if (oc.apply_at == 0) { + oc.apply_at = futureMillis(1500); + oc.apply_hard = futureMillis(15000); + } else if (millisHasNowPassed(oc.apply_at) && + (_mgr->getOutboundTotal() == 0 || millisHasNowPassed(oc.apply_hard))) { + ota::ota_reboot_to_apply(); // does not return + } + } + } + if (millisHasNowPassed(_next_ota_tick)) { + ota::ota_ctx().manager.loop(); // re-request still-missing OTA blocks (rate-limited) + _next_ota_tick = futureMillis(3000); + } +#endif } bool Mesh::allowPacketForward(const mesh::Packet* packet) { @@ -309,6 +351,28 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { } break; +#if defined(ENABLE_OTA) + case PAYLOAD_TYPE_OTA: { + // ALWAYS process every received copy: OTA handlers are idempotent, and "eventually reliable" + // retries deliberately re-send IDENTICAL requests — if we gated processing on hasSeen(), the + // dedup would suppress those retries and the transfer could never recover from a lost reply. + // hasSeen() is used ONLY to avoid re-flooding the same packet more than once. + bool seen = _tables->hasSeen(pkt); + ota::ota_ctx().manager.on_message(pkt->payload, pkt->payload_len); // central OTA receive (all roles) + onOtaRecv(pkt); // optional per-example hook + // Re-flood with a hop cap and the LOWEST priority, so OTA never competes with mesh traffic. + uint8_t n = pkt->getPathHashCount(); + if (!seen && pkt->isRouteFlood() && !pkt->isMarkedDoNotRetransmit() + && n < getOtaHopLimit() + && (n + 1) * pkt->getPathHashSize() <= MAX_PATH_SIZE + && allowPacketForward(pkt)) { + self_id.copyHashTo(&pkt->path[n * pkt->getPathHashSize()], pkt->getPathHashSize()); + pkt->setPathHashCount(n + 1); + action = ACTION_RETRANSMIT_DELAYED(OTA_TX_PRIORITY, getRetransmitDelay(pkt)); + } + break; + } +#endif default: MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): unknown payload type, header: %d", getLogDateTime(), (int) pkt->header); // Don't flood route unknown packet types! action = routeRecvPacket(pkt); @@ -619,6 +683,29 @@ Packet* Mesh::createControlData(const uint8_t* data, size_t len) { return packet; } +#if defined(ENABLE_OTA) +Packet* Mesh::createOtaPacket(const uint8_t* data, size_t len) { + if (len > sizeof(Packet::payload)) return NULL; + Packet* packet = obtainNewPacket(); + if (packet == NULL) { + MESH_DEBUG_PRINTLN("%s Mesh::createOtaPacket(): error, packet pool empty", getLogDateTime()); + return NULL; + } + packet->header = (PAYLOAD_TYPE_OTA << PH_TYPE_SHIFT); // ROUTE_TYPE_* set by sendOtaFlood + memcpy(packet->payload, data, len); + packet->payload_len = len; + return packet; +} + +void Mesh::sendOtaFlood(Packet* packet, uint32_t delay_millis) { + packet->header &= ~PH_ROUTE_MASK; + packet->header |= ROUTE_TYPE_FLOOD; + packet->setPathHashSizeAndCount(1, 0); + _tables->hasSeen(packet); // mark as sent, in case it floods back to us + sendPacket(packet, OTA_TX_PRIORITY, delay_millis); +} +#endif + void Mesh::sendFlood(Packet* packet, uint32_t delay_millis, uint8_t path_hash_size) { if (packet->getPayloadType() == PAYLOAD_TYPE_TRACE) { MESH_DEBUG_PRINTLN("%s Mesh::sendFlood(): TRACE type not suspported", getLogDateTime()); diff --git a/src/Mesh.h b/src/Mesh.h index 2302d6b5..8c03b3d0 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -2,6 +2,16 @@ #include +#if defined(ENABLE_OTA) + // OTA-over-LoRa: lowest TX priority (selected only after all real traffic) + default hop cap. + #ifndef OTA_TX_PRIORITY + #define OTA_TX_PRIORITY 250 + #endif + #ifndef OTA_HOP_LIMIT_DEFAULT + #define OTA_HOP_LIMIT_DEFAULT 3 + #endif +#endif + namespace mesh { class GroupChannel { @@ -144,6 +154,22 @@ protected: */ virtual void onRawDataRecv(Packet* packet) { } +#if defined(ENABLE_OTA) + /** + * \brief An OTA-over-LoRa packet (PAYLOAD_TYPE_OTA) has been received. Subclasses forward the + * payload bytes to their OtaManager. See docs/ota_protocol.md. + */ + virtual void onOtaRecv(Packet* packet) { } + + /** \returns the max hop count for forwarding OTA flood packets (default 3). */ + virtual uint8_t getOtaHopLimit() const { return OTA_HOP_LIMIT_DEFAULT; } + + // OTA mesh-integration is centralized in Mesh::begin()/loop()/dispatch, so every role (repeater, + // companion, room, sensor, ...) gets fetch/serve/apply without per-example wiring. + static void otaSendAdapter(void* ctx, const uint8_t* msg, uint16_t len, bool flood); + unsigned long _next_ota_tick = 0; +#endif + /** * \brief Perform search of local DB of matching GroupChannels. * \param channels OUT - store matching channels in this array, up to max_matches @@ -192,6 +218,13 @@ public: Packet* createPathReturn(const uint8_t* dest_hash, const uint8_t* secret, const uint8_t* path, uint8_t path_len, uint8_t extra_type, const uint8_t*extra, size_t extra_len); Packet* createPathReturn(const Identity& dest, const uint8_t* secret, const uint8_t* path, uint8_t path_len, uint8_t extra_type, const uint8_t*extra, size_t extra_len); Packet* createRawData(const uint8_t* data, size_t len); + +#if defined(ENABLE_OTA) + // Build a PAYLOAD_TYPE_OTA packet from raw OTA message bytes (route set by sendOtaFlood). + Packet* createOtaPacket(const uint8_t* data, size_t len); + // Flood-send at the lowest priority (so OTA never competes with mesh traffic). + void sendOtaFlood(Packet* packet, uint32_t delay_millis = 0); +#endif Packet* createTrace(uint32_t tag, uint32_t auth_code, uint8_t flags = 0); Packet* createControlData(const uint8_t* data, size_t len); diff --git a/src/MeshCore.h b/src/MeshCore.h index cfa33cf9..debcb807 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -64,6 +64,17 @@ public: virtual uint8_t getStartupReason() const = 0; virtual bool getBootloaderVersion(char* version, size_t max_len) { return false; } virtual bool startOTAUpdate(const char* id, char reply[]) { return false; } // not supported +#if defined(ENABLE_OTA) + // 4-byte build-target discriminator for OTA-over-LoRa (docs/ota_protocol.md §9). Default is the + // MOTA_TARGET_ID build flag injected by build.sh; 0 when unset (e.g. a bare IDE build). + virtual uint32_t getOtaTargetId() const { + #ifdef MOTA_TARGET_ID + return (uint32_t)(MOTA_TARGET_ID); + #else + return 0; + #endif + } +#endif // Power management interface (boards with power management override these) virtual bool isExternalPowered() { return false; } diff --git a/src/Packet.h b/src/Packet.h index 0886a06c..3491ae3d 100644 --- a/src/Packet.h +++ b/src/Packet.h @@ -28,6 +28,7 @@ namespace mesh { #define PAYLOAD_TYPE_TRACE 0x09 // trace a path, collecting SNI for each hop #define PAYLOAD_TYPE_MULTIPART 0x0A // packet is one of a set of packets #define PAYLOAD_TYPE_CONTROL 0x0B // a control/discovery packet +#define PAYLOAD_TYPE_OTA 0x0C // OTA-over-LoRa firmware distribution (see docs/ota_protocol.md) //... #define PAYLOAD_TYPE_RAW_CUSTOM 0x0F // custom packet as raw bytes, for applications with custom encryption, payloads, etc diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index b78ad6eb..333873e2 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -4,6 +4,9 @@ #include "AdvertDataHelpers.h" #include "TxtDataHelpers.h" #include +#if defined(ENABLE_OTA) + #include "ota/OtaCli.h" +#endif #ifndef BRIDGE_MAX_BAUD #define BRIDGE_MAX_BAUD 115200 @@ -306,6 +309,10 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re sprintf(reply, "%s (Build: %s)", _callbacks->getFirmwareVer(), _callbacks->getBuildDate()); } else if (memcmp(command, "board", 5) == 0) { sprintf(reply, "%s", _board->getManufacturerName()); +#if defined(ENABLE_OTA) + } else if (memcmp(command, "ota", 3) == 0 && (command[3] == 0 || command[3] == ' ')) { + mesh::ota::handle_ota_command(command, reply, *_board); +#endif } else if (memcmp(command, "sensor get ", 11) == 0) { const char* key = command + 11; const char* val = _sensors->getSettingByKey(key); diff --git a/src/helpers/ota/BlockBitmap.h b/src/helpers/ota/BlockBitmap.h new file mode 100644 index 00000000..c22835f5 --- /dev/null +++ b/src/helpers/ota/BlockBitmap.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include + +// Block-availability helpers (docs/ota_protocol.md §7). +// +// Availability is *derived* from the staged manifest's leaves[]: block i is present iff its 4-byte +// leaf slot is non-erased (!= FF FF FF FF). No separate persistent structure. A compact bitmap +// (1 bit/block) is used on the wire (OTA_HAVE) and as an in-RAM cache. All ops are caller-buffer +// based; no allocation. + +namespace mesh { +namespace ota { + +inline bool leaf_present(const uint8_t* leaves, uint32_t i) { + const uint8_t* p = leaves + (size_t)i * 4; + return !(p[0] == 0xFF && p[1] == 0xFF && p[2] == 0xFF && p[3] == 0xFF); +} + +inline uint32_t bitmap_bytes(uint32_t block_count) { return (block_count + 7) / 8; } + +inline bool bitmap_get(const uint8_t* bm, uint32_t i) { + return (bm[i >> 3] >> (i & 7)) & 1; +} + +inline void bitmap_set(uint8_t* bm, uint32_t i, bool v) { + uint8_t mask = (uint8_t)(1u << (i & 7)); + if (v) bm[i >> 3] |= mask; else bm[i >> 3] &= (uint8_t)~mask; +} + +// Build a bitmap (caller buffer >= bitmap_bytes(count)) from leaves[]. +inline void leaves_to_bitmap(const uint8_t* leaves, uint32_t count, uint8_t* bm_out) { + memset(bm_out, 0, bitmap_bytes(count)); + for (uint32_t i = 0; i < count; i++) + if (leaf_present(leaves, i)) bitmap_set(bm_out, i, true); +} + +inline uint32_t count_present(const uint8_t* leaves, uint32_t count) { + uint32_t n = 0; + for (uint32_t i = 0; i < count; i++) if (leaf_present(leaves, i)) n++; + return n; +} + +inline bool all_present(const uint8_t* leaves, uint32_t count) { + for (uint32_t i = 0; i < count; i++) if (!leaf_present(leaves, i)) return false; + return true; +} + +} // namespace ota +} // namespace mesh diff --git a/src/helpers/ota/FirmwareInfo.cpp b/src/helpers/ota/FirmwareInfo.cpp new file mode 100644 index 00000000..a82f93d2 --- /dev/null +++ b/src/helpers/ota/FirmwareInfo.cpp @@ -0,0 +1,39 @@ +#include "FirmwareInfo.h" +#include "Multihash.h" +#include + +namespace mesh { +namespace ota { + +static uint32_t rd_u32(const uint8_t* p) { + return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); +} + +bool find_self_firmware(const uint8_t* region, uint32_t region_len, + SelfFwInfo& out, bool verify_body) { + out = SelfFwInfo(); + if (!region || region_len < ENDF_LEN) return false; + + for (uint32_t off = 0; off + ENDF_LEN <= region_len; off++) { + if (region[off] != ENDF_MAGIC[0]) continue; // cheap pre-filter ('E') + if (memcmp(region + off, ENDF_MAGIC, 4) != 0) continue; + uint32_t body_len = rd_u32(region + off + 4); + if (body_len != off) continue; // trailer must sit right after the body + + if (verify_body) { + uint8_t h[8]; + mh8(h, region, body_len); + if (memcmp(h, region + off + 8, 8) != 0) continue; // coincidental marker — keep scanning + } + out.valid = true; + out.endf_offset = off; + out.body_len = body_len; + out.image_len = off + ENDF_LEN; + memcpy(out.body_hash, region + off + 8, 8); + return true; + } + return false; +} + +} // namespace ota +} // namespace mesh diff --git a/src/helpers/ota/FirmwareInfo.h b/src/helpers/ota/FirmwareInfo.h new file mode 100644 index 00000000..112a26fb --- /dev/null +++ b/src/helpers/ota/FirmwareInfo.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include "OtaFormat.h" + +// Locate the EndF trailer in a firmware image to learn the running firmware's size + identity +// (docs/ota_protocol.md §2). Portable: operates on a contiguous, readable region — on nRF52/ESP32 +// the application flash is memory-mapped, so the region pointer is just (const uint8_t*)APP_BASE. + +namespace mesh { +namespace ota { + +struct SelfFwInfo { + bool valid = false; + uint32_t body_len = 0; // firmware body length (excludes the 16-byte EndF trailer) + uint32_t image_len = 0; // body_len + ENDF_LEN (what a delta base / full image hashes over) + uint32_t endf_offset = 0; // offset of the "EndF" marker within the region (== body_len) + uint8_t body_hash[8] = {0}; // sha2-256:8 of the body (read from EndF; == a delta's base_hash) +}; + +// Scan `region[0..region_len)` for the firmware's EndF trailer. The body starts at offset 0, so the +// trailer's offset must equal its stored body_len — this uniquely identifies the running firmware's +// EndF even if a staged `.mota` (which contains its own embedded EndF) sits higher in the region. +// If `verify_body` is true the body hash is recomputed and must match (rules out coincidental markers). +bool find_self_firmware(const uint8_t* region, uint32_t region_len, + SelfFwInfo& out, bool verify_body = false); + +} // namespace ota +} // namespace mesh diff --git a/src/helpers/ota/MerkleTree.cpp b/src/helpers/ota/MerkleTree.cpp new file mode 100644 index 00000000..8aa1099a --- /dev/null +++ b/src/helpers/ota/MerkleTree.cpp @@ -0,0 +1,110 @@ +#include "MerkleTree.h" +#include "Multihash.h" +#include + +namespace mesh { +namespace ota { + +void merkle_leaf(uint8_t out[4], const uint8_t* block, uint32_t block_len) { + mh4(out, block, block_len); +} + +void merkle_combine(uint8_t out[4], const uint8_t* left, const uint8_t* right) { + sha256_trunc2(out, 4, left, 4, right, 4); +} + +// Root via binary-counter / Merkle-Mountain-Range with right-to-left bagging. +// Equivalent to the level-by-level "pair adjacent, promote lone last (left||right)" reduction +// (verified against the reference implementation across many counts in the native tests). +void merkle_root(uint8_t out[4], const uint8_t* leaves, uint32_t count) { + if (count == 0) { memset(out, 0, 4); return; } + if (count == 1) { memcpy(out, leaves, 4); return; } + + uint8_t peaks[32][4]; + bool valid[32] = { false }; + + for (uint32_t i = 0; i < count; i++) { + uint8_t cur[4]; + memcpy(cur, leaves + (size_t)i * 4, 4); + uint32_t level = 0; + while (valid[level]) { // carry: combine with the pending peak at this level + merkle_combine(cur, peaks[level], cur); // peak is earlier (left), cur is right + valid[level] = false; + level++; + } + memcpy(peaks[level], cur, 4); + valid[level] = true; + } + + // bag peaks right-to-left: acc starts at the lowest set level (rightmost peak) + int level = 0; + while (level < 32 && !valid[level]) level++; + uint8_t acc[4]; + memcpy(acc, peaks[level], 4); + for (int l = level + 1; l < 32; l++) { + if (valid[l]) merkle_combine(acc, peaks[l], acc); // higher peak is left, acc is right + } + memcpy(out, acc, 4); +} + +bool merkle_verify(const uint8_t* block, uint32_t block_len, uint32_t index, + const uint8_t* siblings, uint8_t n_siblings, + const uint8_t root[4], uint32_t count) { + uint8_t leaf[4]; + merkle_leaf(leaf, block, block_len); + return merkle_verify_from_leaf(leaf, index, siblings, n_siblings, root, count); +} + +bool merkle_verify_from_leaf(const uint8_t leaf[4], uint32_t index, + const uint8_t* siblings, uint8_t n_siblings, + const uint8_t root[4], uint32_t count) { + if (count == 0 || index >= count) return false; + uint8_t h[4]; + memcpy(h, leaf, 4); + + uint32_t idx = index; + uint32_t n = count; + uint8_t p = 0; + while (n > 1) { + bool is_last_odd = (n & 1u) && (idx == n - 1); + if (!is_last_odd) { + if (p >= n_siblings) return false; + const uint8_t* sib = siblings + (size_t)p * 4; + p++; + if (idx & 1u) merkle_combine(h, sib, h); // odd index -> sibling on the left + else merkle_combine(h, h, sib); // even index -> sibling on the right + } + idx >>= 1; + n = (n + 1) >> 1; + } + return (p == n_siblings) && (memcmp(h, root, 4) == 0); +} + +uint8_t merkle_gen_proof(const uint8_t* leaves, uint32_t count, uint32_t index, + uint8_t* scratch, uint8_t* out_siblings) { + if (count == 0 || index >= count) return 0; + memcpy(scratch, leaves, (size_t)count * 4); + uint32_t n = count, idx = index; + uint8_t p = 0; + while (n > 1) { + bool is_last_odd = (n & 1u) && (idx == n - 1); + if (!is_last_odd) { + uint32_t s = (idx & 1u) ? idx - 1 : idx + 1; + memcpy(out_siblings + (size_t)p * 4, scratch + (size_t)s * 4, 4); + p++; + } + // reduce one level in place (parent m written from children 2m,2m+1; m <= i so it's safe) + uint32_t m = 0; + for (uint32_t i = 0; i < n; i += 2) { + if (i + 1 < n) merkle_combine(scratch + (size_t)m * 4, scratch + (size_t)i * 4, scratch + (size_t)(i + 1) * 4); + else memmove(scratch + (size_t)m * 4, scratch + (size_t)i * 4, 4); + m++; + } + idx >>= 1; + n = (n + 1) >> 1; + } + return p; +} + +} // namespace ota +} // namespace mesh diff --git a/src/helpers/ota/MerkleTree.h b/src/helpers/ota/MerkleTree.h new file mode 100644 index 00000000..15987f5b --- /dev/null +++ b/src/helpers/ota/MerkleTree.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include + +// Merkle tree over PAYLOAD blocks, sha2-256:4 (4-byte) leaves/nodes. See docs/ota_protocol.md §6. +// +// Scheme: leaf = H(block); node = H(left || right); on an odd level the last node is promoted +// unchanged (no duplication). Root = single remaining node. +// +// No dynamic allocation: the root is computed with an O(log count) "binary counter" of partial +// peaks (<= 32 levels => 128 bytes of stack). Proofs carry only sibling digests; the left/right +// direction is derived from the block index + total count (no direction bits on the wire). + +namespace mesh { +namespace ota { + +// leaf digest of one payload block +void merkle_leaf(uint8_t out[4], const uint8_t* block, uint32_t block_len); + +// parent of two 4-byte children +void merkle_combine(uint8_t out[4], const uint8_t* left, const uint8_t* right); + +// root over `count` contiguous 4-byte leaf digests (leaves[count*4]). count >= 1. +void merkle_root(uint8_t out[4], const uint8_t* leaves, uint32_t count); + +// Verify that `block` is block `index` of a `count`-block payload whose tree has the given `root`. +// `siblings` is n_siblings contiguous 4-byte digests, ordered leaf->root (promoted levels omitted; +// left/right direction derived from index + count). +bool merkle_verify(const uint8_t* block, uint32_t block_len, uint32_t index, + const uint8_t* siblings, uint8_t n_siblings, + const uint8_t root[4], uint32_t count); + +// Same, but starting from a precomputed 4-byte leaf digest (skips the H(block) step). +bool merkle_verify_from_leaf(const uint8_t leaf[4], uint32_t index, + const uint8_t* siblings, uint8_t n_siblings, + const uint8_t root[4], uint32_t count); + +// Generate the proof (ordered sibling digests) for block `index`, for a server holding leaves[]. +// `scratch` must be >= count*4 bytes (working buffer); `out_siblings` >= 32*4 bytes. +// Returns the number of 4-byte siblings written. Output matches the wire form merkle_verify expects. +uint8_t merkle_gen_proof(const uint8_t* leaves, uint32_t count, uint32_t index, + uint8_t* scratch, uint8_t* out_siblings); + +} // namespace ota +} // namespace mesh diff --git a/src/helpers/ota/MotaContainer.cpp b/src/helpers/ota/MotaContainer.cpp new file mode 100644 index 00000000..7eae43a9 --- /dev/null +++ b/src/helpers/ota/MotaContainer.cpp @@ -0,0 +1,125 @@ +#include "MotaContainer.h" +#include "MerkleTree.h" +#include "Multihash.h" +#include + +namespace mesh { +namespace ota { + +static uint32_t rd_u32(const uint8_t* p) { + return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); +} + +bool MotaManifest::is_approved() const { + return approval && memcmp(approval, APPROVAL_YES, 4) == 0; +} + +bool mota_parse(const uint8_t* buf, uint32_t len, MotaManifest& out) { + out = MotaManifest(); + if (len < 4 + 4 + 5) return false; + if (memcmp(buf, MOTA_MAGIC, 4) != 0) return false; + if (memcmp(buf + len - 5, MOTA_TRAILER, 5) != 0) return false; + uint32_t total = rd_u32(buf + 4); + if (total != len) return false; + + const uint8_t* p = buf + 8; // start of manifest + const uint8_t* end = buf + len - 5; // start of trailer + out.manifest_start = p; + // helper bounds check + #define NEED(n) do { if ((uint32_t)(end - p) < (uint32_t)(n)) return false; } while (0) + + NEED(3 + 16 + 1 + 4 + 32 + 1); + out.format_ver = p[0]; + if (out.format_ver != MOTA_FORMAT_VER) return false; + out.flags = p[1]; + out.hash_algo = p[2]; + out.target_id = rd_u32(p + 3); + out.fw_version = rd_u32(p + 7); + out.image_size = rd_u32(p + 11); + out.payload_size = rd_u32(p + 15); + out.block_size_log2 = p[19]; + out.merkle_root = p + 20; + out.image_hash = p + 24; + out.codec_id = p[56]; + p += 57; + + if (out.block_size_log2 == 0 || out.block_size_log2 > 24) return false; + uint32_t bs = out.block_size(); + out.block_count = (out.payload_size + bs - 1) / bs; + if (out.payload_size == 0 || out.block_count == 0) return false; + + if (!out.is_full()) { NEED(8); out.base_hash = p; p += 8; } + + if (out.is_signed()) { + NEED(32); out.signer_pubkey = p; p += 32; + out.signed_len = (uint32_t)(p - (buf + 8)); // signature covers everything up to here + NEED(64); out.signature = p; p += 64; + } else { + out.signed_len = (uint32_t)(p - (buf + 8)); + } + + NEED(4); out.approval = p; p += 4; + + uint32_t leaves_bytes = out.block_count * 4; + NEED(leaves_bytes); out.leaves = p; p += leaves_bytes; + + NEED(out.payload_size); out.payload = p; p += out.payload_size; + + // payload must end exactly at the trailer + if (p != end) return false; + #undef NEED + return true; +} + +bool mota_parse_manifest(const uint8_t* mf, uint32_t len, MotaManifest& out) { + out = MotaManifest(); + const uint8_t* p = mf; + const uint8_t* end = mf + len; + #define NEEDM(n) do { if ((uint32_t)(end - p) < (uint32_t)(n)) return false; } while (0) + + NEEDM(57); + out.manifest_start = mf; + out.format_ver = p[0]; + if (out.format_ver != MOTA_FORMAT_VER) return false; + out.flags = p[1]; + out.hash_algo = p[2]; + out.target_id = rd_u32(p + 3); + out.fw_version = rd_u32(p + 7); + out.image_size = rd_u32(p + 11); + out.payload_size = rd_u32(p + 15); + out.block_size_log2 = p[19]; + out.merkle_root = p + 20; + out.image_hash = p + 24; + out.codec_id = p[56]; + p += 57; + if (!out.is_full()) { NEEDM(8); out.base_hash = p; p += 8; } + if (out.is_signed()) { + NEEDM(32); out.signer_pubkey = p; p += 32; + out.signed_len = (uint32_t)(p - mf); + NEEDM(64); out.signature = p; p += 64; + } else { + out.signed_len = (uint32_t)(p - mf); + } + NEEDM(4); out.approval = p; p += 4; + if (out.block_size_log2 == 0 || out.block_size_log2 > 24 || out.payload_size == 0) return false; + out.block_count = (out.payload_size + out.block_size() - 1) / out.block_size(); + #undef NEEDM + return true; +} + +bool mota_check_root(const MotaManifest& m) { + if (!m.leaves || m.block_count == 0) return false; + uint8_t root[4]; + merkle_root(root, m.leaves, m.block_count); + return memcmp(root, m.merkle_root, 4) == 0; +} + +bool mota_check_image_hash_full(const MotaManifest& m) { + if (!m.is_full() || !m.payload || !m.image_hash) return false; + uint8_t h[32]; + mh32(h, m.payload, m.payload_size); + return memcmp(h, m.image_hash, 32) == 0; +} + +} // namespace ota +} // namespace mesh diff --git a/src/helpers/ota/MotaContainer.h b/src/helpers/ota/MotaContainer.h new file mode 100644 index 00000000..708674dd --- /dev/null +++ b/src/helpers/ota/MotaContainer.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include "OtaFormat.h" + +// Parse/validate a `.mota` container that is fully present in a RAM buffer (docs/ota_protocol.md +// §3-§4). Variable-length parts are referenced by pointer into the caller's buffer — no copies, no +// allocation. (Device flash-backed staging gets a streaming variant in a later milestone; the field +// layout here is the single source of truth.) + +namespace mesh { +namespace ota { + +struct MotaManifest { + uint8_t format_ver = 0; + uint8_t flags = 0; + uint8_t hash_algo = 0; + uint32_t target_id = 0; + uint32_t fw_version = 0; + uint32_t image_size = 0; + uint32_t payload_size = 0; + uint8_t block_size_log2 = 0; + uint8_t codec_id = 0; + uint32_t block_count = 0; + + const uint8_t* merkle_root = nullptr; // 4 + const uint8_t* image_hash = nullptr; // 32 + const uint8_t* base_hash = nullptr; // 8 (delta only) + const uint8_t* signer_pubkey = nullptr; // 32 (signed only) + const uint8_t* signature = nullptr; // 64 (signed only) + const uint8_t* approval = nullptr; // 4 + const uint8_t* leaves = nullptr; // 4 * block_count + const uint8_t* payload = nullptr; // payload_size + const uint8_t* manifest_start = nullptr;// first manifest byte (== start of the signed region) + uint32_t signed_len = 0; // #bytes the signature covers (from manifest_start) + + bool is_full() const { return flags & MFLAG_FULL; } + bool is_signed() const { return flags & MFLAG_SIGNED; } + uint32_t block_size() const { return 1u << block_size_log2; } + bool is_approved() const; +}; + +// Parse a whole container in `buf[len]`. Returns true on success and fills `out` with pointers into +// `buf`. Validates MAGIC, TRAILER, MOTA_TOTAL_SIZE, format_ver, and internal length consistency. +bool mota_parse(const uint8_t* buf, uint32_t len, MotaManifest& out); + +// Parse a standalone manifest (the bytes [manifest_start, leaves) of a container, i.e. without the +// MAGIC/TOTAL_SIZE framing, leaves[] or payload). Used by the apply path, which receives the manifest +// separately from the image. Sets the fixed fields + signer/signature + signed_len; leaves/payload +// are left null. +bool mota_parse_manifest(const uint8_t* mf, uint32_t len, MotaManifest& out); + +// Recompute the merkle root from the manifest's leaves[] and compare to the merkle_root field. +bool mota_check_root(const MotaManifest& m); + +// For FULL images only: check sha2-256:32(payload) == image_hash. +bool mota_check_image_hash_full(const MotaManifest& m); + +} // namespace ota +} // namespace mesh diff --git a/src/helpers/ota/Multihash.h b/src/helpers/ota/Multihash.h new file mode 100644 index 00000000..82164113 --- /dev/null +++ b/src/helpers/ota/Multihash.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include "Utils.h" // mesh::Utils::sha256 (real on device; real host SHA-256 via test/mocks) +#include "OtaFormat.h" + +// Thin multihash helpers: SHA-256 truncated to N bytes. No state, no allocation. + +namespace mesh { +namespace ota { + +inline void sha256_trunc(uint8_t* out, size_t out_len, const uint8_t* data, size_t len) { + mesh::Utils::sha256(out, out_len, data, (int)len); +} + +inline void sha256_trunc2(uint8_t* out, size_t out_len, + const uint8_t* a, size_t a_len, + const uint8_t* b, size_t b_len) { + mesh::Utils::sha256(out, out_len, a, (int)a_len, b, (int)b_len); +} + +inline void mh4(uint8_t out[4], const uint8_t* data, size_t len) { sha256_trunc(out, 4, data, len); } +inline void mh8(uint8_t out[8], const uint8_t* data, size_t len) { sha256_trunc(out, 8, data, len); } +inline void mh32(uint8_t out[32], const uint8_t* data, size_t len){ sha256_trunc(out, 32, data, len); } + +} // namespace ota +} // namespace mesh diff --git a/src/helpers/ota/OtaApply.cpp b/src/helpers/ota/OtaApply.cpp new file mode 100644 index 00000000..62dfc38a --- /dev/null +++ b/src/helpers/ota/OtaApply.cpp @@ -0,0 +1,265 @@ +#include "OtaApply.h" +#include "OtaFormat.h" +#include "MotaContainer.h" +#include "Identity.h" +#include + +#if defined(ESP32_PLATFORM) + #include // rweather streaming SHA-256 (for hashing the slot in chunks) + #include "esp_ota_ops.h" + #include "esp_partition.h" + #include "esp_system.h" + extern "C" { + #include "detools/detools.h" // vendored detools 0.53.0 embeddable decoder (CRLE-only build) + } +#elif defined(NRF52_PLATFORM) + #include "OtaVerify.h" + #include "OtaSelf.h" + #include "OtaFlashLayout_nrf52.h" + #include "flash/flash_nrf5x.h" // Adafruit core internal-flash driver (has its own extern "C") + #include "nrf.h" + #include "nrf_soc.h" + #include "nrf_sdm.h" +#endif + +namespace mesh { +namespace ota { + +#if defined(ESP32_PLATFORM) + +bool ota_apply_slot_info(uint32_t* addr, uint32_t* size) { + const esp_partition_t* p = esp_ota_get_next_update_partition(nullptr); + if (!p) return false; + if (addr) *addr = p->address; + if (size) *size = p->size; + return true; +} + +bool ota_apply_set_manifest(const uint8_t* mf, uint32_t len, const SignerAllowlist& allow, ApplyState& st) { + st = ApplyState(); + ota_apply_slot_info(&st.slot_addr, &st.slot_size); + MotaManifest m; + if (!mota_parse_manifest(mf, len, m)) return false; + if (!m.is_full()) return false; // A/B apply takes a full image (delta would need decode) + st.image_size = m.image_size; + memcpy(st.image_hash, m.image_hash, 32); + st.manifest_ok = true; + if (m.is_signed()) { + mesh::Identity signer(m.signer_pubkey); + st.sig_ok = signer.verify(m.signature, m.manifest_start, (int)m.signed_len); + st.trusted = st.sig_ok && allow.contains(m.signer_pubkey); + } + return true; +} + +bool ota_apply_verify_slot(ApplyState& st) { + st.slot_ok = false; + if (!st.manifest_ok || st.image_size == 0 || st.image_size > st.slot_size) return false; + const esp_partition_t* p = esp_ota_get_next_update_partition(nullptr); + if (!p) return false; + SHA256 sha; + uint8_t buf[512]; + uint32_t off = 0; + while (off < st.image_size) { + uint32_t n = st.image_size - off; if (n > sizeof(buf)) n = sizeof(buf); + if (esp_partition_read(p, off, buf, n) != ESP_OK) return false; + sha.update(buf, n); + off += n; + } + uint8_t h[32]; + sha.finalize(h, 32); + st.slot_ok = (memcmp(h, st.image_hash, 32) == 0); + return st.slot_ok; +} + +bool ota_apply_commit() { + const esp_partition_t* p = esp_ota_get_next_update_partition(nullptr); + if (!p) return false; + if (esp_ota_set_boot_partition(p) != ESP_OK) return false; + esp_restart(); // does not return + return true; +} + +// --- detools callback context ----------------------------------------------------------------- +// The delta base is the running OTA slot; the reconstructed image is streamed into the inactive slot +// via esp_ota_write (sequential, append-only -- matches detools' sequential output ordering) and +// hashed on the fly so we can check it against the signed manifest image_hash before arming. +struct DetoolsCtx { + const esp_partition_t* base; // delta base (running image), read at absolute `from_pos` + long from_pos; // absolute byte offset into `base` + const uint8_t* patch; // .mota payload (whole patch held in RAM) + uint32_t patch_len; + uint32_t patch_pos; + esp_ota_handle_t out; // inactive slot write handle + SHA256* sha; // running hash of the reconstructed output + uint32_t out_pos; // #bytes written to the output slot + bool io_ok; +}; + +static int dt_from_read(void* arg, uint8_t* buf, size_t size) { + DetoolsCtx* c = (DetoolsCtx*)arg; + if (c->from_pos < 0 || (uint32_t)(c->from_pos) + size > c->base->size) return -DETOOLS_IO_FAILED; + if (esp_partition_read(c->base, (size_t)c->from_pos, buf, size) != ESP_OK) { c->io_ok = false; return -DETOOLS_IO_FAILED; } + c->from_pos += (long)size; + return DETOOLS_OK; +} +static int dt_from_seek(void* arg, int offset) { // detools uses relative seeks + DetoolsCtx* c = (DetoolsCtx*)arg; + c->from_pos += offset; + if (c->from_pos < 0 || (uint32_t)c->from_pos > c->base->size) return -DETOOLS_IO_FAILED; + return DETOOLS_OK; +} +static int dt_patch_read(void* arg, uint8_t* buf, size_t size) { + DetoolsCtx* c = (DetoolsCtx*)arg; + if (c->patch_pos + size > c->patch_len) return -DETOOLS_IO_FAILED; + memcpy(buf, c->patch + c->patch_pos, size); + c->patch_pos += (uint32_t)size; + return DETOOLS_OK; +} +static int dt_to_write(void* arg, const uint8_t* buf, size_t size) { + DetoolsCtx* c = (DetoolsCtx*)arg; + if (esp_ota_write(c->out, buf, size) != ESP_OK) { c->io_ok = false; return -DETOOLS_IO_FAILED; } + c->sha->update(buf, size); + c->out_pos += (uint32_t)size; + return DETOOLS_OK; +} + +bool ota_apply_detools_mota(const uint8_t* buf, uint32_t len, const SignerAllowlist& allow, + ApplyState& st, char* msg) { + st = ApplyState(); + MotaManifest m; + if (!mota_parse(buf, len, m)) { strcpy(msg, "no valid .mota (parse failed)"); return false; } + if (m.is_full() || m.codec_id != CODEC_DETOOLS_SEQUENTIAL) { strcpy(msg, "not a detools-sequential delta"); return false; } + st.image_size = m.image_size; + memcpy(st.image_hash, m.image_hash, 32); + st.manifest_ok = true; + // signature (if signed): valid Ed25519 AND signer in this device's allowlist — refuse otherwise, + // BEFORE decoding an untrusted image into the slot. (The decoded result is also checked against the + // manifest image_hash below, which is the target-firmware-hash gate.) + if (m.is_signed()) { + mesh::Identity signer(m.signer_pubkey); + st.sig_ok = signer.verify(m.signature, m.manifest_start, (int)m.signed_len); + st.trusted = st.sig_ok && allow.contains(m.signer_pubkey); + if (!st.sig_ok) { strcpy(msg, "bad signature"); return false; } + if (!st.trusted) { strcpy(msg, "untrusted signer (pubkey not in allowlist)"); return false; } + } + + const esp_partition_t* base = esp_ota_get_running_partition(); // delta base = what's running + const esp_partition_t* out = esp_ota_get_next_update_partition(nullptr); + if (!base || !out) { strcpy(msg, "no A/B slot"); return false; } + st.slot_addr = out->address; st.slot_size = out->size; + if (m.image_size > out->size) { strcpy(msg, "image > slot"); return false; } + + esp_ota_handle_t h; + if (esp_ota_begin(out, m.image_size, &h) != ESP_OK) { strcpy(msg, "ota_begin failed"); return false; } + + SHA256 sha; + DetoolsCtx ctx; + ctx.base = base; ctx.from_pos = 0; + ctx.patch = m.payload; ctx.patch_len = m.payload_size; ctx.patch_pos = 0; + ctx.out = h; ctx.sha = &sha; ctx.out_pos = 0; ctx.io_ok = true; + + int r = detools_apply_patch_callbacks(dt_from_read, dt_from_seek, dt_patch_read, + (size_t)m.payload_size, dt_to_write, &ctx); + if (r < 0 || !ctx.io_ok) { esp_ota_abort(h); sprintf(msg, "detools err %d @%u/%u", + ctx.io_ok ? r : -DETOOLS_IO_FAILED, (unsigned)ctx.out_pos, (unsigned)m.image_size); return false; } + if ((uint32_t)r != m.image_size || ctx.out_pos != m.image_size) { + esp_ota_abort(h); sprintf(msg, "size mismatch %u!=%u", (unsigned)ctx.out_pos, (unsigned)m.image_size); return false; } + + uint8_t hh[32]; sha.finalize(hh, 32); + st.slot_ok = (memcmp(hh, m.image_hash, 32) == 0); + if (!st.slot_ok) { esp_ota_abort(h); strcpy(msg, "image_hash MISMATCH after decode"); return false; } + if (esp_ota_end(h) != ESP_OK) { strcpy(msg, "ota_end failed"); return false; } + if (esp_ota_set_boot_partition(out) != ESP_OK) { strcpy(msg, "set_boot failed"); return false; } + sprintf(msg, "verified%s; decoded %u B, image hash OK — armed, rebooting to apply", + m.is_signed() ? " (signer trusted)" : " (unsigned)", (unsigned)m.image_size); + return true; +} + +bool ota_apply_mota_nrf52(const uint8_t*, uint32_t, const SignerAllowlist&, ApplyState& st, char* msg) { + st = ApplyState(); strcpy(msg, "nRF52-only (ESP32 uses ota_apply_detools_mota)"); return false; +} + +void ota_reboot_to_apply() { esp_restart(); } // boots the slot armed by ota_apply_detools_mota; no return + +#elif defined(NRF52_PLATFORM) // single-slot: verify + mark APPROVED + hand off to the bootloader + +// ESP32 A/B-only entry points are unsupported on nRF52. +bool ota_apply_slot_info(uint32_t*, uint32_t*) { return false; } +bool ota_apply_set_manifest(const uint8_t*, uint32_t, const SignerAllowlist&, ApplyState& st) { st = ApplyState(); return false; } +bool ota_apply_verify_slot(ApplyState&) { return false; } +bool ota_apply_commit() { return false; } +bool ota_apply_detools_mota(const uint8_t*, uint32_t, const SignerAllowlist&, ApplyState& st, char* msg) { st = ApplyState(); strcpy(msg, "use ota_apply_mota_nrf52"); return false; } + +void ota_reboot_to_apply() { // public: set the apply magic + reset (does not return) + uint8_t sd_en = 0; + sd_softdevice_is_enabled(&sd_en); + if (sd_en) { // POWER is SD-restricted while the SoftDevice runs + sd_power_gpregret_clr(0, 0xFFFFFFFF); + sd_power_gpregret_set(0, GPREGRET_OTA_APPLY); + } else { + NRF_POWER->GPREGRET = GPREGRET_OTA_APPLY; + } + NVIC_SystemReset(); // does not return +} + +bool ota_apply_mota_nrf52(const uint8_t* buf, uint32_t len, const SignerAllowlist& allow, + ApplyState& st, char* msg) { + st = ApplyState(); + MotaManifest m; + if (!mota_parse(buf, len, m)) { strcpy(msg, "parse failed"); return false; } + if (m.is_full() || m.codec_id != CODEC_DETOOLS_INPLACE) { strcpy(msg, "not an in-place delta"); return false; } + st.image_size = m.image_size; + memcpy(st.image_hash, m.image_hash, 32); + st.manifest_ok = true; + + // Gated verification, in order, returning the FIRST failing reason (the bootloader re-checks integrity + // again before booting, so authenticity is gated here and re-validated there): + VerifyResult vr = ota_verify(buf, len, allow); + st.sig_ok = vr.sig_ok; st.trusted = vr.trusted; + + // 1) downloaded payload: the fetched blocks must match the manifest's merkle root (intact + complete) + if (!vr.root_ok || !vr.image_ok) { strcpy(msg, "payload hash mismatch (incomplete or corrupt .mota)"); return false; } + + // 2) target firmware: the delta must be built against THIS running image (base_hash == our EndF body + // hash). The resulting image_hash is re-checked by the bootloader after the in-place decode -- a + // single-slot device cannot produce the target image to hash it before applying. + SelfFwInfo fi; + if (!ota_self_firmware(fi) || !fi.valid) { strcpy(msg, "cannot read running firmware (no EndF)"); return false; } + if (!m.base_hash || memcmp(m.base_hash, fi.body_hash, 8) != 0) { strcpy(msg, "not built for the running firmware (base mismatch)"); return false; } + st.slot_ok = true; + + // 3) signature (only if the .mota is signed): valid Ed25519 AND signer in this device's allowlist + if (vr.is_signed) { + if (!vr.sig_ok) { strcpy(msg, "bad signature"); return false; } + if (!vr.trusted) { strcpy(msg, "untrusted signer (pubkey not in allowlist)"); return false; } + } + + // mark the staged manifest APPROVED in flash (buf is the memory-mapped staging region, so + // m.approval is a real flash address). NOR-clear over the erased 0xFFFFFFFF -> "APRV". + uint32_t approval_addr = (uint32_t)(uintptr_t)m.approval; + if (flash_nrf5x_write(approval_addr, APPROVAL_YES, 4) < 0) { strcpy(msg, "approval write failed"); return false; } + flash_nrf5x_flush(); + if (memcmp((const void*)(uintptr_t)approval_addr, APPROVAL_YES, 4) != 0) { strcpy(msg, "approval not set"); return false; } + + // Approved. Do NOT reset here — return so the caller can deliver `msg` to the operator first; the + // deferred ota_reboot_to_apply() (after the reply is sent) does the actual handoff to the bootloader. + sprintf(msg, "verified%s; applying — rebooting into bootloader once this reply is sent", + vr.is_signed ? " (signer trusted)" : " (unsigned)"); + return true; +} + +#else // native / other platforms + +bool ota_apply_slot_info(uint32_t*, uint32_t*) { return false; } +bool ota_apply_set_manifest(const uint8_t*, uint32_t, const SignerAllowlist&, ApplyState& st) { st = ApplyState(); return false; } +bool ota_apply_verify_slot(ApplyState&) { return false; } +bool ota_apply_commit() { return false; } +bool ota_apply_detools_mota(const uint8_t*, uint32_t, const SignerAllowlist&, ApplyState& st, char* msg) { st = ApplyState(); strcpy(msg, "unsupported"); return false; } +bool ota_apply_mota_nrf52(const uint8_t*, uint32_t, const SignerAllowlist&, ApplyState& st, char* msg) { st = ApplyState(); strcpy(msg, "unsupported"); return false; } +void ota_reboot_to_apply() {} + +#endif + +} // namespace ota +} // namespace mesh diff --git a/src/helpers/ota/OtaApply.h b/src/helpers/ota/OtaApply.h new file mode 100644 index 00000000..a0d06b9b --- /dev/null +++ b/src/helpers/ota/OtaApply.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include +#include "SignerAllowlist.h" + +// P6 apply (full-image, ESP32 A/B). The new image is delivered into the inactive OTA slot; the device +// then verifies that slot against the signed manifest's image_hash (+ Ed25519/allowlist), and commits +// by setting it as the boot partition and rebooting. Safe + rollback-capable (the bootloader validates +// the image; a bad image rolls back). nRF52 apply is the bootloader-handoff path (separate). Functions +// return false on platforms without an A/B OTA layout. + +namespace mesh { +namespace ota { + +struct ApplyState { + bool manifest_ok = false; + bool sig_ok = false; + bool trusted = false; // signer in allowlist + bool slot_ok = false; // inactive slot image hashes to manifest.image_hash + uint32_t slot_addr = 0, slot_size = 0; + uint32_t image_size = 0; + uint8_t image_hash[32] = {0}; +}; + +bool ota_apply_slot_info(uint32_t* addr, uint32_t* size); // the inactive A/B slot +bool ota_apply_set_manifest(const uint8_t* mf, uint32_t len, + const SignerAllowlist& allow, ApplyState& st); // parse + verify signature +bool ota_apply_verify_slot(ApplyState& st); // hash the slot vs image_hash +bool ota_apply_commit(); // set-boot + reboot (no return) + +// Apply a detools-sequential delta `.mota` (whole container in `buf`) using detools' own embeddable +// C decoder (CODEC_DETOOLS_SEQUENTIAL, --compression crle). The running slot is the delta base; the +// decoder streams the patch (held in RAM) and writes the reconstructed image into the inactive slot, +// while we hash the output and check it against the signed manifest image_hash. On success the +// inactive slot is set as boot partition; the caller then reboots. `msg` (>=80 bytes) receives a +// human-readable result. Returns true if the slot is verified + armed. +bool ota_apply_detools_mota(const uint8_t* buf, uint32_t len, + const SignerAllowlist& allow, ApplyState& st, char* msg); + +// nRF52 (RAK4631) single-slot apply. The running app can't rewrite itself, so it does NOT decode: it +// runs the gated verification chain (payload hash -> built-for-this-firmware -> signature/trust) and, +// only if all pass, marks the staged manifest APPROVED in flash. It does NOT reboot — so the caller can +// first send the result back to the operator — the actual handoff is ota_reboot_to_apply() below. +// Returns true (msg = "verified...") when approved, false (msg = the first failing gate) otherwise. +bool ota_apply_mota_nrf52(const uint8_t* buf, uint32_t len, + const SignerAllowlist& allow, ApplyState& st, char* msg); + +// Commit the (already approved/armed) update and reboot into it — does NOT return. Call this only after +// a successful ota_apply_* AND after the confirmation reply has been delivered, so the operator knows +// the apply started (over LoRa the device then goes silent while the bootloader applies). nRF52: set +// the GPREGRET apply magic + reset (the bootloader does the in-place decode + verify). ESP32: reboot +// into the slot already armed by ota_apply_detools_mota. +void ota_reboot_to_apply(); + +} // namespace ota +} // namespace mesh diff --git a/src/helpers/ota/OtaCli.cpp b/src/helpers/ota/OtaCli.cpp new file mode 100644 index 00000000..c1677ff1 --- /dev/null +++ b/src/helpers/ota/OtaCli.cpp @@ -0,0 +1,178 @@ +#include "OtaCli.h" +#include "OtaContext.h" +#include "OtaVerify.h" +#include "OtaSelf.h" +#include "Utils.h" +#include +#include + +namespace mesh { +namespace ota { + +static uint32_t parse_u32(const char* s) { + uint32_t n = 0; + while (*s == ' ') s++; + while (*s >= '0' && *s <= '9') n = n * 10 + (uint32_t)(*s++ - '0'); + return n; +} + +static char fstate_char(OtaManager::FetchState s) { + switch (s) { + case OtaManager::IDLE: return 'I'; + case OtaManager::WANT_MANIFEST: return 'W'; + case OtaManager::FETCHING: return 'F'; + case OtaManager::COMPLETE: return 'C'; + default: return 'X'; + } +} + +bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board) { + const char* a = command + 3; + if (*a != 0 && *a != ' ') return false; + while (*a == ' ') a++; + OtaContext& c = ota_ctx(); + + if (*a == 0 || strncmp(a, "status", 6) == 0) { + SelfFwInfo fi; bool s = ota_self_firmware(fi); + sprintf(reply, "OTA tid=%08X self=%u serve=%u%s fetch=%c %u/%u keys=%u", + (unsigned)board.getOtaTargetId(), (unsigned)(s ? fi.body_len : 0), + (unsigned)c.serve_expected, c.serving ? "(on)" : "", + fstate_char(c.manager.fetchState()), + (unsigned)c.manager.blocksHave(), (unsigned)c.manager.blocksTotal(), + (unsigned)c.allow.count()); + + } else if (strncmp(a, "key add ", 8) == 0) { + uint8_t pub[32]; + strcpy(reply, (mesh::Utils::fromHex(pub, 32, a + 8) && c.allow.add(pub)) ? "OK key added" : "ERR key"); + + } else if (strncmp(a, "key list", 8) == 0) { + int n = sprintf(reply, "keys=%u:", (unsigned)c.allow.count()); + for (uint8_t i = 0; i < c.allow.count() && n < 140; i++) { + char hx[17]; mesh::Utils::toHex(hx, c.allow.get(i), 8); + n += sprintf(reply + n, " %s", hx); + } + + } else if (strncmp(a, "key rm ", 7) == 0) { + uint8_t pub[32]; + strcpy(reply, (mesh::Utils::fromHex(pub, 32, a + 7) && c.allow.remove(pub)) ? "OK removed" : "ERR"); + + } else if (strncmp(a, "stage ", 6) == 0) { + uint32_t sz = parse_u32(a + 6); + if (sz == 0 || sz > OTA_SERVE_BUF_SIZE) { sprintf(reply, "ERR size 1..%u", OTA_SERVE_BUF_SIZE); } + else { memset(c.serve_buf, 0xFF, sz); c.serve_expected = sz; c.serving = false; + sprintf(reply, "OK stage %u bytes", (unsigned)sz); } + + } else if (strncmp(a, "recv ", 5) == 0) { + const char* p = a + 5; uint32_t off = parse_u32(p); + const char* hex = strchr(p, ' '); + if (!hex) { strcpy(reply, "ERR usage: ota recv "); return true; } + hex++; + int blen = (int)strlen(hex) / 2; + uint8_t tmp[80]; + if (blen <= 0 || blen > (int)sizeof(tmp) || !mesh::Utils::fromHex(tmp, blen, hex)) strcpy(reply, "ERR hex"); + else if (off + blen > c.serve_expected) strcpy(reply, "ERR off>size (stage first)"); + else { memcpy(c.serve_buf + off, tmp, blen); sprintf(reply, "OK %d@%u", blen, (unsigned)off); } + + } else if (strncmp(a, "serve", 5) == 0) { + c.serving = c.manager.serve(c.serve_buf, c.serve_expected); + if (!c.serving) { strcpy(reply, "ERR serve (bad .mota)"); return true; } + VerifyResult r = ota_verify(c.serve_buf, c.serve_expected, c.allow); + sprintf(reply, "OK serving | root=%d img=%d sig=%d trust=%d", r.root_ok, r.image_ok, r.sig_ok, r.trusted); + + } else if (strncmp(a, "announce", 8) == 0) { + if (!c.serving) { strcpy(reply, "ERR not serving (ota serve first)"); return true; } + c.manager.announce(); + strcpy(reply, "OK announced"); + + } else if (strncmp(a, "verify", 6) == 0) { + // verify whatever is staged-to-serve, OR the fetched container if a fetch is complete + const uint8_t* buf; uint32_t len; + if (c.manager.fetchState() == OtaManager::COMPLETE) { buf = c.fetch_store.data(); len = c.fetch_store.staged_size(); } + else { buf = c.serve_buf; len = c.serve_expected; } + if (len == 0) { strcpy(reply, "ERR nothing to verify"); return true; } + VerifyResult r = ota_verify(buf, len, c.allow); + sprintf(reply, "verify parsed=%d root=%d img=%d signed=%d sig=%d trust=%d | ok=%d auto=%d", + r.parsed, r.root_ok, r.image_ok, r.is_signed, r.sig_ok, r.trusted, + r.integrity_ok(), r.auto_appliable()); + + } else if (strncmp(a, "applydelta", 10) == 0) { + // Apply the fetched delta. ESP32: detools-sequential decode into the inactive A/B slot + verify + + // arm (reboot after). nRF52: verify + mark APPROVED in flash + reboot into the bootloader, which + // does the in-place decode + verify before booting it (this call does not return on success). + // + // This is destructive (it reboots and reflashes). It is GATED, not interactive — no "type yes" + // round-trip (unreliable over LoRa). First, refuse unless a full update is present: the fetch must + // be COMPLETE (every block received AND the merkle root re-verified). Then the apply path validates + // in order and returns the FIRST failing gate, so the operator knows exactly why it refused + // (payload hash -> built-for-this-firmware -> signature/trust); it proceeds only if all pass. + if (c.manager.fetchState() != OtaManager::COMPLETE || c.fetch_store.staged_size() == 0) { + sprintf(reply, "ERR no complete update fetched (fetch=%c %u/%u)", + fstate_char(c.manager.fetchState()), (unsigned)c.manager.blocksHave(), + (unsigned)c.manager.blocksTotal()); + return true; + } + char m2[100]; +#if defined(NRF52_PLATFORM) + bool ok = ota_apply_mota_nrf52(c.fetch_store.data(), c.fetch_store.staged_size(), c.allow, c.apply_st, m2); +#else + bool ok = ota_apply_detools_mota(c.fetch_store.data(), c.fetch_store.staged_size(), c.allow, c.apply_st, m2); +#endif + // On success the update is approved/armed but NOT yet rebooted — arm the deferred handoff so this + // reply reaches the operator first; the mesh loop reboots once it has been transmitted. + if (ok) c.apply_pending = true; + sprintf(reply, "%s | %s", ok ? "OK" : "ERR", m2); + + } else if (strncmp(a, "self", 4) == 0) { + // running firmware identity (EndF): body_len + body_hash:8 — compare against a delta's base_hash + SelfFwInfo fi; + if (!ota_self_firmware(fi) || !fi.valid) { strcpy(reply, "ERR no EndF (firmware lacks the trailer?)"); return true; } + char hx[17]; mesh::Utils::toHex(hx, fi.body_hash, 8); + sprintf(reply, "self body=%u image=%u base_hash=%s", (unsigned)fi.body_len, (unsigned)fi.image_len, hx); + + } else if (strncmp(a, "apply", 5) == 0) { + const char* sub = a + 5; + while (*sub == ' ') sub++; + if (strncmp(sub, "slot", 4) == 0) { + uint32_t addr = 0, size = 0; + if (ota_apply_slot_info(&addr, &size)) sprintf(reply, "inactive slot addr=0x%X size=%u", (unsigned)addr, (unsigned)size); + else strcpy(reply, "ERR no A/B slot (apply unsupported on this build)"); + } else if (strncmp(sub, "manifest", 8) == 0) { + // the manifest-fixed bytes were loaded into serve_buf via `ota stage`/`ota recv` + if (ota_apply_set_manifest(c.serve_buf, c.serve_expected, c.allow, c.apply_st)) + sprintf(reply, "manifest ok img=%u sig=%d trust=%d", (unsigned)c.apply_st.image_size, + c.apply_st.sig_ok, c.apply_st.trusted); + else strcpy(reply, "ERR manifest parse / not full-image / unsupported"); + } else if (strncmp(sub, "verify", 6) == 0) { + bool ok = ota_apply_verify_slot(c.apply_st); + sprintf(reply, "slot image_hash %s (size=%u)", ok ? "MATCH" : "MISMATCH", (unsigned)c.apply_st.image_size); + } else if (strncmp(sub, "commit", 6) == 0) { + if (!c.apply_st.slot_ok) { strcpy(reply, "ERR run 'ota apply verify' first (slot must match)"); return true; } + // (D2: auto-apply would also require c.apply_st.trusted; a manual commit is allowed here.) + ota_apply_commit(); // sets boot partition + reboots into the new image; no return + strcpy(reply, "ERR commit failed (no A/B slot?)"); + } else { + strcpy(reply, "ERR ota apply (slot|manifest|verify|commit)"); + } + + } else if (strncmp(a, "want ", 5) == 0) { + const char* p = a + 5; + while (*p == ' ') p++; + if (strncmp(p, "auto", 4) == 0) { c.manager.want(0); strcpy(reply, "OK auto (own target only)"); } + else { + uint32_t t = (uint32_t)strtoul(p, nullptr, 16); // hex target_id (e.g. from another env) + c.manager.want(t); + sprintf(reply, "OK cross-target: will fetch %08X (you ensure HW compatible)", (unsigned)t); + } + + } else if (strncmp(a, "clear", 5) == 0) { + c.serve_expected = 0; c.serving = false; c.fetch_store.clear(); + strcpy(reply, "OK cleared"); + + } else { + strcpy(reply, "ERR (status|self|key|stage|recv|serve|announce|verify|want|applydelta|clear)"); + } + return true; +} + +} // namespace ota +} // namespace mesh diff --git a/src/helpers/ota/OtaCli.h b/src/helpers/ota/OtaCli.h new file mode 100644 index 00000000..987eebb5 --- /dev/null +++ b/src/helpers/ota/OtaCli.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +// Text-CLI surface for OTA (P3/P5). Wired from CommonCLI (and reachable over LoRa remote-admin). +// Kept out of CommonCLI.cpp itself so the OTA state (allowlist, staging store) lives in the OTA module. + +namespace mesh { +namespace ota { + +// Handle an "ota ..." command. `command` is the full line (starts with "ota"). Fills `reply` +// (<= ~160 bytes, as per the CLI buffer). Returns true if it was an OTA command. +bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board); + +} // namespace ota +} // namespace mesh diff --git a/src/helpers/ota/OtaContext.cpp b/src/helpers/ota/OtaContext.cpp new file mode 100644 index 00000000..10f2bba4 --- /dev/null +++ b/src/helpers/ota/OtaContext.cpp @@ -0,0 +1,12 @@ +#include "OtaContext.h" + +namespace mesh { +namespace ota { + +OtaContext& ota_ctx() { + static OtaContext ctx; + return ctx; +} + +} // namespace ota +} // namespace mesh diff --git a/src/helpers/ota/OtaContext.h b/src/helpers/ota/OtaContext.h new file mode 100644 index 00000000..68b3abb7 --- /dev/null +++ b/src/helpers/ota/OtaContext.h @@ -0,0 +1,66 @@ +#pragma once + +#include "OtaManager.h" +#include "OtaStore.h" +#include "SignerAllowlist.h" +#include "OtaApply.h" +#include "OtaFormat.h" +#if defined(NRF52_PLATFORM) && defined(OTA_FLASH_STORE) + #include "OtaStoreFlashNrf52.h" +#endif + +// Per-device OTA singleton shared by the CLI (OtaCli) and the mesh adapter (the example's MyMesh). +// Holds the session engine, a staging store (fetch), a RAM serve buffer, and the signer allowlist. +// nRF52 stages into FLASH (OtaStoreFlashNrf52): a delta can be 100 KB+, too big to hold in RAM, and the +// COMPLETE container must persist so the bootloader can apply it after reboot. A flash page-erase halts +// the CPU (~85 ms) and starves the LoRa RX, so the store COALESCES writes to the 4 KB page (the erase +// unit) and commits each page once, off the per-packet path (see OtaManager.h) — RAM stays O(one page). +// (v1 has no mid-transfer resume; an interrupted fetch simply restarts.) ESP32/native use the RAM store. + +namespace mesh { +namespace ota { + +#ifndef OTA_SERVE_BUF_SIZE +#define OTA_SERVE_BUF_SIZE 16384 +#endif +#ifndef OTA_FETCH_BUF_SIZE +#define OTA_FETCH_BUF_SIZE 16384 +#endif + +struct OtaContext { + OtaManager manager; +#if defined(NRF52_PLATFORM) && defined(OTA_FLASH_STORE) + OtaStoreFlashNrf52 fetch_store; // persistent flash staging (survives reboot; large deltas) +#else + OtaStoreRam fetch_store; +#endif + SignerAllowlist allow; + uint8_t serve_buf[OTA_SERVE_BUF_SIZE]; + uint32_t serve_expected = 0; // size declared by `ota stage` + bool serving = false; // manager.serve() succeeded + ApplyState apply_st; // pending apply (P6) + + // Deferred apply-reboot: a verified `ota applydelta` approves the update but does NOT reboot inline, + // so the CLI can first deliver the "verified; applying" reply (over LoRa it's the only way the + // operator learns the apply started). The mesh loop then calls ota_reboot_to_apply() once that reply + // has actually been transmitted. apply_at/apply_hard are mesh-clock deadlines the loop fills in. + bool apply_pending = false; + uint32_t apply_at = 0; // earliest reboot time (lets the reply get queued + start sending) + uint32_t apply_hard = 0; // hard cap, in case the TX queue never idles on a busy node + + void begin(uint32_t target_id, OtaSend send, void* ctx) { + manager.begin(target_id, send, ctx); + // a node only fetches firmware it can apply: ESP32 A/B -> sequential, nRF52 single-slot -> in-place +#if defined(NRF52_PLATFORM) + manager.set_apply_codec(CODEC_DETOOLS_INPLACE); +#elif defined(ESP32_PLATFORM) + manager.set_apply_codec(CODEC_DETOOLS_SEQUENTIAL); +#endif + manager.set_fetch_store(&fetch_store); + } +}; + +OtaContext& ota_ctx(); // process-wide singleton + +} // namespace ota +} // namespace mesh diff --git a/src/helpers/ota/OtaDebug.h b/src/helpers/ota/OtaDebug.h new file mode 100644 index 00000000..a126cdec --- /dev/null +++ b/src/helpers/ota/OtaDebug.h @@ -0,0 +1,11 @@ +#pragma once + +// Opt-in OTA tracing over Serial: build with -D OTA_DEBUG to watch the fetch (ADV/REQ/block/page-flush) +// during bring-up. Compiles to nothing otherwise, and on the native host (no Arduino), so it never +// touches a non-debug or test build. +#if defined(OTA_DEBUG) && defined(ARDUINO) + #include + #define OTA_DBG(...) do { Serial.printf(__VA_ARGS__); } while (0) +#else + #define OTA_DBG(...) do {} while (0) +#endif diff --git a/src/helpers/ota/OtaFlashLayout_nrf52.h b/src/helpers/ota/OtaFlashLayout_nrf52.h new file mode 100644 index 00000000..220a65be --- /dev/null +++ b/src/helpers/ota/OtaFlashLayout_nrf52.h @@ -0,0 +1,33 @@ +#pragma once + +// Shared OTA flash-layout constants for the nRF52840 (RAK4631) single-slot delta-apply path. +// SINGLE SOURCE OF TRUTH — keep byte-identical with the bootloader's src/ota_layout.h. +// +// The running app occupies [APP_BASE, app_end]; the primary LittleFS (InternalFS) starts at FS_START. +// MeshCore stages a verified+approved `.mota` in the free flash below FS_START (bottom-aligned), then +// sets GPREGRET_OTA_APPLY and resets; the bootloader scans [APP_BASE, FS_START) for it and applies it +// in place. These must match the bootloader and the running SoftDevice's app base. + +#include + +namespace mesh { +namespace ota { + +static const uint32_t MOTA_NRF52_APP_BASE = 0x00026000u; // S140 end (== CODE_REGION_1_START) +// Staging ceiling: the lowest filesystem region above the app. RAK4631 companion builds use the +// extrafs ldscript with ExtraFS at 0xD4000..0xED000 (and InternalFS at 0xED000), while the repeater +// uses the default ldscript (InternalFS at 0xED000, 0xD4000..0xED000 free). 0xD4000 is the safe +// universal ceiling for ALL RAK4631 roles: staging below it never touches ExtraFS or InternalFS, and +// the app (~520 KB) sits well below 0xD4000 either way. +static const uint32_t MOTA_NRF52_FS_START = 0x000D4000u; // ExtraFS start (universal staging ceiling) +static const uint32_t MOTA_NRF52_FLASH_PAGE = 4096u; +static const uint8_t GPREGRET_OTA_APPLY = 0x6Au; // distinct from DFU magics 0x57/0x4E/0xA8 + +// In-place patches are built with --inplace-memory = this (the apply workspace, from APP_BASE up). +// It must hold the new image (~520 KB) yet leave the staged mota room below FS_START: workspace ends +// at APP_BASE+this = 0xBE000, leaving 0xBE000..0xD4000 (~88 KB) for the staged delta. The bootloader +// also bounds writes to < the (scanned) mota start, so a mis-sized memory still fails safe. +static const uint32_t MOTA_NRF52_INPLACE_MEMORY = 0x00098000u; // 608 KB (APP_BASE .. 0xBE000) + +} // namespace ota +} // namespace mesh diff --git a/src/helpers/ota/OtaFormat.h b/src/helpers/ota/OtaFormat.h new file mode 100644 index 00000000..f4680b48 --- /dev/null +++ b/src/helpers/ota/OtaFormat.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include + +// On-the-wire constants for the MeshCore OTA `.mota` container and protocol. +// Normative definition: docs/ota_protocol.md (v1). Mirrors tools/mota/motalib.py. +// +// Portable: no Arduino / RadioLib includes. Compiles on the native host (unit tests) and on device. + +namespace mesh { +namespace ota { + +// ---- container framing ---------------------------------------------------- +static const uint8_t MOTA_MAGIC[4] = { 'm', 'O', 'T', 'A' }; // 6D 4F 54 41 +static const uint8_t MOTA_TRAILER[5] = { 'v', 'k', '4', '9', '6' }; // 76 6B 34 39 36 +static const uint8_t ENDF_MAGIC[4] = { 'E', 'n', 'd', 'F' }; // 45 6E 64 46 +static const uint32_t ENDF_LEN = 16; // marker(4)+body_len(4)+body_hash8(8) + +// ---- manifest ------------------------------------------------------------- +static const uint8_t MOTA_FORMAT_VER = 1; +static const uint8_t HASH_ALGO_SHA256 = 0x12; // multihash code + +static const uint8_t MFLAG_FULL = 0x01; // 0 = delta/partial, 1 = full image +static const uint8_t MFLAG_SIGNED = 0x02; + +static const uint8_t CODEC_FULL = 0; +static const uint8_t CODEC_DETOOLS_SEQUENTIAL = 1; +static const uint8_t CODEC_DETOOLS_INPLACE = 2; + +// ---- hash truncations ----------------------------------------------------- +static const uint8_t MH4 = 4; // sha2-256:4 (merkle leaves/nodes/root/proofs) +static const uint8_t MH8 = 8; // sha2-256:8 (base/EndF body hash) +static const uint8_t MH32 = 32; // sha2-256:32 (image security anchor) + +// ---- approval marker (manifest field, after the signature) ---------------- +static const uint8_t APPROVAL_NOT[4] = { 0xFF, 0xFF, 0xFF, 0xFF }; +static const uint8_t APPROVAL_YES[4] = { 'A', 'P', 'R', 'V' }; // 41 50 52 56 + +// ---- LoRa protocol -------------------------------------------------------- +// The packet payload type is PAYLOAD_TYPE_OTA (0x0C), defined in src/Packet.h for the core dispatch. + +enum OtaMsgType : uint8_t { + OTA_ADV = 0x01, + OTA_QUERY = 0x02, + OTA_HAVE = 0x03, + OTA_GET_MANIFEST = 0x04, + OTA_MANIFEST = 0x05, + OTA_REQ = 0x06, + OTA_DATA = 0x07, +}; + +static const uint16_t OTA_DEFAULT_BLOCK_SIZE = 1024; +static const uint8_t OTA_DEFAULT_HOP_LIMIT = 3; + +} // namespace ota +} // namespace mesh diff --git a/src/helpers/ota/OtaManager.cpp b/src/helpers/ota/OtaManager.cpp new file mode 100644 index 00000000..5684448f --- /dev/null +++ b/src/helpers/ota/OtaManager.cpp @@ -0,0 +1,239 @@ +#include "OtaManager.h" +#include "OtaProtocol.h" +#include "MerkleTree.h" +#include "Multihash.h" +#include "OtaDebug.h" +#include + +namespace mesh { +namespace ota { + +static uint32_t rd_u32(const uint8_t* p) { + return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); +} +static void wr_u32(uint8_t* p, uint32_t v) { p[0]=v; p[1]=v>>8; p[2]=v>>16; p[3]=v>>24; } + +void OtaManager::begin(uint32_t my_target_id, OtaSend send, void* ctx) { + _target = my_target_id; _send = send; _ctx = ctx; + _fstate = IDLE; _has_serve = false; _have = 0; _fbc = 0; +} + +// ---------------- serve ---------------- + +bool OtaManager::serve(const uint8_t* mota, uint32_t len) { + if (!mota_parse(mota, len, _sm)) return false; + _serve_buf = mota; _serve_len = len; _has_serve = true; + return true; +} + +void OtaManager::announce() { + if (!_has_serve) return; + AdvMsg a; + a.target_id = _sm.target_id; + a.fw_version = _sm.fw_version; + memcpy(a.manifest_id, _sm.merkle_root, 4); + a.flags = _sm.flags; + a.have_all = 1; + a.codec_id = _sm.codec_id; + uint8_t b[32]; + emit(b, encode_adv(b, sizeof(b), a), true); +} + +void OtaManager::handleGetManifest(const uint8_t* m, uint16_t n) { + GetManifestMsg gm; + if (!decode_get_manifest(m, n, gm) || !_has_serve) return; + if (memcmp(gm.manifest_id, _sm.merkle_root, 4) != 0) return; + // manifest-minus-leaves == bytes [manifest_start, leaves) + uint32_t mfl = (uint32_t)(_sm.leaves - _sm.manifest_start); + uint8_t b[MAX_PACKET_PAYLOAD]; + ManifestMsg mm; + memcpy(mm.manifest_id, _sm.merkle_root, 4); + mm.frag_idx = 0; mm.frag_total = 1; // fits one fragment (signed manifest <= ~165 B) + mm.bytes = _sm.manifest_start; mm.len = (uint16_t)mfl; + emit(b, encode_manifest(b, sizeof(b), mm), false); +} + +void OtaManager::handleReq(const uint8_t* m, uint16_t n) { + ReqMsg rq; + if (!decode_req(m, n, rq) || !_has_serve) return; + if (memcmp(rq.manifest_id, _sm.merkle_root, 4) != 0) return; + uint32_t bs = _sm.block_size(); + for (uint32_t k = 0; k < rq.count; k++) { + uint32_t idx = rq.start_block + k; + if (idx >= _sm.block_count) break; + uint32_t off = idx * bs; + uint32_t blen = (off + bs <= _sm.payload_size) ? bs : (_sm.payload_size - off); + uint8_t proof[32 * 4]; + uint8_t np = merkle_gen_proof(_sm.leaves, _sm.block_count, idx, _scratch, proof); + DataMsg dm; + memcpy(dm.manifest_id, _sm.merkle_root, 4); + dm.block_idx = (uint16_t)idx; dm.frag_idx = 0; dm.frag_total = 1; + dm.n_proof = np; dm.proof = proof; + dm.data = _sm.payload + off; dm.data_len = (uint16_t)blen; + uint8_t b[MAX_PACKET_PAYLOAD]; + emit(b, encode_data(b, sizeof(b), dm), false); + } +} + +// ---------------- fetch ---------------- + +void OtaManager::handleAdv(const uint8_t* m, uint16_t n) { + AdvMsg a; + if (!decode_adv(m, n, a)) return; + // auto-fetch matches our own target; a manual `want(T)` override accepts target T instead. + uint32_t accept = _desired_target ? _desired_target : _target; + if (a.target_id != accept) return; // not the firmware we're (auto/manually) after + if (!codecOk(a.codec_id)) return; // fw we can't apply on this platform — don't fetch + if (!_fetch || _fstate == FETCHING || _fstate == WANT_MANIFEST) return; + if (_fstate == COMPLETE && memcmp(a.manifest_id, _fid, 4) == 0) return; // already have it + // interested: ask for the manifest + memcpy(_fid, a.manifest_id, 4); + _fstate = WANT_MANIFEST; + GetManifestMsg gm; memcpy(gm.manifest_id, _fid, 4); + uint8_t b[16]; + emit(b, encode_get_manifest(b, sizeof(b), gm), false); +} + +void OtaManager::handleManifest(const uint8_t* m, uint16_t n) { + ManifestMsg mm; + if (!decode_manifest(m, n, mm) || !_fetch) return; + if (_fstate != WANT_MANIFEST || memcmp(mm.manifest_id, _fid, 4) != 0) return; + if (mm.frag_total != 1) return; // multi-fragment manifest not supported yet + + const uint8_t* mf = mm.bytes; // manifest-minus-leaves + uint32_t mfl = mm.len; + if (mfl < 57) { _fstate = FAILED; return; } + if (!codecOk(mf[56])) { _fstate = IDLE; return; } // codec we can't apply (lying/stale ADV) — abort + uint32_t payload_size = rd_u32(mf + 15); + uint8_t bsl = mf[19]; + uint32_t bs = 1u << bsl; + if (bs == 0 || payload_size == 0) { _fstate = FAILED; return; } + uint32_t bc = (payload_size + bs - 1) / bs; + memcpy(_froot, mf + 20, 4); + + uint32_t leaves_off = 8 + mfl; + uint32_t payload_off = leaves_off + bc * 4; + uint32_t total = payload_off + payload_size + 5; + + if (!_fetch->begin(total)) { _fstate = FAILED; return; } + // declare the metadata extent so a flash store can pin it (leaves are written all transfer long) + if (!_fetch->set_meta_size(payload_off)) { _fstate = FAILED; return; } + uint8_t hdr[8]; + memcpy(hdr, MOTA_MAGIC, 4); + wr_u32(hdr + 4, total); + if (!_fetch->write(0, hdr, 8) || + !_fetch->write(8, mf, mfl) || + !_fetch->write(total - 5, MOTA_TRAILER, 5)) { _fstate = FAILED; return; } + + _fpoff = payload_off; _floff = leaves_off; _fpsize = payload_size; _fbc = bc; _fbs = bs; + _ftotal = total; _have = 0; _fstate = FETCHING; + OTA_DBG("OTA: FETCHING bc=%u bs=%u total=%u\n", (unsigned)bc, (unsigned)bs, (unsigned)total); + requestMissing(); +} + +uint32_t OtaManager::blockLen(uint32_t i) const { + uint32_t off = i * _fbs; + return (off + _fbs <= _fpsize) ? _fbs : (_fpsize - off); +} + +bool OtaManager::blockPresent(uint32_t i) const { + uint8_t leaf[4]; + if (!_fetch->read(_floff + i * 4, leaf, 4)) return false; + return !(leaf[0]==0xFF && leaf[1]==0xFF && leaf[2]==0xFF && leaf[3]==0xFF); +} + +void OtaManager::handleData(const uint8_t* m, uint16_t n) { + DataMsg dm; + if (!decode_data(m, n, dm) || !_fetch) return; + if (_fstate != FETCHING || memcmp(dm.manifest_id, _fid, 4) != 0) return; + if (dm.frag_total != 1) return; // single-fragment blocks only (v1) + if (dm.block_idx >= _fbc) return; + if (blockPresent(dm.block_idx)) return; // already have it + + uint32_t want = blockLen(dm.block_idx); + if (dm.data_len != want) return; + + // verify the block against the (signed) root via its proof — reject forged/corrupt data + if (!merkle_verify(dm.data, dm.data_len, dm.block_idx, dm.proof, dm.n_proof, _froot, _fbc)) return; + + // commit: payload block first, then the leaf (the commit marker) + if (!_fetch->write(_fpoff + dm.block_idx * _fbs, dm.data, dm.data_len)) return; + uint8_t leaf[4]; + merkle_leaf(leaf, dm.data, dm.data_len); + if (!_fetch->write(_floff + dm.block_idx * 4, leaf, 4)) return; + + _have++; + OTA_DBG("OTA: block %u OK have=%u/%u\n", (unsigned)dm.block_idx, (unsigned)_have, (unsigned)_fbc); + + // if the current request window is fully received, immediately ask for the next one + // (paces the transfer to the link rate instead of flooding the whole image at once) + if (_have < _fbc) { + bool window_done = true; + for (uint32_t i = _req_start; i < _req_start + _req_count && i < _fbc; i++) { + if (!blockPresent(i)) { window_done = false; break; } + } + if (window_done) requestMissing(); + } + + if (_have >= _fbc) { + // recompute the root over all stored leaves as a final cross-check + // (read leaves into the scratch buffer; bounded by OTA_PROOFGEN_SCRATCH) + if (_fbc * 4 <= sizeof(_scratch) && _fetch->read(_floff, _scratch, _fbc * 4)) { + uint8_t root[4]; + merkle_root(root, _scratch, _fbc); + _fstate = (memcmp(root, _froot, 4) == 0) ? COMPLETE : FAILED; + } else { + _fstate = COMPLETE; // per-block proofs already guaranteed integrity vs the root + } + if (_fstate == COMPLETE) _fetch->finalize(); // commit the staged container to persistent storage + OTA_DBG("OTA: transfer %s\n", _fstate == COMPLETE ? "COMPLETE" : "FAILED(root)"); + } +} + +void OtaManager::requestMissing() { + if (_fstate != FETCHING) return; + // request a small WINDOW of the next missing blocks (keeps the server's TX queue small, + // so OTA never floods/saturates the mesh — docs/ota_protocol.md §8) + uint32_t start = 0; + while (start < _fbc && blockPresent(start)) start++; + if (start >= _fbc) return; + uint32_t count = _fbc - start; + if (count > OTA_REQ_WINDOW) count = OTA_REQ_WINDOW; + _req_start = start; _req_count = count; + ReqMsg rq; memcpy(rq.manifest_id, _fid, 4); + rq.start_block = (uint16_t)start; rq.count = (uint8_t)count; + uint8_t b[16]; + OTA_DBG("OTA: REQ start=%u count=%u (have=%u/%u)\n", + (unsigned)start, (unsigned)count, (unsigned)_have, (unsigned)_fbc); + emit(b, encode_req(b, sizeof(b), rq), false); +} + +void OtaManager::loop() { + if (_fstate == WANT_MANIFEST) { + // the MANIFEST reply may have been lost on a marginal link — retry GET_MANIFEST + GetManifestMsg gm; memcpy(gm.manifest_id, _fid, 4); + uint8_t b[16]; + emit(b, encode_get_manifest(b, sizeof(b), gm), false); + return; + } + if (_fstate != FETCHING) return; + // retry only when a tick passed with no progress (avoids re-request spam during active flow) + if (_have == _loop_last_have) requestMissing(); + _loop_last_have = _have; +} + +// ---------------- dispatch ---------------- + +void OtaManager::on_message(const uint8_t* msg, uint16_t len) { + switch (ota_msg_type(msg, len)) { + case OTA_ADV: handleAdv(msg, len); break; + case OTA_GET_MANIFEST: handleGetManifest(msg, len); break; + case OTA_MANIFEST: handleManifest(msg, len); break; + case OTA_REQ: handleReq(msg, len); break; + case OTA_DATA: handleData(msg, len); break; + default: break; + } +} + +} // namespace ota +} // namespace mesh diff --git a/src/helpers/ota/OtaManager.h b/src/helpers/ota/OtaManager.h new file mode 100644 index 00000000..4121ba18 --- /dev/null +++ b/src/helpers/ota/OtaManager.h @@ -0,0 +1,109 @@ +#pragma once + +#include +#include +#include "OtaFormat.h" +#include "OtaStore.h" +#include "MotaContainer.h" + +// Transport-agnostic OTA session engine (docs/ota_protocol.md §5/§8). It SERVES a complete `.mota` +// (answering GET_MANIFEST / REQ) and/or FETCHES one into an OtaStore (verifying every block against +// the signed merkle root via proofs). It is portable (no Arduino / radio / Ed25519) so it can be +// driven by a host simulation; a thin Mesh adapter wires it to PAYLOAD_TYPE_OTA on device. +// +// v1 assumes single-fragment blocks (block_size small enough to fit one packet). Multi-fragment +// reassembly (for 1 KB blocks) is a later optimization; the wire format already carries frag fields. + +namespace mesh { +namespace ota { + +// Emit an OTA message (one packet payload). `flood`=true for announce/query, false for direct replies. +typedef void (*OtaSend)(void* ctx, const uint8_t* msg, uint16_t len, bool flood); + +#ifndef OTA_PROOFGEN_SCRATCH +#define OTA_PROOFGEN_SCRATCH 4096 // server proof-gen working buffer (supports up to 1024 blocks) +#endif + +#ifndef OTA_REQ_WINDOW +#define OTA_REQ_WINDOW 6 // blocks requested per REQ (keeps the server's TX queue small) +#endif +// nRF52 note: a flash page-erase halts the CPU (~85 ms, code runs from flash) and starves the LoRa RX, +// so writing to flash on every received packet drops in-flight DATA and the transfer stalls. The SD-safe +// driver (Adafruit flash_nrf5x) always erases on flush, so there is no erase-free write; instead +// OtaStoreFlashNrf52 COALESCES to the 4 KB page (the erase unit) and writes each page once — RAM stays +// O(one page), never O(mota). It pins flash page 0 (header+manifest+merkle leaves, which update all +// transfer long) in RAM and streams the payload through one sliding page buffer, flushing page 0 and the +// last page at finalize() (radio idle). Flash is then touched ~once per 4 KB (≈1 per 4 blocks), not per +// packet; a small delta that fits page 0 does ZERO flash I/O until COMPLETE. (Pacing alone is not enough.) + +class OtaManager { +public: + enum FetchState : uint8_t { IDLE, WANT_MANIFEST, FETCHING, COMPLETE, FAILED }; + + void begin(uint32_t my_target_id, OtaSend send, void* ctx); + + // --- serve --- Provide a complete, contiguous `.mota` to serve (caller keeps it alive). + bool serve(const uint8_t* mota, uint32_t len); + void announce(); // broadcast OTA_ADV for the served .mota + + // --- fetch --- Provide the staging store; fetching starts on a matching OTA_ADV. + void set_fetch_store(OtaStore* s) { _fetch = s; } + + // Manual cross-target override (decision: deliberate role switch, e.g. companion -> repeater on the + // same hardware). Normally a node only auto-fetches its OWN target_id; `want(T)` makes it accept an + // ADV for target T instead (T=0 restores auto). The user takes responsibility for HW compatibility; + // a hw_id brick-safety check is the planned safety layer (see docs/ota_protocol.md / plan). + void want(uint32_t target_id) { _desired_target = target_id; } + uint32_t wanted() const { return _desired_target; } + + // Codec compatibility: a node only fetches/accepts fw it can actually apply. CODEC_FULL is always + // acceptable; the platform's single delta codec is set here (ESP32 A/B -> sequential, nRF52 single- + // slot -> in-place). A mismatching `.mota` is rejected at OTA_ADV time, before fetching anything. + void set_apply_codec(uint8_t c) { _apply_codec = c; } + bool codecOk(uint8_t c) const { return c == CODEC_FULL || c == _apply_codec; } + + void on_message(const uint8_t* msg, uint16_t len); // feed one received OTA message + void loop(); // drive fetch (re-request missing blocks) + + FetchState fetchState() const { return _fstate; } + uint32_t blocksHave() const { return _have; } + uint32_t blocksTotal() const { return _fbc; } + const uint8_t* fetchManifestId() const { return _fid; } + +private: + void emit(const uint8_t* b, uint16_t n, bool flood) { if (_send && n) _send(_ctx, b, n, flood); } + void handleAdv(const uint8_t* m, uint16_t n); + void handleGetManifest(const uint8_t* m, uint16_t n); + void handleManifest(const uint8_t* m, uint16_t n); + void handleReq(const uint8_t* m, uint16_t n); + void handleData(const uint8_t* m, uint16_t n); + bool blockPresent(uint32_t i) const; + void requestMissing(); + uint32_t blockLen(uint32_t i) const; + + uint32_t _target = 0; + OtaSend _send = nullptr; + void* _ctx = nullptr; + + // serve + bool _has_serve = false; + const uint8_t* _serve_buf = nullptr; + uint32_t _serve_len = 0; + MotaManifest _sm; + uint8_t _scratch[OTA_PROOFGEN_SCRATCH]; + + // fetch + OtaStore* _fetch = nullptr; + FetchState _fstate = IDLE; + uint8_t _fid[4] = {0}; + uint8_t _froot[4] = {0}; + uint32_t _ftotal = 0, _fpoff = 0, _floff = 0, _fpsize = 0, _fbc = 0, _fbs = 0; + uint32_t _have = 0; + uint32_t _req_start = 0, _req_count = 0; // current outstanding request window + uint32_t _loop_last_have = 0; // for stall detection in loop() + uint32_t _desired_target = 0; // manual cross-target override (0 = auto / own target) + uint8_t _apply_codec = CODEC_DETOOLS_SEQUENTIAL; // platform's delta codec (OtaContext sets it) +}; + +} // namespace ota +} // namespace mesh diff --git a/src/helpers/ota/OtaProtocol.cpp b/src/helpers/ota/OtaProtocol.cpp new file mode 100644 index 00000000..a4929d30 --- /dev/null +++ b/src/helpers/ota/OtaProtocol.cpp @@ -0,0 +1,93 @@ +#include "OtaProtocol.h" +#include + +namespace mesh { +namespace ota { + +// Little-endian cursor helpers. +namespace { +struct W { + uint8_t* p; uint16_t cap; uint16_t n; bool ok; + W(uint8_t* b, uint16_t c) : p(b), cap(c), n(0), ok(true) {} + void u8(uint8_t v) { if (n + 1 > cap) { ok = false; return; } p[n++] = v; } + void u16(uint16_t v){ u8(v & 0xFF); u8(v >> 8); } + void u32(uint32_t v){ u8(v); u8(v >> 8); u8(v >> 16); u8(v >> 24); } + void raw(const uint8_t* d, uint16_t l) { if (n + l > cap) { ok = false; return; } memcpy(p + n, d, l); n += l; } +}; +struct R { + const uint8_t* p; uint16_t len; uint16_t n; bool ok; + R(const uint8_t* b, uint16_t l) : p(b), len(l), n(0), ok(true) {} + uint8_t u8() { if (n + 1 > len) { ok = false; return 0; } return p[n++]; } + uint16_t u16() { uint16_t a = u8(); return a | ((uint16_t)u8() << 8); } + uint32_t u32() { uint32_t a = u8(); a |= (uint32_t)u8() << 8; a |= (uint32_t)u8() << 16; a |= (uint32_t)u8() << 24; return a; } + const uint8_t* raw(uint16_t l) { if (n + l > len) { ok = false; return nullptr; } const uint8_t* r = p + n; n += l; return r; } + uint16_t remaining() const { return len - n; } +}; +} // namespace + +uint16_t encode_adv(uint8_t* buf, uint16_t cap, const AdvMsg& m) { + W w(buf, cap); w.u8(OTA_ADV); w.u32(m.target_id); w.u32(m.fw_version); + w.raw(m.manifest_id, 4); w.u8(m.flags); w.u8(m.have_all); w.u8(m.codec_id); + return w.ok ? w.n : 0; +} +bool decode_adv(const uint8_t* buf, uint16_t len, AdvMsg& m) { + R r(buf, len); if (r.u8() != OTA_ADV) return false; + m.target_id = r.u32(); m.fw_version = r.u32(); + const uint8_t* id = r.raw(4); if (id) memcpy(m.manifest_id, id, 4); + m.flags = r.u8(); m.have_all = r.u8(); m.codec_id = r.u8(); + return r.ok; +} + +uint16_t encode_get_manifest(uint8_t* buf, uint16_t cap, const GetManifestMsg& m) { + W w(buf, cap); w.u8(OTA_GET_MANIFEST); w.raw(m.manifest_id, 4); + return w.ok ? w.n : 0; +} +bool decode_get_manifest(const uint8_t* buf, uint16_t len, GetManifestMsg& m) { + R r(buf, len); if (r.u8() != OTA_GET_MANIFEST) return false; + const uint8_t* id = r.raw(4); if (id) memcpy(m.manifest_id, id, 4); + return r.ok; +} + +uint16_t encode_manifest(uint8_t* buf, uint16_t cap, const ManifestMsg& m) { + W w(buf, cap); w.u8(OTA_MANIFEST); w.raw(m.manifest_id, 4); w.u8(m.frag_idx); w.u8(m.frag_total); + w.raw(m.bytes, m.len); + return w.ok ? w.n : 0; +} +bool decode_manifest(const uint8_t* buf, uint16_t len, ManifestMsg& m) { + R r(buf, len); if (r.u8() != OTA_MANIFEST) return false; + const uint8_t* id = r.raw(4); if (id) memcpy(m.manifest_id, id, 4); + m.frag_idx = r.u8(); m.frag_total = r.u8(); + m.len = r.remaining(); m.bytes = r.raw(m.len); + return r.ok; +} + +uint16_t encode_req(uint8_t* buf, uint16_t cap, const ReqMsg& m) { + W w(buf, cap); w.u8(OTA_REQ); w.raw(m.manifest_id, 4); w.u16(m.start_block); w.u8(m.count); + return w.ok ? w.n : 0; +} +bool decode_req(const uint8_t* buf, uint16_t len, ReqMsg& m) { + R r(buf, len); if (r.u8() != OTA_REQ) return false; + const uint8_t* id = r.raw(4); if (id) memcpy(m.manifest_id, id, 4); + m.start_block = r.u16(); m.count = r.u8(); + return r.ok; +} + +uint16_t encode_data(uint8_t* buf, uint16_t cap, const DataMsg& m) { + W w(buf, cap); w.u8(OTA_DATA); w.raw(m.manifest_id, 4); w.u16(m.block_idx); + w.u8(m.frag_idx); w.u8(m.frag_total); + if (m.frag_idx == 0) { w.u8(m.n_proof); w.raw(m.proof, (uint16_t)m.n_proof * 4); } + w.raw(m.data, m.data_len); + return w.ok ? w.n : 0; +} +bool decode_data(const uint8_t* buf, uint16_t len, DataMsg& m) { + R r(buf, len); if (r.u8() != OTA_DATA) return false; + const uint8_t* id = r.raw(4); if (id) memcpy(m.manifest_id, id, 4); + m.block_idx = r.u16(); m.frag_idx = r.u8(); m.frag_total = r.u8(); + m.n_proof = 0; m.proof = nullptr; + if (m.frag_idx == 0) { m.n_proof = r.u8(); m.proof = r.raw((uint16_t)m.n_proof * 4); } + m.data_len = r.remaining(); m.data = r.raw(m.data_len); + return r.ok; +} + +} // namespace ota +} // namespace mesh diff --git a/src/helpers/ota/OtaProtocol.h b/src/helpers/ota/OtaProtocol.h new file mode 100644 index 00000000..e17af2ed --- /dev/null +++ b/src/helpers/ota/OtaProtocol.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include "OtaFormat.h" + +// Encode/decode for the OTA LoRa messages (docs/ota_protocol.md §8). Each message is a packet payload: +// [0]=ota_msg_type, then a fixed body. Portable + allocation-free; unit-tested on the host. +// +// manifest_id == the manifest's merkle_root (4 bytes), a compact content id. + +namespace mesh { +namespace ota { + +// ---- OTA_ADV: "I have (part of) fw X for target T" (flood, periodic + on demand) ---- +struct AdvMsg { + uint32_t target_id; + uint32_t fw_version; + uint8_t manifest_id[4]; // = merkle_root + uint8_t flags; // manifest flags (FULL/SIGNED) + uint8_t have_all; // 1 = holder has the complete payload + uint8_t codec_id; // manifest codec (0=full,1=detools-seq,2=detools-inplace) — lets a + // receiver reject fw it can't apply before fetching anything +}; + +// ---- OTA_GET_MANIFEST: request the manifest for a content id (direct) ---- +struct GetManifestMsg { uint8_t manifest_id[4]; }; + +// ---- OTA_MANIFEST: the manifest-minus-leaves[], fragmented (direct) ---- +// body: manifest_id(4) frag_idx(1) frag_total(1) bytes[] +struct ManifestMsg { + uint8_t manifest_id[4]; + uint8_t frag_idx, frag_total; + const uint8_t* bytes; uint16_t len; +}; + +// ---- OTA_REQ: request a window of blocks (direct) ---- +struct ReqMsg { uint8_t manifest_id[4]; uint16_t start_block; uint8_t count; }; + +// ---- OTA_DATA: one (fragment of a) block (direct) ---- +// body: manifest_id(4) block_idx(2) frag_idx(1) frag_total(1) [frag0: n_proof(1) proof(n_proof*4)] data[] +struct DataMsg { + uint8_t manifest_id[4]; + uint16_t block_idx; + uint8_t frag_idx, frag_total; + uint8_t n_proof; // only meaningful on frag_idx==0 + const uint8_t* proof; // n_proof*4 bytes (frag0 only) + const uint8_t* data; uint16_t data_len; +}; + +// Each encode_* returns the total payload length (incl. the leading msg-type byte), 0 on overflow. +// Each decode_* returns true on success (and points struct fields into `buf`). + +uint16_t encode_adv(uint8_t* buf, uint16_t cap, const AdvMsg& m); +bool decode_adv(const uint8_t* buf, uint16_t len, AdvMsg& m); + +uint16_t encode_get_manifest(uint8_t* buf, uint16_t cap, const GetManifestMsg& m); +bool decode_get_manifest(const uint8_t* buf, uint16_t len, GetManifestMsg& m); + +uint16_t encode_manifest(uint8_t* buf, uint16_t cap, const ManifestMsg& m); +bool decode_manifest(const uint8_t* buf, uint16_t len, ManifestMsg& m); + +uint16_t encode_req(uint8_t* buf, uint16_t cap, const ReqMsg& m); +bool decode_req(const uint8_t* buf, uint16_t len, ReqMsg& m); + +uint16_t encode_data(uint8_t* buf, uint16_t cap, const DataMsg& m); +bool decode_data(const uint8_t* buf, uint16_t len, DataMsg& m); + +inline uint8_t ota_msg_type(const uint8_t* buf, uint16_t len) { return len ? buf[0] : 0xFF; } + +} // namespace ota +} // namespace mesh diff --git a/src/helpers/ota/OtaSelf.cpp b/src/helpers/ota/OtaSelf.cpp new file mode 100644 index 00000000..dbcb4e91 --- /dev/null +++ b/src/helpers/ota/OtaSelf.cpp @@ -0,0 +1,64 @@ +#include "OtaSelf.h" +#include "FirmwareInfo.h" +#include + +#if defined(ESP32_PLATFORM) + #include "esp_ota_ops.h" + #include "esp_partition.h" +#elif defined(NRF52_PLATFORM) + #include "OtaFlashLayout_nrf52.h" +#endif + +namespace mesh { +namespace ota { + +#if defined(ESP32_PLATFORM) +// Scan the running app partition for the firmware's EndF trailer using esp_partition_read (stable +// across IDF versions — no mmap). Same rule as find_self_firmware(): the marker's absolute offset +// must equal its stored body_len, which uniquely identifies the running firmware's own trailer. +bool ota_self_firmware(SelfFwInfo& out) { + out = SelfFwInfo(); + const esp_partition_t* p = esp_ota_get_running_partition(); + if (!p) return false; + + const uint32_t CH = 512; + uint8_t buf[CH + ENDF_LEN]; // overlap so a marker spanning a chunk edge is still seen + for (uint32_t base = 0; base + ENDF_LEN <= p->size; base += CH) { + uint32_t want = CH + ENDF_LEN; + if (base + want > p->size) want = p->size - base; + if (esp_partition_read(p, base, buf, want) != ESP_OK) return false; + for (uint32_t i = 0; i + ENDF_LEN <= want; i++) { + if (buf[i] != ENDF_MAGIC[0]) continue; + if (memcmp(buf + i, ENDF_MAGIC, 4) != 0) continue; + uint32_t body_len = (uint32_t)buf[i+4] | ((uint32_t)buf[i+5] << 8) + | ((uint32_t)buf[i+6] << 16) | ((uint32_t)buf[i+7] << 24); + if (body_len != base + i) continue; // must sit immediately after a body of that length + out.valid = true; + out.endf_offset = base + i; + out.body_len = body_len; + out.image_len = body_len + ENDF_LEN; + memcpy(out.body_hash, buf + i + 8, 8); + return true; + } + } + return false; +} +#elif defined(NRF52_PLATFORM) +// nRF52 internal flash is memory-mapped, so the running app is directly scannable. The body starts at +// APP_BASE; find_self_firmware() picks the EndF whose stored body_len equals its offset (the running +// firmware's own trailer), ignoring any staged `.mota` (which carries its own embedded EndF) higher up. +bool ota_self_firmware(SelfFwInfo& out) { + const uint8_t* region = (const uint8_t*)(uintptr_t)MOTA_NRF52_APP_BASE; + uint32_t region_len = MOTA_NRF52_FS_START - MOTA_NRF52_APP_BASE; + return find_self_firmware(region, region_len, out, /*verify_body=*/true); +} +#else +bool ota_self_firmware(SelfFwInfo& out) { + // STM32/RP2040: app-region access lands with their apply path. + out = SelfFwInfo(); + return false; +} +#endif + +} // namespace ota +} // namespace mesh diff --git a/src/helpers/ota/OtaSelf.h b/src/helpers/ota/OtaSelf.h new file mode 100644 index 00000000..e3373657 --- /dev/null +++ b/src/helpers/ota/OtaSelf.h @@ -0,0 +1,18 @@ +#pragma once + +#include "FirmwareInfo.h" + +// Device-side accessor for the running firmware's own image (to read its EndF trailer). +// Per-platform: ESP32 memory-maps the running app partition; other platforms TBD (nRF52 uses the +// bootloader-apply path, so its app-region wiring lands with that work). Not compiled on the native +// host — the portable scan logic in FirmwareInfo.{h,cpp} is what gets unit-tested there. + +namespace mesh { +namespace ota { + +// Locate this firmware's EndF trailer in its own flash image. Returns false if unsupported on this +// platform or no valid EndF is present (e.g. firmware built without the EndF build hook). +bool ota_self_firmware(SelfFwInfo& out); + +} // namespace ota +} // namespace mesh diff --git a/src/helpers/ota/OtaStore.h b/src/helpers/ota/OtaStore.h new file mode 100644 index 00000000..3a75e852 --- /dev/null +++ b/src/helpers/ota/OtaStore.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include +#include + +// Staging backend for an in-transit `.mota` (docs/ota_protocol.md §7). Blocks may arrive out of order +// and progress must survive reboots, so the store is random-access. The transfer/verify logic is +// written against this interface; concrete impls are per-platform (RAM for tests/bring-up; persistent +// flash — ESP32 OTA slot / nRF52 raw region — for production, dropped in behind the same interface). + +namespace mesh { +namespace ota { + +class OtaStore { +public: + virtual ~OtaStore() {} + // Prepare staging for a container of `total_size` bytes (erases/clears). false if it won't fit. + virtual bool begin(uint32_t total_size) = 0; + virtual bool write(uint32_t offset, const uint8_t* data, uint32_t len) = 0; + virtual bool read(uint32_t offset, uint8_t* buf, uint32_t len) const = 0; + virtual uint32_t capacity() const = 0; + virtual uint32_t staged_size() const = 0; // total_size from begin(), 0 if none + virtual void clear() = 0; + + // Optional: declare the size of the leading metadata (header + manifest + merkle leaves, i.e. + // everything before the payload). A flash-backed store keeps that region — which is updated + // throughout the transfer (a leaf is committed per block) — pinned in one RAM page, so it can + // flush the bulk payload page-by-page without re-erasing the leaves' page on every block. + // Returns false if the metadata won't fit the store's pinned region (transfer is then refused). + virtual bool set_meta_size(uint32_t meta_bytes) { (void)meta_bytes; return true; } + + // Optional: commit any RAM-buffered data to persistent storage. Called once when the transfer + // reaches COMPLETE (radio idle), so a flash store does its page writes off the RX critical path. + // After this returns, a flash store's data() view is coherent. No-op for purely in-RAM stores. + virtual void finalize() {} +}; + +// Fixed-capacity RAM store — for native tests and device bring-up of the transfer/verify path. +// (Does NOT survive reboot; a persistent flash store replaces it for production — see D1.) +template +class OtaStoreRam : public OtaStore { + uint8_t _buf[CAP]; + uint32_t _total = 0; +public: + bool begin(uint32_t total_size) override { + if (total_size > CAP) return false; + _total = total_size; + memset(_buf, 0xFF, total_size); // mimic erased flash (so unfilled leaf slots read as 'missing') + return true; + } + bool write(uint32_t off, const uint8_t* d, uint32_t len) override { + if ((uint64_t)off + len > _total) return false; + memcpy(_buf + off, d, len); + return true; + } + bool read(uint32_t off, uint8_t* b, uint32_t len) const override { + if ((uint64_t)off + len > _total) return false; + memcpy(b, _buf + off, len); + return true; + } + uint32_t capacity() const override { return CAP; } + uint32_t staged_size() const override { return _total; } + void clear() override { _total = 0; } + const uint8_t* data() const { return _buf; } // contiguous view (RAM store only) +}; + +} // namespace ota +} // namespace mesh diff --git a/src/helpers/ota/OtaStoreFlashNrf52.cpp b/src/helpers/ota/OtaStoreFlashNrf52.cpp new file mode 100644 index 00000000..5bce71fe --- /dev/null +++ b/src/helpers/ota/OtaStoreFlashNrf52.cpp @@ -0,0 +1,121 @@ +#include "OtaStoreFlashNrf52.h" + +#if defined(NRF52_PLATFORM) && defined(OTA_FLASH_STORE) + +#include "OtaSelf.h" +#include "OtaDebug.h" +#include +#include "flash/flash_nrf5x.h" // Adafruit core internal-flash driver (SoftDevice-safe; LittleFS path) + +namespace mesh { +namespace ota { + +// Write one whole 4 KB page from `buf` to flash (erase + program, ~85 ms). `buf` is PG bytes, 0xFF-padded +// past the container, so the program is clean. The last container page ends exactly at FS_START (both +// FS_START and _write_start are page-aligned and the container ends <= FS_START), so a full-page write +// never reaches into ExtraFS. +void OtaStoreFlashNrf52::flush_page(uint32_t page_idx, const uint8_t* buf) { + uint32_t addr = _write_start + page_idx * PG; + if (addr + PG > MOTA_NRF52_FS_START) return; // defensive: never cross the staging ceiling + OTA_DBG("OTA flash: write page %u @ %08x\n", (unsigned)page_idx, (unsigned)addr); + flash_nrf5x_write(addr, buf, PG); + flash_nrf5x_flush(); +} + +void OtaStoreFlashNrf52::flush_pay() { + if (_pay_idx != 0) flush_page(_pay_idx, _pay_page); // _pay_idx 0 == no payload page open +} + +uint32_t OtaStoreFlashNrf52::run(uint32_t pos, uint32_t remain) const { + uint32_t trailer = _total - 5; // container is always >= 13 bytes (begin checks) + if (pos >= trailer) return remain; // tail: caller bounds remain to <= 5 already + uint32_t end = pos + remain; + uint32_t page_end = (pos / PG + 1) * PG; + if (end > page_end) end = page_end; // a run stays within one flash page, + if (end > trailer) end = trailer; // and never crosses into the trailer tail + return end - pos; +} + +const uint8_t* OtaStoreFlashNrf52::read_slot(uint32_t pos) const { + if (pos >= _total - 5) return _trailer + (pos - (_total - 5)); // trailer tail (RAM until finalize) + uint32_t page = pos / PG; + if (page == 0) return _meta_page + pos; // pinned page 0 (incl. leaves) + if (page == _pay_idx) return _pay_page + (pos - page * PG); // current sliding payload page + return (const uint8_t*)(uintptr_t)(_write_start + pos); // already flushed -> memory-mapped +} + +uint8_t* OtaStoreFlashNrf52::write_slot(uint32_t pos) { + if (pos >= _total - 5) return _trailer + (pos - (_total - 5)); + uint32_t page = pos / PG; + if (page == 0) return _meta_page + pos; + if (page > _pay_idx) { flush_pay(); _pay_idx = page; memset(_pay_page, 0xFF, PG); } // advance, fresh page + if (page == _pay_idx) return _pay_page + (pos - page * PG); + return nullptr; // page < _pay_idx: already flushed +} + +bool OtaStoreFlashNrf52::begin(uint32_t total_size) { + clear(); + if (total_size < 13 || total_size > capacity()) return false; // 13 = header(8) + trailer(5) + + // bottom-align against FS_START so the trailer ends exactly at FS_START (bootloader scans for it) + uint32_t start = (MOTA_NRF52_FS_START - total_size) & ~(PG - 1); + + // never collide with the running application image (its extent comes from its EndF trailer) + uint32_t app_end = MOTA_NRF52_APP_BASE; + SelfFwInfo fi; + if (ota_self_firmware(fi) && fi.valid) app_end = MOTA_NRF52_APP_BASE + fi.image_len; + if (start < app_end) return false; + + _write_start = start; + _total = total_size; + memset(_meta_page, 0xFF, PG); // assemble page 0 in RAM; 0xFF = erased sentinel (unfilled leaf slots) + memset(_trailer, 0xFF, sizeof(_trailer)); + _pay_idx = 0; + _flushed = false; + OTA_DBG("OTA flash: begin total=%u start=%08x app_end=%08x\n", + (unsigned)total_size, (unsigned)start, (unsigned)app_end); + return true; // no pre-erase: each page is erased by its own (single) flush +} + +bool OtaStoreFlashNrf52::write(uint32_t offset, const uint8_t* d, uint32_t len) { + if ((uint64_t)offset + len > _total) return false; + for (uint32_t pos = offset, end = offset + len; pos < end; ) { + uint32_t n = run(pos, end - pos); + if (uint8_t* dst = write_slot(pos)) { + memcpy(dst, d, n); + } else { + // out-of-order write to an already-flushed page: read-modify-write straight to flash. Safe -- the + // driver erases the page before programming, so re-touching it never breaks writes-per-word. + OTA_DBG("OTA flash: RMW page %u (out-of-order) @ off %u\n", (unsigned)(pos / PG), (unsigned)pos); + if (flash_nrf5x_write(_write_start + pos, d, n) < 0) return false; + flash_nrf5x_flush(); + } + pos += n; d += n; + } + return true; +} + +bool OtaStoreFlashNrf52::read(uint32_t offset, uint8_t* buf, uint32_t len) const { + if ((uint64_t)offset + len > _total) return false; + for (uint32_t pos = offset, end = offset + len; pos < end; ) { + uint32_t n = run(pos, end - pos); + memcpy(buf, read_slot(pos), n); + pos += n; buf += n; + } + return true; +} + +void OtaStoreFlashNrf52::finalize() { + if (_flushed || _total == 0) return; + OTA_DBG("OTA flash: finalize total=%u\n", (unsigned)_total); + flush_pay(); // the last (highest) payload page, if one is open + flush_page(0, _meta_page); // page 0: header + manifest + leaves + first payload bytes + flash_nrf5x_write(_write_start + _total - 5, _trailer, 5); // trailer tail (radio idle at COMPLETE) + flash_nrf5x_flush(); + _flushed = true; +} + +} // namespace ota +} // namespace mesh + +#endif diff --git a/src/helpers/ota/OtaStoreFlashNrf52.h b/src/helpers/ota/OtaStoreFlashNrf52.h new file mode 100644 index 00000000..16268b62 --- /dev/null +++ b/src/helpers/ota/OtaStoreFlashNrf52.h @@ -0,0 +1,75 @@ +#pragma once + +#if defined(NRF52_PLATFORM) && defined(OTA_FLASH_STORE) + +#include "OtaStore.h" +#include "OtaFlashLayout_nrf52.h" + +// Persistent flash-backed OtaStore for nRF52 (RAK4631). Stages the received `.mota` in the free flash +// below the primary LittleFS (FS_START), bottom-aligned so its trailer ends at FS_START and the +// bootloader can scan for it. Survives reboot — the whole point — so the bootloader can apply the +// staged delta on the next boot. +// +// RAM is bounded to O(one flash page), NEVER O(mota): a 100 KB+ delta must not live in RAM. +// - On nRF52 the flash *erase* unit is one 4 KB page and the only SoftDevice-safe writer +// (Adafruit `flash_nrf5x`) erases the whole page on every flush (~85 ms, CPU stalled → LoRa RX +// starved). Writing to flash per received packet therefore drops in-flight DATA and the transfer +// stalls. The fix: coalesce to the *page*, the hardware-natural unit, and write each page once. +// - `_meta_page` pins flash page 0 (header + manifest + the merkle-leaf progress markers, which are +// written one-per-block all transfer long). Keeping it in RAM means streaming the payload never +// re-erases the leaves' page. Flushed once at finalize(). Requires metadata <= one page +// (set_meta_size enforces it; true for <= ~979 blocks, i.e. any realistic MeshCore image). +// - `_pay_page` is a single sliding buffer for one payload page (index >= 1). It advances +// monotonically with the (mostly in-order) block stream and flushes the page it leaves behind. +// Rare out-of-order writes to an already-flushed page go straight to flash as a safe read-modify- +// write (flash_nrf5x erases before programming, so re-touching a page never violates the +// writes-per-word limit — it just costs one extra erase). +// - The 5-byte trailer is buffered and written at finalize(). +// Net: flash is touched ~once per 4 KB page (≈ 1 per 4 blocks at 1 KB), off the per-packet path; page +// 0 and the last page are written at finalize() with the radio idle. For a small delta (whole .mota +// in page 0) there is ZERO flash I/O during the transfer. + +namespace mesh { +namespace ota { + +class OtaStoreFlashNrf52 : public OtaStore { + static const uint32_t PG = MOTA_NRF52_FLASH_PAGE; // 4096 + + uint32_t _write_start = 0; // flash address of container offset 0 (page-aligned) + uint32_t _total = 0; // staged container size (0 = none) + bool _flushed = false; // finalize() committed everything to flash + + uint8_t _meta_page[PG]; // pinned flash page 0 (header + manifest + leaves + 1st payload) + uint8_t _pay_page[PG]; // sliding buffer for one payload page (index _pay_idx) + uint32_t _pay_idx = 0; // page index currently held in _pay_page (0 = none open; pages >= 1) + uint8_t _trailer[5]; // last 5 container bytes (kept in RAM, written at finalize) + + // Bytes from `pos` that stay in one store region (a single flash page, or the trailer tail). + uint32_t run(uint32_t pos, uint32_t remain) const; + // RAM home of byte `pos`: read_slot always resolves (flushed pages → memory-mapped flash); write_slot + // opens/advances the sliding payload page and returns nullptr if `pos` is in an already-flushed page. + const uint8_t* read_slot(uint32_t pos) const; + uint8_t* write_slot(uint32_t pos); + void flush_pay(); // commit _pay_page to flash (erase + program, one page) + void flush_page(uint32_t page_idx, const uint8_t* buf); // write a full page to flash + +public: + bool begin(uint32_t total_size) override; + bool write(uint32_t offset, const uint8_t* data, uint32_t len) override; + bool read(uint32_t offset, uint8_t* buf, uint32_t len) const override; + uint32_t capacity() const override { return MOTA_NRF52_FS_START - MOTA_NRF52_APP_BASE; } + uint32_t staged_size() const override { return _total; } + void clear() override { _total = 0; _pay_idx = 0; _flushed = false; } + bool set_meta_size(uint32_t meta_bytes) override { return meta_bytes <= PG; } // leaves must fit page 0 + void finalize() override; + + // Contiguous view (flash is memory-mapped). VALID ONLY AFTER finalize() — before that, page 0 and the + // tail are still in RAM. OtaManager/OtaCli/verify use this only once the transfer is COMPLETE. + const uint8_t* data() const { return (const uint8_t*)(uintptr_t)_write_start; } + uint32_t write_start() const { return _write_start; } +}; + +} // namespace ota +} // namespace mesh + +#endif diff --git a/src/helpers/ota/OtaVerify.cpp b/src/helpers/ota/OtaVerify.cpp new file mode 100644 index 00000000..42b6d53f --- /dev/null +++ b/src/helpers/ota/OtaVerify.cpp @@ -0,0 +1,27 @@ +#include "OtaVerify.h" +#include "MerkleTree.h" +#include "Multihash.h" +#include "Identity.h" + +namespace mesh { +namespace ota { + +VerifyResult ota_verify(const uint8_t* buf, uint32_t len, const SignerAllowlist& allow) { + VerifyResult r; + MotaManifest m; + if (!mota_parse(buf, len, m)) return r; + r.parsed = true; + r.root_ok = mota_check_root(m); + r.image_ok = m.is_full() ? mota_check_image_hash_full(m) + : true; // delta image_hash needs the base; verified at apply time + r.is_signed = m.is_signed(); + if (r.is_signed) { + mesh::Identity signer(m.signer_pubkey); + r.sig_ok = signer.verify(m.signature, m.manifest_start, (int)m.signed_len); + r.trusted = r.sig_ok && allow.contains(m.signer_pubkey); + } + return r; +} + +} // namespace ota +} // namespace mesh diff --git a/src/helpers/ota/OtaVerify.h b/src/helpers/ota/OtaVerify.h new file mode 100644 index 00000000..85fb9683 --- /dev/null +++ b/src/helpers/ota/OtaVerify.h @@ -0,0 +1,29 @@ +#pragma once + +#include "MotaContainer.h" +#include "SignerAllowlist.h" + +// Full verification of a staged `.mota` (device-side: uses Ed25519 via mesh::Identity, so NOT compiled +// on the native host — the portable integrity checks live in MotaContainer and are unit-tested there). + +namespace mesh { +namespace ota { + +struct VerifyResult { + bool parsed = false; // container + manifest parsed + bool root_ok = false; // merkle_root recomputed from leaves[] matches + bool image_ok = false; // full: sha2-256(payload)==image_hash; delta: deferred to apply (set true) + bool is_signed = false; + bool sig_ok = false; // Ed25519 signature valid for signer_pubkey + bool trusted = false; // signer_pubkey is in the allowlist + + // Integrity holds (safe to keep/serve). For a signed image, the signature must also verify. + bool integrity_ok() const { return parsed && root_ok && image_ok && (!is_signed || sig_ok); } + // Eligible for AUTO-apply: integrity + signed by an allowlisted key (decision D2). + bool auto_appliable() const { return integrity_ok() && is_signed && sig_ok && trusted; } +}; + +VerifyResult ota_verify(const uint8_t* buf, uint32_t len, const SignerAllowlist& allow); + +} // namespace ota +} // namespace mesh diff --git a/src/helpers/ota/SignerAllowlist.h b/src/helpers/ota/SignerAllowlist.h new file mode 100644 index 00000000..726644fd --- /dev/null +++ b/src/helpers/ota/SignerAllowlist.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include + +// Runtime-managed allowlist of trusted Ed25519 firmware-signer public keys (docs/ota_protocol.md §9, +// decision D2 + Q1: no key embedded in firmware; only allowlist-signed firmware may auto-apply). +// Portable + fixed-capacity (no dynamic allocation). Persistence (load/save) is layered on per-platform. + +namespace mesh { +namespace ota { + +#ifndef MAX_OTA_SIGNERS +#define MAX_OTA_SIGNERS 4 +#endif + +class SignerAllowlist { + uint8_t _keys[MAX_OTA_SIGNERS][32]; + uint8_t _count = 0; + +public: + void clear() { _count = 0; } + uint8_t count() const { return _count; } + const uint8_t* get(uint8_t i) const { return (i < _count) ? _keys[i] : nullptr; } + + bool contains(const uint8_t* pub) const { + for (uint8_t i = 0; i < _count; i++) + if (memcmp(_keys[i], pub, 32) == 0) return true; + return false; + } + + // Add a key (idempotent). Returns false if the list is full. + bool add(const uint8_t* pub) { + if (contains(pub)) return true; + if (_count >= MAX_OTA_SIGNERS) return false; + memcpy(_keys[_count++], pub, 32); + return true; + } + + bool remove(const uint8_t* pub) { + for (uint8_t i = 0; i < _count; i++) { + if (memcmp(_keys[i], pub, 32) == 0) { + memmove(_keys[i], _keys[i + 1], (size_t)(_count - i - 1) * 32); + _count--; + return true; + } + } + return false; + } + + // Serialize as: count(1) || key0(32) || key1(32) ... Returns bytes written. + uint32_t serialize(uint8_t* out, uint32_t max_len) const { + uint32_t need = 1 + (uint32_t)_count * 32; + if (max_len < need) return 0; + out[0] = _count; + memcpy(out + 1, _keys, (size_t)_count * 32); + return need; + } + + bool deserialize(const uint8_t* in, uint32_t len) { + if (len < 1) return false; + uint8_t n = in[0]; + if (n > MAX_OTA_SIGNERS || (uint32_t)1 + n * 32 > len) return false; + _count = n; + memcpy(_keys, in + 1, (size_t)n * 32); + return true; + } +}; + +} // namespace ota +} // namespace mesh diff --git a/src/helpers/ota/detools/README.meshcore.txt b/src/helpers/ota/detools/README.meshcore.txt new file mode 100644 index 00000000..770733aa --- /dev/null +++ b/src/helpers/ota/detools/README.meshcore.txt @@ -0,0 +1,42 @@ +Vendored detools embeddable C decoder +===================================== + +Source : https://github.com/eerimoq/detools (tag 0.53.0, c/ directory) +Files : detools.c, detools.h +License : BSD 2-Clause (Erik Moqvist; original bsdiff (c) Colin Percival). + sais.c (host packager only, not vendored) is MIT. + Compatible with MeshCore's MIT license; notices retained in-file. + +Why vendored +------------ +The detools PyPI package (used by tools/mota to *create* patches with +detools.create_patch) ships only the Python library + patch-creation C +extensions -- NOT the embeddable decoder. The on-device delta applier needs +detools' own decoder, so we vendor c/detools.{c,h} verbatim. This is detools' +official C implementation; MeshCore does not reimplement the delta codec. + +Local modifications +------------------- +Only the config defaults at the top of detools.h were changed (upstream = 1): + DETOOLS_CONFIG_FILE_IO -> 0 (no file IO on device) + DETOOLS_CONFIG_COMPRESSION_LZMA -> 0 (would need liblzma) + DETOOLS_CONFIG_COMPRESSION_HEATSHRINK -> 0 (would need malloc + heatshrink/) + DETOOLS_CONFIG_COMPRESSION_NONE = 1 (kept) + DETOOLS_CONFIG_COMPRESSION_CRLE = 1 (kept) +detools.c is byte-for-byte upstream. + +With this config the decoder is self-contained (no malloc, no liblzma, no +heatshrink/, no file IO) and applies `--codec sequential --compression crle` +patches, which is what tools/mota produces for MeshCore .mota deltas. + +Usage on device +--------------- +src/helpers/ota/OtaApply.cpp wraps detools_apply_patch_callbacks(): + from_read/from_seek -> running OTA slot (the delta base, via esp_partition_read) + patch_read -> the .mota payload held in RAM (fetched over LoRa) + to_write -> inactive OTA slot (via esp_ota_write) + running SHA-256 +The decoded image is verified against the signed manifest image_hash before the +slot is armed as boot partition. + +To update: re-copy c/detools.{c,h} from the pinned detools tag and re-apply the +three config-default edits above. diff --git a/src/helpers/ota/detools/detools.c b/src/helpers/ota/detools/detools.c new file mode 100644 index 00000000..5d254895 --- /dev/null +++ b/src/helpers/ota/detools/detools.c @@ -0,0 +1,2783 @@ +/** + * BSD 2-Clause License + * + * Copyright (c) 2019-2020, Erik Moqvist + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include "detools.h" + +/* Patch types. */ +#define PATCH_TYPE_SEQUENTIAL 0 +#define PATCH_TYPE_IN_PLACE 1 + +/* Compressions. */ +#define COMPRESSION_NONE 0 +#define COMPRESSION_LZMA 1 +#define COMPRESSION_CRLE 2 +#define COMPRESSION_HEATSHRINK 4 + +#define MIN(x, y) (((x) < (y)) ? (x) : (y)) +#define MAX(x, y) (((x) > (y)) ? (x) : (y)) +#define DIV_CEIL(n, d) (((n) + (d) - 1) / (d)) + +/* + * Utility functions. + */ + +static size_t chunk_left(struct detools_apply_patch_chunk_t *self_p) +{ + return (self_p->size - self_p->offset); +} + +static bool chunk_available(struct detools_apply_patch_chunk_t *self_p) +{ + return (chunk_left(self_p) > 0); +} + +static uint8_t chunk_get_no_check(struct detools_apply_patch_chunk_t *self_p) +{ + uint8_t data; + + data = self_p->buf_p[self_p->offset]; + self_p->offset++; + + return (data); +} + +static int chunk_get(struct detools_apply_patch_chunk_t *self_p, + uint8_t *data_p) +{ + if (!chunk_available(self_p)) { + return (1); + } + + *data_p = chunk_get_no_check(self_p); + + return (0); +} + +#if DETOOLS_CONFIG_COMPRESSION_NONE == 1 \ + || DETOOLS_CONFIG_COMPRESSION_CRLE == 1 \ + || DETOOLS_CONFIG_COMPRESSION_LZMA == 1 + +static void chunk_read_all_no_check(struct detools_apply_patch_chunk_t *self_p, + uint8_t *buf_p, + size_t size) +{ + memcpy(buf_p, &self_p->buf_p[self_p->offset], size); + self_p->offset += size; +} + +#endif + +#if DETOOLS_CONFIG_COMPRESSION_NONE == 1 \ + || DETOOLS_CONFIG_COMPRESSION_CRLE == 1 + +static int chunk_read(struct detools_apply_patch_chunk_t *self_p, + uint8_t *buf_p, + size_t *size_p) +{ + if (!chunk_available(self_p)) { + return (1); + } + + *size_p = MIN(*size_p, chunk_left(self_p)); + chunk_read_all_no_check(self_p, buf_p, *size_p); + + return (0); +} + +#endif + +static bool is_overflow(int value) +{ + return ((value + 7) > (int)(8 * sizeof(int))); +} + +static int chunk_unpack_header_size(struct detools_apply_patch_chunk_t *self_p, + struct detools_apply_patch_size_t *size_state_p, + int *size_p) +{ + int res; + uint8_t byte; + + do { + switch (size_state_p->state) { + + case detools_unpack_usize_state_first_t: + res = chunk_get(self_p, &byte); + + if (res != 0) { + return (res); + } + + size_state_p->value = (byte & 0x3f); + size_state_p->offset = 6; + size_state_p->state = detools_unpack_usize_state_consecutive_t; + break; + + case detools_unpack_usize_state_consecutive_t: + res = chunk_get(self_p, &byte); + + if (res != 0) { + return (res); + } + + if (is_overflow(size_state_p->offset)) { + return (-DETOOLS_CORRUPT_PATCH_OVERFLOW); + } + + size_state_p->value |= ((byte & 0x7f) << size_state_p->offset); + size_state_p->offset += 7; + break; + + default: + return (-DETOOLS_INTERNAL_ERROR); + } + } while ((byte & 0x80) != 0); + + /* Done, fix sign. */ + size_state_p->state = detools_unpack_usize_state_first_t; + + *size_p = size_state_p->value; + + return (res); +} + +/* + * None patch reader. + */ + +#if DETOOLS_CONFIG_COMPRESSION_NONE == 1 + +static int patch_reader_none_decompress( + struct detools_apply_patch_patch_reader_t *self_p, + uint8_t *buf_p, + size_t *size_p) +{ + int res; + struct detools_apply_patch_patch_reader_none_t *none_p; + + none_p = &self_p->compression.none; + + if (none_p->patch_offset + *size_p > none_p->patch_size) { + return (-DETOOLS_CORRUPT_PATCH); + } + + res = chunk_read(self_p->patch_chunk_p, + buf_p, + size_p); + + if (res != 0) { + return (res); + } + + none_p->patch_offset += *size_p; + + return (0); +} + +static int patch_reader_none_destroy( + struct detools_apply_patch_patch_reader_t *self_p) +{ + struct detools_apply_patch_patch_reader_none_t *none_p; + + none_p = &self_p->compression.none; + + if (none_p->patch_offset == none_p->patch_size) { + return (0); + } else { + return (-DETOOLS_CORRUPT_PATCH); + } +} + +static int patch_reader_none_init(struct detools_apply_patch_patch_reader_t *self_p, + size_t patch_size) +{ + struct detools_apply_patch_patch_reader_none_t *none_p; + + none_p = &self_p->compression.none; + none_p->patch_size = patch_size; + none_p->patch_offset = 0; + self_p->destroy = patch_reader_none_destroy; + self_p->decompress = patch_reader_none_decompress; + + return (0); +} + +#endif + +/* + * Heatshrink patch reader. + */ + +#if DETOOLS_CONFIG_COMPRESSION_HEATSHRINK == 1 + +static void unpack_heatshrink_header(uint8_t byte, + int8_t *window_sz2_p, + int8_t *lookahead_sz2_p) +{ + *window_sz2_p = (((byte >> 4) & 0xf) + 4); + *lookahead_sz2_p = ((byte & 0xf) + 3); +} + +static int patch_reader_heatshrink_decompress( + struct detools_apply_patch_patch_reader_t *self_p, + uint8_t *buf_p, + size_t *size_p) +{ + int res; + struct detools_apply_patch_patch_reader_heatshrink_t *heatshrink_p; + size_t size; + size_t left; + HSD_poll_res pres; + HSD_sink_res sres; + uint8_t byte; + + heatshrink_p = &self_p->compression.heatshrink; + left = *size_p; + + if (heatshrink_p->window_sz2 == -1) { + res = chunk_get(self_p->patch_chunk_p, &byte); + + if (res != 0) { + return (1); + } + + unpack_heatshrink_header(byte, + &heatshrink_p->window_sz2, + &heatshrink_p->lookahead_sz2); + +#if HEATSHRINK_DYNAMIC_ALLOC == 1 + heatshrink_p->decoder_p = heatshrink_decoder_alloc( + 256, + heatshrink_p->window_sz2, + heatshrink_p->lookahead_sz2); + + if (heatshrink_p->decoder_p == NULL) { + return (-DETOOLS_HEATSHRINK_HEADER); + } +#else + if ((heatshrink_p->window_sz2 != HEATSHRINK_STATIC_WINDOW_BITS) + || (heatshrink_p->lookahead_sz2 != HEATSHRINK_STATIC_LOOKAHEAD_BITS)) { + return (-DETOOLS_HEATSHRINK_HEADER); + } + + heatshrink_p->decoder_p = &heatshrink_p->decoder; + heatshrink_decoder_reset(heatshrink_p->decoder_p); +#endif + + } + + while (1) { + /* Get available data. */ + pres = heatshrink_decoder_poll(heatshrink_p->decoder_p, + buf_p, + left, + &size); + + if (pres < 0) { + return (-DETOOLS_HEATSHRINK_POLL); + } + + buf_p += size; + left -= size; + + if (left == 0) { + return (0); + } + + /* Input (sink) more data if available. */ + res = chunk_get(self_p->patch_chunk_p, &byte); + + if (res == 0) { + sres = heatshrink_decoder_sink(heatshrink_p->decoder_p, + &byte, + sizeof(byte), + &size); + + if ((sres < 0) || (size != sizeof(byte))) { + return (-DETOOLS_HEATSHRINK_SINK); + } + } else { + if (left != *size_p) { + *size_p -= left; + + return (0); + } else { + return (1); + } + } + } + + return (res); +} + +static int patch_reader_heatshrink_destroy( + struct detools_apply_patch_patch_reader_t *self_p) +{ + struct detools_apply_patch_patch_reader_heatshrink_t *heatshrink_p; + HSD_finish_res fres; + + heatshrink_p = &self_p->compression.heatshrink; + + if (heatshrink_p->decoder_p == NULL) { + return (0); + } + + fres = heatshrink_decoder_finish(heatshrink_p->decoder_p); + +#if HEATSHRINK_DYNAMIC_ALLOC == 1 + heatshrink_decoder_free(heatshrink_p->decoder_p); +#endif + + if (fres == HSDR_FINISH_DONE) { + return (0); + } else { + return (-DETOOLS_CORRUPT_PATCH); + } +} + +static int patch_reader_heatshrink_init( + struct detools_apply_patch_patch_reader_t *self_p) +{ + struct detools_apply_patch_patch_reader_heatshrink_t *heatshrink_p; + + heatshrink_p = &self_p->compression.heatshrink; + heatshrink_p->window_sz2 = -1; + heatshrink_p->lookahead_sz2 = -1; + heatshrink_p->decoder_p = NULL; + self_p->destroy = patch_reader_heatshrink_destroy; + self_p->decompress = patch_reader_heatshrink_decompress; + + return (0); +} + +#endif + +/* + * LZMA patch reader. + */ + +#if DETOOLS_CONFIG_COMPRESSION_LZMA == 1 + +static int get_decompressed_data( + struct detools_apply_patch_patch_reader_lzma_t *lzma_p, + uint8_t *buf_p, + size_t size) +{ + int res; + + if (lzma_p->output_size >= size) { + memcpy(buf_p, lzma_p->output_p, size); + memmove(lzma_p->output_p, + &lzma_p->output_p[size], + lzma_p->output_size - size); + lzma_p->output_size -= size; + res = 0; + } else { + res = 1; + } + + return (res); +} + +static int prepare_input_buffer(struct detools_apply_patch_patch_reader_t *self_p) +{ + struct detools_apply_patch_patch_reader_lzma_t *lzma_p; + uint8_t *next_p; + size_t left; + + lzma_p = &self_p->compression.lzma; + left = chunk_left(self_p->patch_chunk_p); + + if (left == 0) { + return (1); + } + + next_p = malloc(lzma_p->stream.avail_in + left); + + if (next_p == NULL) { + return (-DETOOLS_OUT_OF_MEMORY); + } + + if (lzma_p->stream.next_in != NULL) { + memcpy(next_p, lzma_p->stream.next_in, lzma_p->stream.avail_in); + free(lzma_p->input_p); + } + + lzma_p->input_p = next_p; + chunk_read_all_no_check(self_p->patch_chunk_p, + &lzma_p->input_p[lzma_p->stream.avail_in], + left); + lzma_p->stream.next_in = next_p; + lzma_p->stream.avail_in += left; + + return (0); +} + +static int prepare_output_buffer(struct detools_apply_patch_patch_reader_t *self_p, + size_t size) +{ + struct detools_apply_patch_patch_reader_lzma_t *lzma_p; + uint8_t *output_p; + + lzma_p = &self_p->compression.lzma; + + output_p = malloc(size); + + if (output_p == NULL) { + return (-DETOOLS_OUT_OF_MEMORY); + } + + if (lzma_p->output_p != NULL) { + memcpy(output_p, lzma_p->output_p, lzma_p->output_size); + free(lzma_p->output_p); + } + + lzma_p->output_p = output_p; + lzma_p->stream.next_out = (output_p + lzma_p->output_size); + lzma_p->stream.avail_out = (size - lzma_p->output_size); + + return (0); +} + +static int patch_reader_lzma_decompress( + struct detools_apply_patch_patch_reader_t *self_p, + uint8_t *buf_p, + size_t *size_p) +{ + int res; + struct detools_apply_patch_patch_reader_lzma_t *lzma_p; + lzma_ret ret; + + lzma_p = &self_p->compression.lzma; + + /* Check if enough decompressed data is available. */ + res = get_decompressed_data(lzma_p, buf_p, *size_p); + + if (res == 0) { + return (res); + } + + while (1) { + /* Try to decompress requested data. */ + if (lzma_p->stream.avail_in > 0) { + res = prepare_output_buffer(self_p, *size_p); + + if (res != 0) { + return (res); + } + + ret = lzma_code(&lzma_p->stream, LZMA_RUN); + + switch (ret) { + + case LZMA_OK: + case LZMA_STREAM_END: + break; + + default: + return (-DETOOLS_LZMA_DECODE); + } + + lzma_p->output_size = (size_t)(lzma_p->stream.next_out - lzma_p->output_p); + } + + /* Check if enough decompressed data is available. */ + res = get_decompressed_data(lzma_p, buf_p, *size_p); + + if (res == 0) { + return (res); + } + + /* Get more data to decompress. */ + res = prepare_input_buffer(self_p); + + if (res != 0) { + return (res); + } + } +} + +static int patch_reader_lzma_destroy( + struct detools_apply_patch_patch_reader_t *self_p) +{ + struct detools_apply_patch_patch_reader_lzma_t *lzma_p; + + lzma_p = &self_p->compression.lzma; + + if (lzma_p->input_p != NULL) { + free(lzma_p->input_p); + } + + if (lzma_p->output_p != NULL) { + free(lzma_p->output_p); + } + + lzma_end(&lzma_p->stream); + + if ((lzma_p->stream.avail_in == 0) && (lzma_p->output_size == 0)) { + return (0); + } else { + return (-DETOOLS_CORRUPT_PATCH); + } +} + +static int patch_reader_lzma_init(struct detools_apply_patch_patch_reader_t *self_p) +{ + lzma_ret ret; + struct detools_apply_patch_patch_reader_lzma_t *lzma_p; + + lzma_p = &self_p->compression.lzma; + memset(&lzma_p->stream, 0, sizeof(lzma_p->stream)); + + ret = lzma_alone_decoder(&lzma_p->stream, UINT64_MAX); + + if (ret != LZMA_OK) { + return (-DETOOLS_LZMA_INIT); + } + + lzma_p->input_p = NULL; + lzma_p->output_p = NULL; + lzma_p->output_size = 0; + self_p->destroy = patch_reader_lzma_destroy; + self_p->decompress = patch_reader_lzma_decompress; + + return (0); +} + +#endif + +/* + * CRLE patch reader. + */ + +#if DETOOLS_CONFIG_COMPRESSION_CRLE == 1 + +static void unpack_usize_init(struct detools_unpack_usize_t *self_p) +{ + self_p->state = detools_unpack_usize_state_first_t; + self_p->value = 0; + self_p->offset = 0; +} + +static int unpack_usize(struct detools_unpack_usize_t *self_p, + struct detools_apply_patch_chunk_t *patch_chunk_p, + int *size_p) +{ + int res; + uint8_t byte; + + switch (self_p->state) { + + case detools_unpack_usize_state_first_t: + self_p->value = 0; + self_p->offset = 0; + self_p->state = detools_unpack_usize_state_consecutive_t; + break; + + case detools_unpack_usize_state_consecutive_t: + break; + + default: + return (-DETOOLS_INTERNAL_ERROR); + } + + do { + res = chunk_get(patch_chunk_p, &byte); + + if (res != 0) { + return (res); + } + + if (is_overflow(self_p->offset)) { + return (-DETOOLS_CORRUPT_PATCH_OVERFLOW); + } + + self_p->value |= ((byte & 0x7f) << self_p->offset); + self_p->offset += 7; + } while ((byte & 0x80) != 0); + + *size_p = self_p->value; + + return (0); +} + +static int patch_reader_crle_decompress_idle( + struct detools_apply_patch_patch_reader_t *self_p, + struct detools_apply_patch_patch_reader_crle_t *crle_p) +{ + int res; + uint8_t kind; + + res = chunk_get(self_p->patch_chunk_p, &kind); + + if (res != 0) { + return (res); + } + + res = 2; + + switch (kind) { + + case 0: + crle_p->state = detools_crle_state_scattered_size_t; + unpack_usize_init(&crle_p->kind.scattered.size); + break; + + case 1: + crle_p->state = detools_crle_state_repeated_repetitions_t; + unpack_usize_init(&crle_p->kind.repeated.size); + break; + + default: + res = -DETOOLS_CORRUPT_PATCH_CRLE_KIND; + break; + } + + return (res); +} + +static int patch_reader_crle_decompress_scattered_size( + struct detools_apply_patch_patch_reader_t *self_p, + struct detools_apply_patch_patch_reader_crle_t *crle_p) +{ + int res; + int size; + + res = unpack_usize(&crle_p->kind.scattered.size, + self_p->patch_chunk_p, + &size); + + if (res != 0) { + return (res); + } + + crle_p->state = detools_crle_state_scattered_data_t; + crle_p->kind.scattered.number_of_bytes_left = (size_t)size; + + return (2); +} + +static int patch_reader_crle_decompress_scattered_data( + struct detools_apply_patch_patch_reader_t *self_p, + struct detools_apply_patch_patch_reader_crle_t *crle_p, + uint8_t *buf_p, + size_t *size_p) +{ + int res; + + *size_p = MIN(*size_p, crle_p->kind.scattered.number_of_bytes_left); + res = chunk_read(self_p->patch_chunk_p, buf_p, size_p); + + if (res != 0) { + return (res); + } + + crle_p->kind.scattered.number_of_bytes_left -= *size_p; + + if (crle_p->kind.scattered.number_of_bytes_left == 0) { + crle_p->state = detools_crle_state_idle_t; + } + + return (0); +} + +static int patch_reader_crle_decompress_repeated_repetitions( + struct detools_apply_patch_patch_reader_t *self_p, + struct detools_apply_patch_patch_reader_crle_t *crle_p) +{ + int res; + int repetitions; + + res = unpack_usize(&crle_p->kind.repeated.size, + self_p->patch_chunk_p, + &repetitions); + + if (res != 0) { + return (res); + } + + crle_p->state = detools_crle_state_repeated_data_t; + crle_p->kind.repeated.number_of_bytes_left = (size_t)repetitions; + + return (2); +} + +static int patch_reader_crle_decompress_repeated_data( + struct detools_apply_patch_patch_reader_t *self_p, + struct detools_apply_patch_patch_reader_crle_t *crle_p) +{ + int res; + + res = chunk_get(self_p->patch_chunk_p, + &crle_p->kind.repeated.value); + + if (res != 0) { + return (res); + } + + crle_p->state = detools_crle_state_repeated_data_read_t; + + return (2); +} + +static int patch_reader_crle_decompress_repeated_data_read( + struct detools_apply_patch_patch_reader_crle_t *crle_p, + uint8_t *buf_p, + size_t *size_p) +{ + size_t size; + size_t i; + + size = MIN(*size_p, crle_p->kind.repeated.number_of_bytes_left); + + for (i = 0; i < size; i++) { + buf_p[i] = crle_p->kind.repeated.value; + } + + *size_p = size; + crle_p->kind.repeated.number_of_bytes_left -= size; + + if (crle_p->kind.repeated.number_of_bytes_left == 0) { + crle_p->state = detools_crle_state_idle_t; + } + + return (0); +} + +static int patch_reader_crle_decompress( + struct detools_apply_patch_patch_reader_t *self_p, + uint8_t *buf_p, + size_t *size_p) +{ + int res; + struct detools_apply_patch_patch_reader_crle_t *crle_p; + + crle_p = &self_p->compression.crle; + + do { + switch (crle_p->state) { + + case detools_crle_state_idle_t: + res = patch_reader_crle_decompress_idle(self_p, crle_p); + break; + + case detools_crle_state_scattered_size_t: + res = patch_reader_crle_decompress_scattered_size(self_p, crle_p); + break; + + case detools_crle_state_scattered_data_t: + res = patch_reader_crle_decompress_scattered_data(self_p, + crle_p, + buf_p, + size_p); + break; + + case detools_crle_state_repeated_repetitions_t: + res = patch_reader_crle_decompress_repeated_repetitions(self_p, + crle_p); + break; + + case detools_crle_state_repeated_data_t: + res = patch_reader_crle_decompress_repeated_data(self_p, crle_p); + break; + + case detools_crle_state_repeated_data_read_t: + res = patch_reader_crle_decompress_repeated_data_read(crle_p, + buf_p, + size_p); + break; + + default: + res = -DETOOLS_INTERNAL_ERROR; + break; + } + } while (res == 2); + + return (res); +} + +static int patch_reader_crle_destroy( + struct detools_apply_patch_patch_reader_t *self_p) +{ + (void)self_p; + + return (0); +} + +static int patch_reader_crle_init(struct detools_apply_patch_patch_reader_t *self_p) +{ + + struct detools_apply_patch_patch_reader_crle_t *crle_p; + + crle_p = &self_p->compression.crle; + crle_p->state = detools_crle_state_idle_t; + self_p->destroy = patch_reader_crle_destroy; + self_p->decompress = patch_reader_crle_decompress; + + return (0); +} + +#endif + +/* + * Patch reader. + */ + +/** + * Initialize given patch reader. + */ +static int patch_reader_init(struct detools_apply_patch_patch_reader_t *self_p, + struct detools_apply_patch_chunk_t *patch_chunk_p, + size_t patch_size, + int compression) +{ + int res; + +#if DETOOLS_CONFIG_COMPRESSION_NONE != 1 + (void)patch_size; +#endif + + self_p->patch_chunk_p = patch_chunk_p; + self_p->size.state = detools_unpack_usize_state_first_t; + + switch (compression) { + +#if DETOOLS_CONFIG_COMPRESSION_NONE == 1 + case COMPRESSION_NONE: + res = patch_reader_none_init(self_p, patch_size); + break; +#endif + +#if DETOOLS_CONFIG_COMPRESSION_LZMA == 1 + case COMPRESSION_LZMA: + res = patch_reader_lzma_init(self_p); + break; +#endif + +#if DETOOLS_CONFIG_COMPRESSION_CRLE == 1 + case COMPRESSION_CRLE: + res = patch_reader_crle_init(self_p); + break; +#endif + +#if DETOOLS_CONFIG_COMPRESSION_HEATSHRINK == 1 + case COMPRESSION_HEATSHRINK: + res = patch_reader_heatshrink_init(self_p); + break; +#endif + + default: + res = -DETOOLS_BAD_COMPRESSION; + break; + } + + return (res); +} + +static int patch_reader_dump(struct detools_apply_patch_patch_reader_t *self_p, + int compression, + detools_state_write_t state_write) +{ + (void)self_p; + (void)state_write; + + int res; + + res = 0; + + switch (compression) { + +#if DETOOLS_CONFIG_COMPRESSION_NONE == 1 + case COMPRESSION_NONE: + break; +#endif + +#if DETOOLS_CONFIG_COMPRESSION_CRLE == 1 + case COMPRESSION_CRLE: + break; +#endif + +#if DETOOLS_CONFIG_COMPRESSION_HEATSHRINK == 1 +# if HEATSHRINK_DYNAMIC_ALLOC == 0 + case COMPRESSION_HEATSHRINK: + break; +# endif +#endif + + default: + res = -DETOOLS_NOT_IMPLEMENTED; + break; + } + + return (res); +} + +static int patch_reader_restore(struct detools_apply_patch_patch_reader_t *self_p, + struct detools_apply_patch_patch_reader_t *dumped_p, + struct detools_apply_patch_chunk_t *patch_chunk_p, + int compression, + detools_state_read_t state_read) +{ + (void)state_read; + + int res; + + res = 0; + *self_p = *dumped_p; + self_p->patch_chunk_p = patch_chunk_p; + + switch (compression) { + +#if DETOOLS_CONFIG_COMPRESSION_NONE == 1 + case COMPRESSION_NONE: + self_p->destroy = patch_reader_none_destroy; + self_p->decompress = patch_reader_none_decompress; + break; +#endif + +#if DETOOLS_CONFIG_COMPRESSION_CRLE == 1 + case COMPRESSION_CRLE: + self_p->destroy = patch_reader_crle_destroy; + self_p->decompress = patch_reader_crle_decompress; + break; +#endif + +#if DETOOLS_CONFIG_COMPRESSION_HEATSHRINK == 1 +# if HEATSHRINK_DYNAMIC_ALLOC == 0 + case COMPRESSION_HEATSHRINK: + self_p->compression.heatshrink.decoder_p = + &self_p->compression.heatshrink.decoder; + self_p->destroy = patch_reader_heatshrink_destroy; + self_p->decompress = patch_reader_heatshrink_decompress; + break; +# endif +#endif + + default: + res = -DETOOLS_NOT_IMPLEMENTED; + break; + } + + return (res); +} + +/** + * Try to decompress given number of bytes. + * + * @return zero(0) if at least one byte was decompressed, one(1) if + * zero bytes were decompressed and more input is needed, or + * negative error code. + */ +static int patch_reader_decompress( + struct detools_apply_patch_patch_reader_t *self_p, + uint8_t *buf_p, + size_t *size_p) +{ + return (self_p->decompress(self_p, buf_p, size_p)); +} + +/** + * Unpack a size value. + */ +static int patch_reader_unpack_size( + struct detools_apply_patch_patch_reader_t *self_p, + int *size_p) +{ + int res; + uint8_t byte; + size_t size; + + size = 1; + + do { + switch (self_p->size.state) { + + case detools_unpack_usize_state_first_t: + res = patch_reader_decompress(self_p, &byte, &size); + + if (res != 0) { + return (res); + } + + self_p->size.is_signed = ((byte & 0x40) == 0x40); + self_p->size.value = (byte & 0x3f); + self_p->size.offset = 6; + self_p->size.state = detools_unpack_usize_state_consecutive_t; + break; + + case detools_unpack_usize_state_consecutive_t: + res = patch_reader_decompress(self_p, &byte, &size); + + if (res != 0) { + return (res); + } + + if (is_overflow(self_p->size.offset)) { + return (-DETOOLS_CORRUPT_PATCH_OVERFLOW); + } + + self_p->size.value |= ((byte & 0x7f) << self_p->size.offset); + self_p->size.offset += 7; + break; + + default: + return (-DETOOLS_INTERNAL_ERROR); + } + } while ((byte & 0x80) != 0); + + /* Done, fix sign. */ + self_p->size.state = detools_unpack_usize_state_first_t; + + if (self_p->size.is_signed) { + self_p->size.value *= -1; + } + + *size_p = self_p->size.value; + + return (res); +} + +static int common_process_size( + struct detools_apply_patch_patch_reader_t *patch_reader_p, + size_t to_pos, + size_t to_size, + int *size_p) +{ + int res; + + res = patch_reader_unpack_size(patch_reader_p, size_p); + + if (res != 0) { + return (res); + } + + if (to_pos + (size_t)*size_p > to_size) { + return (-DETOOLS_CORRUPT_PATCH); + } + + return (res); +} + +/* + * Low level sequential patch type functionality. + */ + +static int process_init_fixed_header(struct detools_apply_patch_t *self_p) +{ + int patch_type; + uint8_t byte; + + if (chunk_get(&self_p->chunk, &byte) != 0) { + return (-DETOOLS_SHORT_HEADER); + } + + patch_type = ((byte >> 4) & 0x7); + self_p->compression = (byte & 0xf); + + if (patch_type != PATCH_TYPE_SEQUENTIAL) { + return (-DETOOLS_BAD_PATCH_TYPE); + } + + self_p->init_state = detools_apply_patch_init_state_to_size_t; + self_p->size.state = detools_unpack_usize_state_first_t; + + return (0); +} + +static int process_init_to_size(struct detools_apply_patch_t *self_p) +{ + int res; + int to_size; + + res = chunk_unpack_header_size(&self_p->chunk, &self_p->size, &to_size); + + if (res != 0) { + return (res); + } + + res = patch_reader_init(&self_p->patch_reader, + &self_p->chunk, + self_p->patch_size - self_p->chunk.offset, + self_p->compression); + + if (res != 0) { + return (res); + } + + if (to_size < 0) { + return (-DETOOLS_CORRUPT_PATCH); + } + + self_p->to_size = (size_t)to_size; + + if (to_size > 0) { + self_p->state = detools_apply_patch_state_dfpatch_size_t; + } else { + self_p->state = detools_apply_patch_state_done_t; + } + + return (res); +} + +static int process_init(struct detools_apply_patch_t *self_p) +{ + int res; + + switch (self_p->init_state) { + + case detools_apply_patch_init_state_fixed_header_t: + res = process_init_fixed_header(self_p); + break; + + case detools_apply_patch_init_state_to_size_t: + res = process_init_to_size(self_p); + break; + + default: + res = -DETOOLS_INTERNAL_ERROR; + break; + } + + return (res); +} + +static int process_dfpatch_size(struct detools_apply_patch_t *self_p) +{ + int res; + int size; + + res = patch_reader_unpack_size(&self_p->patch_reader, &size); + + if (res != 0) { + return (res); + } + + if (size > 0) { + return (-DETOOLS_NOT_IMPLEMENTED); + } + + self_p->state = detools_apply_patch_state_diff_size_t; + + return (0); +} + +static int process_size(struct detools_apply_patch_t *self_p, + enum detools_apply_patch_state_t next_state) +{ + int res; + int size; + + res = common_process_size(&self_p->patch_reader, + self_p->to_offset, + self_p->to_size, + &size); + + if (res != 0) { + return (res); + } + + self_p->state = next_state; + self_p->chunk_size = (size_t)size; + + return (res); +} + +static int process_data(struct detools_apply_patch_t *self_p, + enum detools_apply_patch_state_t next_state) +{ + int res; + size_t i; + uint8_t to[128]; + size_t to_size; + uint8_t from[128]; + + to_size = MIN(sizeof(to), self_p->chunk_size); + + if (to_size == 0) { + self_p->state = next_state; + + return (0); + } + + res = patch_reader_decompress(&self_p->patch_reader, + &to[0], + &to_size); + + if (res != 0) { + return (res); + } + + if (next_state == detools_apply_patch_state_extra_size_t) { + res = self_p->from_read(self_p->arg_p, &from[0], to_size); + + if (res != 0) { + return (-DETOOLS_IO_FAILED); + } + + self_p->from_offset += to_size; + + for (i = 0; i < to_size; i++) { + to[i] = (uint8_t)(to[i] + from[i]); + } + } + + self_p->to_offset += to_size; + self_p->chunk_size -= to_size; + + res = self_p->to_write(self_p->arg_p, &to[0], to_size); + + if (res != 0) { + return (-DETOOLS_IO_FAILED); + } + + return (res); +} + +static int process_diff_size(struct detools_apply_patch_t *self_p) +{ + return (process_size(self_p, detools_apply_patch_state_diff_data_t)); +} + +static int process_diff_data(struct detools_apply_patch_t *self_p) +{ + return (process_data(self_p, detools_apply_patch_state_extra_size_t)); +} + +static int process_extra_size(struct detools_apply_patch_t *self_p) +{ + return (process_size(self_p, detools_apply_patch_state_extra_data_t)); +} + +static int process_extra_data(struct detools_apply_patch_t *self_p) +{ + return (process_data(self_p, detools_apply_patch_state_adjustment_t)); +} + +static int process_adjustment(struct detools_apply_patch_t *self_p) +{ + int res; + int offset; + + res = patch_reader_unpack_size(&self_p->patch_reader, &offset); + + if (res != 0) { + return (res); + } + + res = self_p->from_seek(self_p->arg_p, offset); + + if (res != 0) { + return (-DETOOLS_IO_FAILED); + } + + self_p->from_offset += offset; + + if (self_p->to_offset == self_p->to_size) { + self_p->state = detools_apply_patch_state_done_t; + } else { + self_p->state = detools_apply_patch_state_diff_size_t; + } + + return (res); +} + +static int apply_patch_process_once(struct detools_apply_patch_t *self_p) +{ + int res; + + switch (self_p->state) { + + case detools_apply_patch_state_init_t: + res = process_init(self_p); + break; + + case detools_apply_patch_state_dfpatch_size_t: + res = process_dfpatch_size(self_p); + break; + + case detools_apply_patch_state_diff_size_t: + res = process_diff_size(self_p); + break; + + case detools_apply_patch_state_diff_data_t: + res = process_diff_data(self_p); + break; + + case detools_apply_patch_state_extra_size_t: + res = process_extra_size(self_p); + break; + + case detools_apply_patch_state_extra_data_t: + res = process_extra_data(self_p); + break; + + case detools_apply_patch_state_adjustment_t: + res = process_adjustment(self_p); + break; + + case detools_apply_patch_state_done_t: + return (-DETOOLS_ALREADY_DONE); + + case detools_apply_patch_state_failed_t: + res = -DETOOLS_ALREADY_FAILED; + break; + + default: + res = -DETOOLS_INTERNAL_ERROR; + break; + } + + if (res < 0) { + self_p->state = detools_apply_patch_state_failed_t; + } + + return (res); +} + +static int apply_patch_common_finalize( + int res, + struct detools_apply_patch_patch_reader_t *patch_reader_p, + size_t to_size) +{ + if (res == 1) { + res = -DETOOLS_NOT_ENOUGH_PATCH_DATA; + } + + if (res == -DETOOLS_ALREADY_DONE) { + res = 0; + } + + if (patch_reader_p->destroy != NULL) { + if (res == 0) { + res = patch_reader_p->destroy(patch_reader_p); + } else { + (void)patch_reader_p->destroy(patch_reader_p); + } + } + + if (res == 0) { + res = (int)to_size; + } + + return (res); +} + +int detools_apply_patch_init(struct detools_apply_patch_t *self_p, + detools_read_t from_read, + detools_seek_t from_seek, + size_t patch_size, + detools_write_t to_write, + void *arg_p) +{ + self_p->from_read = from_read; + self_p->from_seek = from_seek; + self_p->patch_size = patch_size; + self_p->patch_offset = 0; + self_p->to_offset = 0; + self_p->to_write = to_write; + self_p->from_offset = 0; + self_p->arg_p = arg_p; + self_p->state = detools_apply_patch_state_init_t; + self_p->init_state = detools_apply_patch_init_state_fixed_header_t; + self_p->patch_reader.destroy = NULL; + + return (0); +} + +int detools_apply_patch_dump(struct detools_apply_patch_t *self_p, + detools_state_write_t state_write) +{ + int res; + + res = state_write(self_p->arg_p, self_p, sizeof(*self_p)); + + if (res != 0) { + return (-DETOOLS_IO_FAILED); + } + + if (self_p->state == detools_apply_patch_state_init_t) { + return (0); + } + + return (patch_reader_dump(&self_p->patch_reader, + self_p->compression, + state_write)); +} + +int detools_apply_patch_restore(struct detools_apply_patch_t *self_p, + detools_state_read_t state_read) +{ + int res; + struct detools_apply_patch_t dumped; + + res = state_read(self_p->arg_p, &dumped, sizeof(dumped)); + + if (res != 0) { + return (-DETOOLS_IO_FAILED); + } + + self_p->state = dumped.state; + self_p->patch_size = dumped.patch_size; + + if (self_p->state == detools_apply_patch_state_init_t) { + return (0); + } + + self_p->compression = dumped.compression; + self_p->patch_offset = dumped.patch_offset; + self_p->to_offset = dumped.to_offset; + self_p->to_size = dumped.to_size; + self_p->from_offset = dumped.from_offset; + self_p->chunk_size = dumped.chunk_size; + + res = self_p->from_seek(self_p->arg_p, self_p->from_offset); + + if (res != 0) { + return (-DETOOLS_IO_FAILED); + } + + return (patch_reader_restore(&self_p->patch_reader, + &dumped.patch_reader, + &self_p->chunk, + self_p->compression, + state_read)); +} + +size_t detools_apply_patch_get_patch_offset(struct detools_apply_patch_t *self_p) +{ + return (self_p->patch_offset); +} + +size_t detools_apply_patch_get_to_offset(struct detools_apply_patch_t *self_p) +{ + return (self_p->to_offset); +} + +int detools_apply_patch_process(struct detools_apply_patch_t *self_p, + const uint8_t *patch_p, + size_t size) +{ + int res; + + res = 0; + self_p->patch_offset += size; + self_p->chunk.buf_p = patch_p; + self_p->chunk.size = size; + self_p->chunk.offset = 0; + + while (chunk_available(&self_p->chunk) && (res >= 0)) { + res = apply_patch_process_once(self_p); + } + + if ((res == 1) || (res == -DETOOLS_ALREADY_DONE)) { + res = 0; + } + + return (res); +} + +int detools_apply_patch_finalize(struct detools_apply_patch_t *self_p) +{ + int res; + + self_p->chunk.size = 0; + self_p->chunk.offset = 0; + + do { + res = apply_patch_process_once(self_p); + } while (res == 0); + + return (apply_patch_common_finalize(res, + &self_p->patch_reader, + self_p->to_size)); +} + +/* + * Low level in-place patch type functionality. + */ + +static int in_place_all_steps_completed(struct detools_apply_patch_in_place_t *self_p) +{ + int res; + + res = 0; + + if (self_p->step_set != NULL) { + res = self_p->step_set(self_p->arg_p, 0); + + if (res != 0) { + res = -DETOOLS_STEP_SET_FAILED; + } + } + + return (res); +} + +static int in_place_is_step_completed(struct detools_apply_patch_in_place_t *self_p, + bool *res_p) +{ + int res; + int completed_step; + + if (self_p->step_get != NULL) { + res = self_p->step_get(self_p->arg_p, &completed_step); + + if (res != 0) { + return (-DETOOLS_STEP_GET_FAILED); + } + + *res_p = (self_p->ongoing_step <= completed_step); + } else { + *res_p = false; + } + + return (0); +} + +static int in_place_next_step(struct detools_apply_patch_in_place_t *self_p) +{ + int res; + bool is_step_completed; + + res = 0; + + if (self_p->step_set != NULL) { + res = in_place_is_step_completed(self_p, &is_step_completed); + + if (res != 0) { + return (res); + } + + if (!is_step_completed) { + res = self_p->step_set(self_p->arg_p, self_p->ongoing_step); + + if (res != 0) { + res = -DETOOLS_STEP_SET_FAILED; + } + } + } + + self_p->ongoing_step++; + + return (res); +} + +static int in_place_mem_read(struct detools_apply_patch_in_place_t *self_p, + void *dst_p, + uintptr_t src, + size_t size) +{ + int res; + bool is_step_completed; + + res = in_place_is_step_completed(self_p, &is_step_completed); + + if (res != 0) { + return (res); + } + + if (!is_step_completed) { + return (self_p->mem_read(self_p->arg_p, dst_p, src, size)); + } else { + memset(dst_p, 0, size); + + return (0); + } +} + +static int in_place_mem_write(struct detools_apply_patch_in_place_t *self_p, + uintptr_t dst, + void *src_p, + size_t size) +{ + int res; + bool is_step_completed; + + res = in_place_is_step_completed(self_p, &is_step_completed); + + if (res != 0) { + return (res); + } + + if (!is_step_completed) { + return (self_p->mem_write(self_p->arg_p, dst, src_p, size)); + } else { + return (0); + } +} + +static int in_place_mem_erase(struct detools_apply_patch_in_place_t *self_p, + uintptr_t addr, + size_t size) +{ + int res; + bool is_step_completed; + + res = in_place_is_step_completed(self_p, &is_step_completed); + + if (res != 0) { + return (res); + } + + if (!is_step_completed) { + return (self_p->mem_erase(self_p->arg_p, addr, size)); + } else { + return (0); + } +} + +static int in_place_shift_memory(struct detools_apply_patch_in_place_t *self_p, + size_t memory_size, + size_t from_size) +{ + size_t i; + size_t number_of_segments; + int res; + size_t read_address; + size_t write_address; + uint8_t buf[128]; + size_t offset; + size_t size; + + number_of_segments = DIV_CEIL(MIN(from_size, memory_size - self_p->shift_size), + self_p->segment_size); + read_address = ((number_of_segments - 1) * self_p->segment_size); + write_address = (read_address + self_p->shift_size); + + for (i = 0; i < number_of_segments; i++) { + /* Erase segment to write to. */ + res = in_place_mem_erase(self_p, + write_address, + self_p->segment_size); + + if (res != 0) { + return (res); + } + + /* Copy data to erased segment. */ + offset = 0; + + while (offset < self_p->segment_size) { + size = MIN(sizeof(buf), self_p->segment_size - offset); + res = in_place_mem_read(self_p, + &buf[0], + read_address + offset, + size); + + if (res != 0) { + return (res); + } + + res = in_place_mem_write(self_p, + write_address + offset, + &buf[0], + size); + + if (res != 0) { + return (res); + } + + offset += size; + } + + res = in_place_next_step(self_p); + + if (res != 0) { + return (res); + } + + write_address -= self_p->segment_size; + read_address -= self_p->segment_size; + } + + return (0); +} + +static int in_place_process_init_fixed_header( + struct detools_apply_patch_in_place_t *self_p) +{ + int patch_type; + uint8_t byte; + + if (chunk_get(&self_p->chunk, &byte) != 0) { + return (-DETOOLS_SHORT_HEADER); + } + + patch_type = ((byte >> 4) & 0x7); + self_p->compression = (byte & 0xf); + + if (patch_type != PATCH_TYPE_IN_PLACE) { + return (-DETOOLS_BAD_PATCH_TYPE); + } + + self_p->init_state = detools_apply_patch_in_place_init_state_memory_size_t; + self_p->size.state = detools_unpack_usize_state_first_t; + + return (0); +} + +static int in_place_process_init_memory_size( + struct detools_apply_patch_in_place_t *self_p) +{ + int res; + int memory_size; + + res = chunk_unpack_header_size(&self_p->chunk, &self_p->size, &memory_size); + + if (res != 0) { + return (res); + } + + self_p->memory_size = (size_t)memory_size; + self_p->init_state = detools_apply_patch_in_place_init_state_segment_size_t; + self_p->size.state = detools_unpack_usize_state_first_t; + + return (0); +} + +static int in_place_process_init_segment_size( + struct detools_apply_patch_in_place_t *self_p) +{ + int res; + int segment_size; + + res = chunk_unpack_header_size(&self_p->chunk, &self_p->size, &segment_size); + + if (res != 0) { + return (res); + } + + self_p->segment_size = (size_t)segment_size; + self_p->init_state = detools_apply_patch_in_place_init_state_shift_size_t; + self_p->size.state = detools_unpack_usize_state_first_t; + + return (0); +} + +static int in_place_process_init_shift_size( + struct detools_apply_patch_in_place_t *self_p) +{ + int res; + int shift_size; + + res = chunk_unpack_header_size(&self_p->chunk, &self_p->size, &shift_size); + + if (res != 0) { + return (res); + } + + self_p->shift_size = (size_t)shift_size; + self_p->init_state = detools_apply_patch_in_place_init_state_from_size_t; + self_p->size.state = detools_unpack_usize_state_first_t; + + return (0); +} + +static int in_place_process_init_from_size( + struct detools_apply_patch_in_place_t *self_p) +{ + int res; + int from_size; + + res = chunk_unpack_header_size(&self_p->chunk, &self_p->size, &from_size); + + if (res != 0) { + return (res); + } + + self_p->from_size = (size_t)from_size; + self_p->init_state = detools_apply_patch_in_place_init_state_to_size_t; + self_p->size.state = detools_unpack_usize_state_first_t; + + return (0); +} + +static int in_place_process_init_to_size( + struct detools_apply_patch_in_place_t *self_p) +{ + int res; + int to_size; + + res = chunk_unpack_header_size(&self_p->chunk, &self_p->size, &to_size); + + if (res != 0) { + return (res); + } + + res = patch_reader_init(&self_p->patch_reader, + &self_p->chunk, + self_p->patch_size - self_p->chunk.offset, + self_p->compression); + + if (res != 0) { + return (res); + } + + if (to_size < 0) { + return (-DETOOLS_CORRUPT_PATCH); + } + + self_p->to_pos = 0; + self_p->to_size = (size_t)to_size; + self_p->segment.index = 0; + + if (to_size > 0) { + res = in_place_shift_memory(self_p, + self_p->memory_size, + self_p->from_size); + + if (res != 0) { + return (res); + } + + self_p->state = detools_apply_patch_state_dfpatch_size_t; + } else { + self_p->state = detools_apply_patch_state_done_t; + } + + return (res); +} + +static int in_place_process_init(struct detools_apply_patch_in_place_t *self_p) +{ + int res; + + switch (self_p->init_state) { + + case detools_apply_patch_in_place_init_state_fixed_header_t: + res = in_place_process_init_fixed_header(self_p); + break; + + case detools_apply_patch_in_place_init_state_memory_size_t: + res = in_place_process_init_memory_size(self_p); + break; + + case detools_apply_patch_in_place_init_state_segment_size_t: + res = in_place_process_init_segment_size(self_p); + break; + + case detools_apply_patch_in_place_init_state_shift_size_t: + res = in_place_process_init_shift_size(self_p); + break; + + case detools_apply_patch_in_place_init_state_from_size_t: + res = in_place_process_init_from_size(self_p); + break; + + case detools_apply_patch_in_place_init_state_to_size_t: + res = in_place_process_init_to_size(self_p); + break; + + default: + res = -DETOOLS_INTERNAL_ERROR; + break; + } + + return (res); +} + +static int in_place_process_dfpatch_size( + struct detools_apply_patch_in_place_t *self_p) +{ + int res; + int size; + + res = patch_reader_unpack_size(&self_p->patch_reader, &size); + + if (res != 0) { + return (res); + } + + if (size > 0) { + return (-DETOOLS_NOT_IMPLEMENTED); + } + + self_p->state = detools_apply_patch_state_diff_size_t; + self_p->segment.from_offset = + (int)MAX(self_p->segment_size * (self_p->segment.index + 1), + self_p->shift_size); + self_p->segment.to_offset = (self_p->segment.index * self_p->segment_size); + self_p->segment.to_size = MIN(self_p->segment_size, + self_p->to_size - self_p->segment.to_offset); + self_p->segment.to_pos = 0; + self_p->segment.index++; + + return (in_place_mem_erase(self_p, + self_p->segment.to_offset, + self_p->segment.to_size)); +} + +static int in_place_process_size(struct detools_apply_patch_in_place_t *self_p, + enum detools_apply_patch_state_t next_state) +{ + int res; + int size; + + res = common_process_size(&self_p->patch_reader, + self_p->to_pos, + self_p->to_size, + &size); + + if (res != 0) { + return (res); + } + + self_p->state = next_state; + self_p->chunk_size = (size_t)size; + + return (0); +} + +static int in_place_process_data(struct detools_apply_patch_in_place_t *self_p, + enum detools_apply_patch_state_t next_state) +{ + int res; + size_t i; + uint8_t to[128]; + size_t to_size; + uint8_t from[128]; + + to_size = MIN(sizeof(to), self_p->chunk_size); + + if (to_size == 0) { + self_p->state = next_state; + + return (0); + } + + res = patch_reader_decompress(&self_p->patch_reader, + &to[0], + &to_size); + + if (res != 0) { + return (res); + } + + if (next_state == detools_apply_patch_state_extra_size_t) { + res = in_place_mem_read(self_p, + &from[0], + (size_t)self_p->segment.from_offset, + to_size); + + if (res != 0) { + return (-DETOOLS_IO_FAILED); + } + + self_p->segment.from_offset += (int)to_size; + + for (i = 0; i < to_size; i++) { + to[i] = (uint8_t)(to[i] + from[i]); + } + } + + res = in_place_mem_write(self_p, + self_p->segment.to_pos + self_p->segment.to_offset, + &to[0], + to_size); + + if (res != 0) { + return (-DETOOLS_IO_FAILED); + } + + self_p->to_pos += to_size; + self_p->segment.to_pos += to_size; + self_p->chunk_size -= to_size; + + return (res); +} + +static int in_place_process_diff_size(struct detools_apply_patch_in_place_t *self_p) +{ + return (in_place_process_size(self_p, detools_apply_patch_state_diff_data_t)); +} + +static int in_place_process_diff_data(struct detools_apply_patch_in_place_t *self_p) +{ + return (in_place_process_data(self_p, detools_apply_patch_state_extra_size_t)); +} + +static int in_place_process_extra_size(struct detools_apply_patch_in_place_t *self_p) +{ + return (in_place_process_size(self_p, detools_apply_patch_state_extra_data_t)); +} + +static int in_place_process_extra_data(struct detools_apply_patch_in_place_t *self_p) +{ + return (in_place_process_data(self_p, detools_apply_patch_state_adjustment_t)); +} + +static int in_place_process_adjustment(struct detools_apply_patch_in_place_t *self_p) +{ + int res; + int offset; + + res = patch_reader_unpack_size(&self_p->patch_reader, &offset); + + if (res != 0) { + return (res); + } + + if (self_p->to_pos == self_p->to_size) { + res = in_place_all_steps_completed(self_p); + self_p->state = detools_apply_patch_state_done_t; + } else if (self_p->segment.to_pos == self_p->segment.to_size) { + res = in_place_next_step(self_p); + self_p->state = detools_apply_patch_state_dfpatch_size_t; + } else { + self_p->segment.from_offset += offset; + self_p->state = detools_apply_patch_state_diff_size_t; + } + + return (res); +} + +static int apply_patch_in_place_process_once( + struct detools_apply_patch_in_place_t *self_p) +{ + int res; + + switch (self_p->state) { + + case detools_apply_patch_state_init_t: + res = in_place_process_init(self_p); + break; + + case detools_apply_patch_state_dfpatch_size_t: + res = in_place_process_dfpatch_size(self_p); + break; + + case detools_apply_patch_state_diff_size_t: + res = in_place_process_diff_size(self_p); + break; + + case detools_apply_patch_state_diff_data_t: + res = in_place_process_diff_data(self_p); + break; + + case detools_apply_patch_state_extra_size_t: + res = in_place_process_extra_size(self_p); + break; + + case detools_apply_patch_state_extra_data_t: + res = in_place_process_extra_data(self_p); + break; + + case detools_apply_patch_state_adjustment_t: + res = in_place_process_adjustment(self_p); + break; + + case detools_apply_patch_state_done_t: + return (-DETOOLS_ALREADY_DONE); + + case detools_apply_patch_state_failed_t: + res = -DETOOLS_ALREADY_FAILED; + break; + + default: + res = -DETOOLS_INTERNAL_ERROR; + break; + } + + if (res < 0) { + self_p->state = detools_apply_patch_state_failed_t; + } + + return (res); +} + +int detools_apply_patch_in_place_init( + struct detools_apply_patch_in_place_t *self_p, + detools_mem_read_t mem_read, + detools_mem_write_t mem_write, + detools_mem_erase_t mem_erase, + detools_step_set_t step_set, + detools_step_get_t step_get, + size_t patch_size, + void *arg_p) +{ + self_p->mem_read = mem_read; + self_p->mem_write = mem_write; + self_p->mem_erase = mem_erase; + self_p->step_set = step_set; + self_p->step_get = step_get; + self_p->patch_size = patch_size; + self_p->arg_p = arg_p; + self_p->state = detools_apply_patch_state_init_t; + self_p->ongoing_step = 1; + self_p->init_state = detools_apply_patch_in_place_init_state_fixed_header_t; + self_p->patch_reader.destroy = NULL; + + return (0); +} + +int detools_apply_patch_in_place_process( + struct detools_apply_patch_in_place_t *self_p, + const uint8_t *patch_p, + size_t size) +{ + int res; + + res = 0; + self_p->chunk.buf_p = patch_p; + self_p->chunk.size = size; + self_p->chunk.offset = 0; + + while (chunk_available(&self_p->chunk) && (res >= 0)) { + res = apply_patch_in_place_process_once(self_p); + } + + if ((res == 1) || (res == -DETOOLS_ALREADY_DONE)) { + res = 0; + } + + return (res); +} + +int detools_apply_patch_in_place_finalize( + struct detools_apply_patch_in_place_t *self_p) +{ + int res; + + self_p->chunk.size = 0; + self_p->chunk.offset = 0; + + do { + res = apply_patch_in_place_process_once(self_p); + } while (res == 0); + + return (apply_patch_common_finalize(res, + &self_p->patch_reader, + self_p->to_size)); +} + +/* + * Callback functionality. + */ + +static int callbacks_process(struct detools_apply_patch_t *apply_patch_p, + detools_read_t patch_read, + size_t patch_size, + void *arg_p) +{ + int res; + size_t patch_offset; + size_t chunk_size; + uint8_t chunk[512]; + + res = 0; + patch_offset = 0; + + while ((patch_offset < patch_size) && (res == 0)) { + chunk_size = MIN(patch_size - patch_offset, 512); + res = patch_read(arg_p, &chunk[0], chunk_size); + + if (res == 0) { + res = detools_apply_patch_process(apply_patch_p, + &chunk[0], + chunk_size); + patch_offset += chunk_size; + } else { + res = -DETOOLS_IO_FAILED; + } + } + + if (res == 0) { + res = detools_apply_patch_finalize(apply_patch_p); + } else { + (void)detools_apply_patch_finalize(apply_patch_p); + } + + return (res); +} + +int detools_apply_patch_callbacks(detools_read_t from_read, + detools_seek_t from_seek, + detools_read_t patch_read, + size_t patch_size, + detools_write_t to_write, + void *arg_p) +{ + int res; + struct detools_apply_patch_t apply_patch; + + res = detools_apply_patch_init(&apply_patch, + from_read, + from_seek, + patch_size, + to_write, + arg_p); + + if (res != 0) { + return (res); + } + + return (callbacks_process(&apply_patch, patch_read, patch_size, arg_p)); +} + +static int in_place_callbacks_process( + struct detools_apply_patch_in_place_t *apply_patch_p, + detools_read_t patch_read, + size_t patch_size, + void *arg_p) +{ + int res; + size_t patch_offset; + size_t chunk_size; + uint8_t chunk[512]; + + res = 0; + patch_offset = 0; + + while ((patch_offset < patch_size) && (res == 0)) { + chunk_size = MIN(patch_size - patch_offset, 512); + res = patch_read(arg_p, &chunk[0], chunk_size); + + if (res == 0) { + res = detools_apply_patch_in_place_process(apply_patch_p, + &chunk[0], + chunk_size); + patch_offset += chunk_size; + } else { + res = -DETOOLS_IO_FAILED; + } + } + + if (res == 0) { + res = detools_apply_patch_in_place_finalize(apply_patch_p); + } else { + (void)detools_apply_patch_in_place_finalize(apply_patch_p); + } + + return (res); +} + +int detools_apply_patch_in_place_callbacks(detools_mem_read_t mem_read, + detools_mem_write_t mem_write, + detools_mem_erase_t mem_erase, + detools_step_set_t step_set, + detools_step_get_t step_get, + detools_read_t patch_read, + size_t patch_size, + void *arg_p) +{ + int res; + struct detools_apply_patch_in_place_t apply_patch; + + res = detools_apply_patch_in_place_init(&apply_patch, + mem_read, + mem_write, + mem_erase, + step_set, + step_get, + patch_size, + arg_p); + + if (res != 0) { + return (res); + } + + return (in_place_callbacks_process(&apply_patch, + patch_read, + patch_size, + arg_p)); +} + +/* + * File io functionality. + */ + +#if DETOOLS_CONFIG_FILE_IO == 1 + +struct file_io_t { + FILE *ffrom_p; + FILE *fpatch_p; + FILE *fto_p; +}; + +static int file_size(FILE *file_p, size_t *size_p) +{ + int res; + long size; + + res = fseek(file_p, 0, SEEK_END); + + if (res != 0) { + return (-DETOOLS_FILE_SEEK_FAILED); + } + + size = ftell(file_p); + + if (size <= 0) { + return (-DETOOLS_FILE_TELL_FAILED); + } + + *size_p = (size_t)size; + + res = fseek(file_p, 0, SEEK_SET); + + if (res != 0) { + return (-DETOOLS_FILE_SEEK_FAILED); + } + + return (res); +} + +static int file_io_init(struct file_io_t *self_p, + const char *from_p, + const char *patch_p, + const char *to_p, + size_t *patch_size_p) +{ + int res; + FILE *file_p; + + res = -DETOOLS_FILE_OPEN_FAILED; + + /* From. */ + file_p = fopen(from_p, "rb"); + + if (file_p == NULL) { + return (res); + } + + self_p->ffrom_p = file_p; + + /* To. */ + file_p = fopen(to_p, "wb"); + + if (file_p == NULL) { + goto err1; + } + + self_p->fto_p = file_p; + + /* Patch. */ + file_p = fopen(patch_p, "rb"); + + if (file_p == NULL) { + goto err2; + } + + self_p->fpatch_p = file_p; + res = file_size(self_p->fpatch_p, patch_size_p); + + if (res != 0) { + goto err3; + } + + return (res); + + err3: + fclose(self_p->fpatch_p); + + err2: + fclose(self_p->fto_p); + + err1: + fclose(self_p->ffrom_p); + + return (res); +} + +static int file_io_cleanup(struct file_io_t *self_p) +{ + int res; + int res2; + int res3; + + res = fclose(self_p->ffrom_p); + res2 = fclose(self_p->fto_p); + res3 = fclose(self_p->fpatch_p); + + if ((res != 0) || (res2 != 0) || (res3 != 0)) { + res = -DETOOLS_FILE_CLOSE_FAILED; + } + + return (res); +} + +static int file_io_read(FILE *file_p, uint8_t *buf_p, size_t size) +{ + int res; + + res = 0; + + if (size > 0) { + if (fread(buf_p, size, 1, file_p) != 1) { + res = -DETOOLS_FILE_READ_FAILED; + } + } + + return (res); +} + +static int file_io_from_read(void *arg_p, uint8_t *buf_p, size_t size) +{ + struct file_io_t *self_p; + + self_p = (struct file_io_t *)arg_p; + + return (file_io_read(self_p->ffrom_p, buf_p, size)); +} + +static int file_io_from_seek(void *arg_p, int offset) +{ + struct file_io_t *self_p; + + self_p = (struct file_io_t *)arg_p; + + return (fseek(self_p->ffrom_p, offset, SEEK_CUR)); +} + +static int file_io_patch_read(void *arg_p, uint8_t *buf_p, size_t size) +{ + struct file_io_t *self_p; + + self_p = (struct file_io_t *)arg_p; + + return (file_io_read(self_p->fpatch_p, buf_p, size)); +} + +static int file_io_to_write(void *arg_p, const uint8_t *buf_p, size_t size) +{ + int res; + struct file_io_t *self_p; + + self_p = (struct file_io_t *)arg_p; + res = 0; + + if (size > 0) { + if (fwrite(buf_p, size, 1, self_p->fto_p) != 1) { + res = -DETOOLS_FILE_WRITE_FAILED; + } + } + + return (res); +} + +int detools_apply_patch_filenames(const char *from_p, + const char *patch_p, + const char *to_p) +{ + int res; + struct file_io_t file_io; + size_t patch_size; + + res = file_io_init(&file_io, + from_p, + patch_p, + to_p, + &patch_size); + + if (res != 0) { + return (res); + } + + res = detools_apply_patch_callbacks(file_io_from_read, + file_io_from_seek, + file_io_patch_read, + patch_size, + file_io_to_write, + &file_io); + + if (res != 0) { + goto err1; + } + + return (file_io_cleanup(&file_io)); + + err1: + (void)file_io_cleanup(&file_io); + + return (res); +} + +struct in_place_file_io_t { + FILE *fmemory_p; + FILE *fpatch_p; +}; + +static int in_place_file_io_init(struct in_place_file_io_t *self_p, + const char *memory_p, + const char *patch_p, + size_t *patch_size_p) +{ + int res; + FILE *file_p; + + res = -DETOOLS_FILE_OPEN_FAILED; + + /* Memory. */ + file_p = fopen(memory_p, "r+b"); + + if (file_p == NULL) { + return (res); + } + + self_p->fmemory_p = file_p; + + /* Patch. */ + file_p = fopen(patch_p, "rb"); + + if (file_p == NULL) { + goto err1; + } + + self_p->fpatch_p = file_p; + res = file_size(self_p->fpatch_p, patch_size_p); + + if (res != 0) { + goto err2; + } + + return (res); + + err2: + fclose(self_p->fpatch_p); + + err1: + fclose(self_p->fmemory_p); + + return (res); +} + +static int in_place_file_io_mem_read(void *arg_p, + void *dst_p, + uintptr_t src, + size_t size) +{ + int res; + struct in_place_file_io_t *self_p; + + self_p = (struct in_place_file_io_t *)arg_p; + res = 0; + + if (size > 0) { + res = fseek(self_p->fmemory_p, (int)src, SEEK_SET); + + if (res != 0) { + return (-DETOOLS_FILE_SEEK_FAILED); + } + + if (fread(dst_p, size, 1, self_p->fmemory_p) != 1) { + res = -DETOOLS_FILE_READ_FAILED; + } + } + + return (res); +} + +static int in_place_file_io_mem_write(void *arg_p, + uintptr_t dst, + void *src_p, + size_t size) +{ + int res; + struct in_place_file_io_t *self_p; + + self_p = (struct in_place_file_io_t *)arg_p; + res = 0; + + if (size > 0) { + res = fseek(self_p->fmemory_p, (int)dst, SEEK_SET); + + if (res != 0) { + return (-DETOOLS_FILE_SEEK_FAILED); + } + + if (fwrite(src_p, size, 1, self_p->fmemory_p) != 1) { + res = -DETOOLS_FILE_WRITE_FAILED; + } + } + + return (res); +} + +static int in_place_file_io_mem_erase(void *arg_p, uintptr_t addr, size_t size) +{ + (void)arg_p; + (void)addr; + (void)size; + + return (0); +} + +static int in_place_file_io_cleanup(struct in_place_file_io_t *self_p) +{ + int res; + int res2; + + res = fclose(self_p->fmemory_p); + res2 = fclose(self_p->fpatch_p); + + if ((res != 0) || (res2 != 0)) { + res = -DETOOLS_FILE_CLOSE_FAILED; + } + + return (res); +} + +int detools_apply_patch_in_place_filenames(const char *memory_p, + const char *patch_p, + detools_step_set_t step_set, + detools_step_get_t step_get) +{ + int res; + struct in_place_file_io_t file_io; + size_t patch_size; + + res = in_place_file_io_init(&file_io, + memory_p, + patch_p, + &patch_size); + + if (res != 0) { + return (res); + } + + res = detools_apply_patch_in_place_callbacks(in_place_file_io_mem_read, + in_place_file_io_mem_write, + in_place_file_io_mem_erase, + step_set, + step_get, + file_io_patch_read, + patch_size, + &file_io); + + if (res != 0) { + goto err1; + } + + return (in_place_file_io_cleanup(&file_io)); + + err1: + (void)in_place_file_io_cleanup(&file_io); + + return (res); +} + +#endif + +const char *detools_error_as_string(int error) +{ + if (error < 0) { + error *= -1; + } + + switch (error) { + + case DETOOLS_NOT_IMPLEMENTED: + return "Function not implemented."; + + case DETOOLS_NOT_DONE: + return "Not done."; + + case DETOOLS_BAD_PATCH_TYPE: + return "Bad patch type."; + + case DETOOLS_BAD_COMPRESSION: + return "Bad compression."; + + case DETOOLS_INTERNAL_ERROR: + return "Internal error."; + + case DETOOLS_LZMA_INIT: + return "LZMA init."; + + case DETOOLS_LZMA_DECODE: + return "LZMA decode."; + + case DETOOLS_OUT_OF_MEMORY: + return "Out of memory."; + + case DETOOLS_CORRUPT_PATCH: + return "Corrupt patch."; + + case DETOOLS_IO_FAILED: + return "Input/output failed."; + + case DETOOLS_ALREADY_DONE: + return "Already done."; + + case DETOOLS_FILE_OPEN_FAILED: + return "File open failed."; + + case DETOOLS_FILE_CLOSE_FAILED: + return "File close failed."; + + case DETOOLS_FILE_READ_FAILED: + return "File read failed."; + + case DETOOLS_FILE_WRITE_FAILED: + return "File write failed."; + + case DETOOLS_FILE_SEEK_FAILED: + return "File seek failed."; + + case DETOOLS_FILE_TELL_FAILED: + return "File tell failed."; + + case DETOOLS_SHORT_HEADER: + return "Short header."; + + case DETOOLS_NOT_ENOUGH_PATCH_DATA: + return "Not enough patch data."; + + case DETOOLS_HEATSHRINK_SINK: + return "Heatshrink sink."; + + case DETOOLS_HEATSHRINK_POLL: + return "Heatshrink poll."; + + case DETOOLS_STEP_SET_FAILED: + return "Step set failed."; + + case DETOOLS_STEP_GET_FAILED: + return "Step get failed."; + + case DETOOLS_ALREADY_FAILED: + return "Already failed."; + + case DETOOLS_CORRUPT_PATCH_OVERFLOW: + return "Corrupt patch, overflow."; + + case DETOOLS_CORRUPT_PATCH_CRLE_KIND: + return "Corrupt patch, CRLE kind."; + + case DETOOLS_HEATSHRINK_HEADER: + return "Heatshrink header."; + + default: + return "Unknown error."; + } +} diff --git a/src/helpers/ota/detools/detools.h b/src/helpers/ota/detools/detools.h new file mode 100644 index 00000000..c24d9677 --- /dev/null +++ b/src/helpers/ota/detools/detools.h @@ -0,0 +1,639 @@ +/** + * BSD 2-Clause License + * + * Copyright (c) 2019-2020, Erik Moqvist + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DETOOLS_H +#define DETOOLS_H + +/* + * Configuration. + * + * Define any of the defines below to 0 to disable given feature. + * + * MeshCore note: upstream defaults are all 1. We flip FILE_IO, LZMA and + * HEATSHRINK off here so this vendored copy is safe-by-default on bare-metal + * targets (no liblzma, no malloc/heatshrink, no file IO) regardless of + * build flags. MeshCore .mota deltas use --codec sequential --compression crle, + * which is fully self-contained in detools.c (NONE + CRLE only). Re-enable a + * feature by passing -DDETOOLS_CONFIG_..=1. See detools/README.meshcore.txt. + */ + +#ifndef DETOOLS_CONFIG_FILE_IO +# define DETOOLS_CONFIG_FILE_IO 0 +#endif + +#ifndef DETOOLS_CONFIG_COMPRESSION_NONE +# define DETOOLS_CONFIG_COMPRESSION_NONE 1 +#endif + +#ifndef DETOOLS_CONFIG_COMPRESSION_LZMA +# define DETOOLS_CONFIG_COMPRESSION_LZMA 0 +#endif + +#ifndef DETOOLS_CONFIG_COMPRESSION_CRLE +# define DETOOLS_CONFIG_COMPRESSION_CRLE 1 +#endif + +#ifndef DETOOLS_CONFIG_COMPRESSION_HEATSHRINK +# define DETOOLS_CONFIG_COMPRESSION_HEATSHRINK 0 +#endif + +#include +#include +#include +#include + +#define DETOOLS_VERSION "0.53.0" + +/* Error codes. */ +#define DETOOLS_OK 0 +#define DETOOLS_NOT_IMPLEMENTED 1 +#define DETOOLS_NOT_DONE 2 +#define DETOOLS_BAD_PATCH_TYPE 3 +#define DETOOLS_BAD_COMPRESSION 4 +#define DETOOLS_INTERNAL_ERROR 5 +#define DETOOLS_LZMA_INIT 6 +#define DETOOLS_LZMA_DECODE 7 +#define DETOOLS_OUT_OF_MEMORY 8 +#define DETOOLS_CORRUPT_PATCH 9 +#define DETOOLS_IO_FAILED 10 +#define DETOOLS_ALREADY_DONE 11 +#define DETOOLS_FILE_OPEN_FAILED 12 +#define DETOOLS_FILE_CLOSE_FAILED 13 +#define DETOOLS_FILE_READ_FAILED 14 +#define DETOOLS_FILE_WRITE_FAILED 15 +#define DETOOLS_FILE_SEEK_FAILED 16 +#define DETOOLS_FILE_TELL_FAILED 17 +#define DETOOLS_SHORT_HEADER 18 +#define DETOOLS_NOT_ENOUGH_PATCH_DATA 19 +#define DETOOLS_HEATSHRINK_SINK 20 +#define DETOOLS_HEATSHRINK_POLL 21 +#define DETOOLS_STEP_SET_FAILED 22 +#define DETOOLS_STEP_GET_FAILED 23 +#define DETOOLS_ALREADY_FAILED 24 +#define DETOOLS_CORRUPT_PATCH_OVERFLOW 25 +#define DETOOLS_CORRUPT_PATCH_CRLE_KIND 26 +#define DETOOLS_HEATSHRINK_HEADER 27 + +/** + * Read callback. + * + * @param[in] arg_p User data passed to detools_apply_patch_init(). + * @param[out] buf_p Buffer to read into. + * @param[in] size Number of bytes to read. + * + * @return zero(0) or negative error code. + */ +typedef int (*detools_read_t)(void *arg_p, uint8_t *buf_p, size_t size); + +/** + * Write callback. + * + * @param[in] arg_p User data passed to detools_apply_patch_init(). + * @param[in] buf_p Buffer to write. + * @param[in] size Number of bytes to write. + * + * @return zero(0) or negative error code. + */ +typedef int (*detools_write_t)(void *arg_p, const uint8_t *buf_p, size_t size); + +/** + * Seek from current position callback. + * + * @param[in] arg_p User data passed to detools_apply_patch_init(). + * @param[in] offset Offset to seek to from current position. + * + * @return zero(0) or negative error code. + */ +typedef int (*detools_seek_t)(void *arg_p, int offset); + +/** + * Memory read callback. + * + * @param[in] arg_p User data passed to detools_apply_patch_init(). + * @param[out] dst_p Buffer to read into. + * @param[in] src Address to read from. + * @param[in] size Number of bytes to read. + * + * @return zero(0) or negative error code. + */ +typedef int (*detools_mem_read_t)(void *arg_p, + void *dst_p, + uintptr_t src, + size_t size); + +/** + * Memory write callback. + * + * @param[in] arg_p User data passed to detools_apply_patch_init(). + * @param[in] dst Address to write to. + * @param[in] addr src_p Buffer to write from. + * @param[in] size Number of bytes to write. + * + * @return zero(0) or negative error code. + */ +typedef int (*detools_mem_write_t)(void *arg_p, + uintptr_t dst, + void *src_p, + size_t size); + +/** + * Memory erase callback. + * + * @param[in] arg_p User data passed to detools_apply_patch_init(). + * @param[in] addr Address to erase from. + * @param[in] size Number of bytes to erase. + * + * @return zero(0) or negative error code. + */ +typedef int (*detools_mem_erase_t)(void *arg_p, uintptr_t addr, size_t size); + +/** + * State read callback. + * + * @param[in] arg_p User data passed to detools_apply_patch_init(). + * @param[out] buf_p Buffer to read into. + * @param[in] size Number of bytes to read. + * + * @return zero(0) or negative error code. + */ +typedef int (*detools_state_read_t)(void *arg_p, void *buf_p, size_t size); + +/** + * State write callback. + * + * @param[in] arg_p User data passed to detools_apply_patch_init(). + * @param[in] buf_p Buffer to write. + * @param[in] size Number of bytes to write. + * + * @return zero(0) or negative error code. + */ +typedef int (*detools_state_write_t)(void *arg_p, const void *buf_p, size_t size); + +/** + * Step set callback. + * + * @param[in] arg_p User data passed to detools_apply_patch_init(). + * @param[in] step Step to set. Later read by the step get callback. + * + * @return zero(0) or negative error code. + */ +typedef int (*detools_step_set_t)(void *arg_p, int step); + +/** + * Step get callback. + * + * @param[in] arg_p User data passed to detools_apply_patch_init(). + * @param[out] step_p Outputs the most recently set step by the set + * callback, or zero(0) if not yet set. + * + * @return zero(0) or negative error code. + */ +typedef int (*detools_step_get_t)(void *arg_p, int *step_p); + +struct detools_apply_patch_size_t { + int state; + int value; + int offset; + bool is_signed; +}; + +struct detools_apply_patch_patch_reader_none_t { + size_t patch_size; + size_t patch_offset; +}; + +#if DETOOLS_CONFIG_COMPRESSION_LZMA == 1 + +#include + +struct detools_apply_patch_patch_reader_lzma_t { + lzma_stream stream; + uint8_t *input_p; + uint8_t *output_p; + size_t output_size; +}; + +#endif + +#if DETOOLS_CONFIG_COMPRESSION_HEATSHRINK == 1 + +#include "heatshrink_decoder.h" + +struct detools_apply_patch_patch_reader_heatshrink_t { + int8_t window_sz2; + int8_t lookahead_sz2; + heatshrink_decoder *decoder_p; +#if HEATSHRINK_DYNAMIC_ALLOC == 0 + heatshrink_decoder decoder; +#endif +}; + +#endif + +enum detools_unpack_usize_state_t { + detools_unpack_usize_state_first_t = 0, + detools_unpack_usize_state_consecutive_t +}; + +struct detools_unpack_usize_t { + enum detools_unpack_usize_state_t state; + int value; + int offset; +}; + +enum detools_crle_state_t { + detools_crle_state_idle_t = 0, + detools_crle_state_scattered_size_t, + detools_crle_state_scattered_data_t, + detools_crle_state_repeated_repetitions_t, + detools_crle_state_repeated_data_t, + detools_crle_state_repeated_data_read_t +}; + +struct detools_apply_patch_patch_reader_crle_t { + enum detools_crle_state_t state; + union { + struct { + size_t number_of_bytes_left; + struct detools_unpack_usize_t size; + } scattered; + struct { + uint8_t value; + size_t number_of_bytes_left; + struct detools_unpack_usize_t size; + } repeated; + } kind; +}; + +struct detools_apply_patch_patch_reader_t { + struct detools_apply_patch_chunk_t *patch_chunk_p; + struct detools_apply_patch_size_t size; + union { +#if DETOOLS_CONFIG_COMPRESSION_NONE == 1 + struct detools_apply_patch_patch_reader_none_t none; +#endif +#if DETOOLS_CONFIG_COMPRESSION_LZMA == 1 + struct detools_apply_patch_patch_reader_lzma_t lzma; +#endif +#if DETOOLS_CONFIG_COMPRESSION_CRLE == 1 + struct detools_apply_patch_patch_reader_crle_t crle; +#endif +#if DETOOLS_CONFIG_COMPRESSION_HEATSHRINK == 1 + struct detools_apply_patch_patch_reader_heatshrink_t heatshrink; +#endif + } compression; + int (*destroy)(struct detools_apply_patch_patch_reader_t *self_p); + int (*decompress)(struct detools_apply_patch_patch_reader_t *self_p, + uint8_t *buf_p, + size_t *size_p); +}; + +struct detools_apply_patch_chunk_t { + const uint8_t *buf_p; + size_t size; + size_t offset; +}; + +enum detools_apply_patch_state_t { + detools_apply_patch_state_init_t = 0, + detools_apply_patch_state_dfpatch_size_t, + detools_apply_patch_state_diff_size_t, + detools_apply_patch_state_diff_data_t, + detools_apply_patch_state_extra_size_t, + detools_apply_patch_state_extra_data_t, + detools_apply_patch_state_adjustment_t, + detools_apply_patch_state_done_t, + detools_apply_patch_state_failed_t +}; + +enum detools_apply_patch_init_state_t { + detools_apply_patch_init_state_fixed_header_t = 0, + detools_apply_patch_init_state_to_size_t +}; + +/** + * The apply patch data structure. + */ +struct detools_apply_patch_t { + detools_read_t from_read; + detools_seek_t from_seek; + size_t patch_size; + detools_write_t to_write; + void *arg_p; + enum detools_apply_patch_state_t state; + enum detools_apply_patch_init_state_t init_state; + int compression; + size_t patch_offset; + size_t to_offset; + size_t to_size; + int from_offset; + size_t chunk_size; + struct detools_apply_patch_patch_reader_t patch_reader; + struct detools_apply_patch_chunk_t chunk; + struct detools_apply_patch_size_t size; +}; + +enum detools_apply_patch_in_place_init_state_t { + detools_apply_patch_in_place_init_state_fixed_header_t = 0, + detools_apply_patch_in_place_init_state_memory_size_t, + detools_apply_patch_in_place_init_state_segment_size_t, + detools_apply_patch_in_place_init_state_shift_size_t, + detools_apply_patch_in_place_init_state_from_size_t, + detools_apply_patch_in_place_init_state_to_size_t +}; + +/** + * The in-place apply patch data structure. + */ +struct detools_apply_patch_in_place_t { + detools_mem_read_t mem_read; + detools_mem_write_t mem_write; + detools_mem_erase_t mem_erase; + detools_step_set_t step_set; + detools_step_get_t step_get; + size_t patch_size; + void *arg_p; + enum detools_apply_patch_state_t state; + enum detools_apply_patch_in_place_init_state_t init_state; + int compression; + int ongoing_step; + size_t to_pos; + size_t to_size; + size_t from_size; + size_t memory_size; + size_t segment_size; + size_t shift_size; + size_t chunk_size; + struct { + size_t index; + int from_offset; + size_t to_offset; + size_t to_size; + size_t to_pos; + } segment; + struct detools_apply_patch_patch_reader_t patch_reader; + struct detools_apply_patch_chunk_t chunk; + struct detools_apply_patch_size_t size; +}; + +/** + * Initialize given apply patch object. + * + * @param[out] self_p Apply patch object to initialize. + * @param[in] from_read Callback to read from-data. + * @param[in] from_seek Callback to seek from current position in from-data. + * @param[in] patch_size Patch size in bytes. Not used if + * `detools_apply_patch_restore()` is called + * immediately after this function. + * @param[in] to_write Destination callback. + * @param[in] arg_p Argument passed to the callbacks. + * + * @return zero(0) or negative error code. + */ +int detools_apply_patch_init(struct detools_apply_patch_t *self_p, + detools_read_t from_read, + detools_seek_t from_seek, + size_t patch_size, + detools_write_t to_write, + void *arg_p); + +/** + * Dump given apply patch object state. Call + * `detools_apply_patch_restore()` to restore an apply patch object to + * the dumped state. + * + * @param[in] self_p Apply patch object to dump. + * @param[in] write Write callback. + * + * @return zero(0) or negative error code. + */ +int detools_apply_patch_dump(struct detools_apply_patch_t *self_p, + detools_state_write_t state_write); + +/** + * Restore given apply patch object to given dumped + * state. + * + * `detools_apply_patch_get_to_offset()` and + * `detools_apply_patch_get_patch_offset()` are often called after + * this function to restore the to and patch streams. + * + * @param[in,out] self_p Initialized apply patch object to restore. + * @param[in] read Callback to read the dumped state. + * + * @return zero(0) or negative error code. + */ +int detools_apply_patch_restore(struct detools_apply_patch_t *self_p, + detools_state_read_t state_read); + +/** + * Get the current to stream offset. Often used to restore the to + * stream after restore. + * + * @param[in] self_p Apply patch object. + * + * @return The current to stream offset. + */ +size_t detools_apply_patch_get_to_offset(struct detools_apply_patch_t *self_p); + +/** + * Get the current patch stream offset. Often used to restore the + * patch stream after restore. + * + * @param[in] self_p Apply patch object. + * + * @return The current patch stream offset. + */ +size_t detools_apply_patch_get_patch_offset(struct detools_apply_patch_t *self_p); + +/** + * Call this function repeatedly until all patch data has been + * processed or an error occurres. Call detools_apply_patch_finalize() + * to finalize the patching, even if an error occurred. + * + * @param[in,out] self_p Initialized apply patch object. + * @param[in] patch_p Next chunk of the patch. + * @param[in] size Patch buffer size. + * + * @return zero(0) or negative error code. + */ +int detools_apply_patch_process(struct detools_apply_patch_t *self_p, + const uint8_t *patch_p, + size_t size); + +/** + * Call once after all data has been processed to finalize the + * patching. The value returned from this function should be ignored + * if an error occurred in detools_apply_patch_process(). + * + * @param[in,out] self_p Initialized apply patch object. + * + * @return Size of to-data in bytes if the patch was applied + * successfully, or negative error code. + */ +int detools_apply_patch_finalize(struct detools_apply_patch_t *self_p); + +/** + * Initialize given in-place apply patch object. + * + * @param[out] self_p In-place apply patch object to initialize. + * @param[in] mem_read Callback to read data. + * @param[in] mem_write Callback to write data. + * @param[in] mem_erase Callback to erase data. + * @param[in] step_set Callback to set the step. + * @param[in] step_get Callback to get the step. + * @param[in] patch_size Patch size in bytes. + * @param[in] arg_p Argument passed to the callbacks. + * + * @return zero(0) or negative error code. + */ +int detools_apply_patch_in_place_init( + struct detools_apply_patch_in_place_t *self_p, + detools_mem_read_t mem_read, + detools_mem_write_t mem_write, + detools_mem_erase_t mem_erase, + detools_step_set_t step_set, + detools_step_get_t step_get, + size_t patch_size, + void *arg_p); + +/** + * Call this function repeatedly until all patch data has been + * processed or an error occurres. Call + * detools_apply_patch_in_place_finalize() to finalize the patching, + * even if an error occurred. + * + * @param[in,out] self_p Initialized apply patch object. + * @param[in] patch_p Next chunk of the patch. + * @param[in] size Patch buffer size. + * + * @return zero(0) or negative error code. + */ +int detools_apply_patch_in_place_process( + struct detools_apply_patch_in_place_t *self_p, + const uint8_t *patch_p, + size_t size); + +/** + * Call once after all data has been processed to finalize the + * patching. The value returned from this function should be ignored + * if an error occurred in detools_apply_patch_in_place_process(). + * + * @param[in,out] self_p Initialized apply patch object. + * + * @return Size of to-data in bytes if the patch was applied + * successfully, or negative error code. + */ +int detools_apply_patch_in_place_finalize( + struct detools_apply_patch_in_place_t *self_p); + +/** + * Apply given patch using read, write and seek callbacks. + * + * @param[in] from_read Source read callback. + * @param[in] from_seek Source seek callback. + * @param[in] patch_read Patch read callback. + * @param[in] patch_size Patch size in bytes. + * @param[in] to_write Destination write callback. + * @param[in] arg_p Argument passed to all callbacks. + * + * @return Size of to-data in bytes or negative error code. + */ +int detools_apply_patch_callbacks(detools_read_t from_read, + detools_seek_t from_seek, + detools_read_t patch_read, + size_t patch_size, + detools_write_t to_write, + void *arg_p); + +/** + * Apply given in-place patch using read, write and erase callbacks. + * + * @param[in] mem_read Callback to read data. + * @param[in] mem_write Callback to write data. + * @param[in] mem_erase Callback to erase data. + * @param[in] step_set Callback to set the step. + * @param[in] step_get Callback to get the step. + * @param[in] patch_read Patch read callback. + * @param[in] patch_size Patch size in bytes. + * @param[in] arg_p Argument passed to the callbacks. + * + * @return Size of to-data in bytes or negative error code. + */ +int detools_apply_patch_in_place_callbacks(detools_mem_read_t mem_read, + detools_mem_write_t mem_write, + detools_mem_erase_t mem_erase, + detools_step_set_t step_set, + detools_step_get_t step_get, + detools_read_t patch_read, + size_t patch_size, + void *arg_p); + +#if DETOOLS_CONFIG_FILE_IO == 1 + +/** + * Apply given patch file to given from file and write the output to + * given to file. + * + * @param[in] from_p Source file name. + * @param[in] patch_p Patch file name. + * @param[in] to_p Destination file name. + * + * @return Size of to-data in bytes or negative error code. + */ +int detools_apply_patch_filenames(const char *from_p, + const char *patch_p, + const char *to_p); + +/** + * Apply given patch file to given memory file. + * + * @param[in] memory_p Memory file name. + * @param[in] patch_p Patch file name. + * @param[in] step_set Callback to set the step. + * @param[in] step_get Callback to get the step. + * + * @return Size of to-data in bytes or negative error code. + */ +int detools_apply_patch_in_place_filenames(const char *memory_p, + const char *patch_p, + detools_step_set_t step_set, + detools_step_get_t step_get); + +#endif + +/** + * Get the error string for given error code. + * + * @param[in] Error code. + * + * @return Error string. + */ +const char *detools_error_as_string(int error); + +#endif diff --git a/test/mocks/SHA256.h b/test/mocks/SHA256.h index b6e551a0..56d214b1 100644 --- a/test/mocks/SHA256.h +++ b/test/mocks/SHA256.h @@ -2,13 +2,102 @@ #include #include +#include -// Mock SHA256 class for testing -// Provides minimal interface to allow Utils.cpp to compile +// Real SHA-256 for native/host tests. Mirrors the rweather/Crypto streaming API used by +// src/Utils.cpp (update / finalize-with-truncation, plus resetHMAC / finalizeHMAC), so that +// Utils::sha256(...) produces correct digests on the host and OTA merkle tests are meaningful. +// (On-device the real rweather is used instead of this mock.) class SHA256 { + uint32_t h[8]; + uint8_t buf[64]; + uint32_t buf_len; + uint64_t total_len; + uint8_t hmac_key[64]; + + static uint32_t ror(uint32_t x, uint32_t n) { return (x >> n) | (x << (32 - n)); } + + void process(const uint8_t* p) { + static const uint32_t K[64] = { + 0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5, + 0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174, + 0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da, + 0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967, + 0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85, + 0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070, + 0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3, + 0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2}; + uint32_t w[64]; + for (int i = 0; i < 16; i++) + w[i] = ((uint32_t)p[i*4] << 24) | ((uint32_t)p[i*4+1] << 16) | ((uint32_t)p[i*4+2] << 8) | p[i*4+3]; + for (int i = 16; i < 64; i++) { + uint32_t s0 = ror(w[i-15],7) ^ ror(w[i-15],18) ^ (w[i-15] >> 3); + uint32_t s1 = ror(w[i-2],17) ^ ror(w[i-2],19) ^ (w[i-2] >> 10); + w[i] = w[i-16] + s0 + w[i-7] + s1; + } + uint32_t a=h[0],b=h[1],c=h[2],d=h[3],e=h[4],f=h[5],g=h[6],hh=h[7]; + for (int i = 0; i < 64; i++) { + uint32_t S1 = ror(e,6) ^ ror(e,11) ^ ror(e,25); + uint32_t ch = (e & f) ^ ((~e) & g); + uint32_t t1 = hh + S1 + ch + K[i] + w[i]; + uint32_t S0 = ror(a,2) ^ ror(a,13) ^ ror(a,22); + uint32_t maj = (a & b) ^ (a & c) ^ (b & c); + uint32_t t2 = S0 + maj; + hh=g; g=f; f=e; e=d+t1; d=c; c=b; b=a; a=t1+t2; + } + h[0]+=a; h[1]+=b; h[2]+=c; h[3]+=d; h[4]+=e; h[5]+=f; h[6]+=g; h[7]+=hh; + } + public: - void update(const uint8_t* data, size_t len) {} - void finalize(uint8_t* hash, size_t hashLen) {} - void resetHMAC(const uint8_t* key, size_t keyLen) {} - void finalizeHMAC(const uint8_t* key, size_t keyLen, uint8_t* hash, size_t hashLen) {} + SHA256() { reset(); } + + void reset() { + h[0]=0x6a09e667; h[1]=0xbb67ae85; h[2]=0x3c6ef372; h[3]=0xa54ff53a; + h[4]=0x510e527f; h[5]=0x9b05688c; h[6]=0x1f83d9ab; h[7]=0x5be0cd19; + buf_len = 0; total_len = 0; + } + + void update(const uint8_t* data, size_t n) { + total_len += n; + while (n) { + size_t take = 64 - buf_len; if (take > n) take = n; + memcpy(buf + buf_len, data, take); buf_len += (uint32_t)take; data += take; n -= take; + if (buf_len == 64) { process(buf); buf_len = 0; } + } + } + + void finalize(uint8_t* out, size_t out_len) { + uint64_t bits = total_len * 8; + buf[buf_len++] = 0x80; + if (buf_len > 56) { while (buf_len < 64) buf[buf_len++] = 0; process(buf); buf_len = 0; } + while (buf_len < 56) buf[buf_len++] = 0; + for (int i = 0; i < 8; i++) buf[56 + i] = (uint8_t)(bits >> (56 - 8*i)); + process(buf); buf_len = 0; + uint8_t full[32]; + for (int i = 0; i < 8; i++) { + full[i*4] = (uint8_t)(h[i] >> 24); full[i*4+1] = (uint8_t)(h[i] >> 16); + full[i*4+2] = (uint8_t)(h[i] >> 8); full[i*4+3] = (uint8_t)(h[i]); + } + if (out_len > 32) out_len = 32; + memcpy(out, full, out_len); + } + + // Standard HMAC-SHA256 (kept correct for API parity; OTA tests don't exercise it). + void resetHMAC(const uint8_t* key, size_t keyLen) { + memset(hmac_key, 0, 64); + if (keyLen > 64) { SHA256 t; t.update(key, keyLen); t.finalize(hmac_key, 32); } + else memcpy(hmac_key, key, keyLen); + reset(); + uint8_t ipad[64]; + for (int i = 0; i < 64; i++) ipad[i] = hmac_key[i] ^ 0x36; + update(ipad, 64); + } + + void finalizeHMAC(const uint8_t* key, size_t keyLen, uint8_t* out, size_t out_len) { + (void)key; (void)keyLen; + uint8_t inner[32]; finalize(inner, 32); + uint8_t opad[64]; + for (int i = 0; i < 64; i++) opad[i] = hmac_key[i] ^ 0x5c; + reset(); update(opad, 64); update(inner, 32); finalize(out, out_len); + } }; diff --git a/test/test_ota/mota_vectors.h b/test/test_ota/mota_vectors.h new file mode 100644 index 00000000..06571730 --- /dev/null +++ b/test/test_ota/mota_vectors.h @@ -0,0 +1,115 @@ +// AUTO-GENERATED by tools/mota/gen_vectors.py — do not edit by hand. +// Cross-check vectors: a real .mota from the reference packager (motalib.py). +#pragma once +#include + +static const uint8_t MOTA_VEC[5371] = {109,79,84,65,251,20,0,0,1,1,18,68,51,34,17,0,0,16,1,153,20,0,0,153,20,0,0,10,175,252,9,108,103,145,119,80,26,44,231,124,151,8,14,131,41,90,36,227,134,142,35,227,246,136,63,104,34,211,80,66,55,32,79,153,0,255,255,255,255,136,178,44,23,110,140,98,143,139,154,17,11,47,111,103,108,222,151,146,129,34,221,184,64,163,28,6,189,70,62,57,35,188,26,173,189,228,139,22,151,108,8,7,23,55,59,129,154,6,143,50,183,166,179,139,107,56,114,150,71,207,222,1,194,206,40,178,108,87,71,39,55,245,195,86,26,23,97,24,91,216,88,154,67,206,11,186,117,137,31,249,236,96,20,141,75,212,160,158,226,220,92,147,49,180,17,11,169,58,197,74,252,20,218,59,221,25,97,71,116,162,213,93,41,94,90,53,171,68,179,239,174,165,18,155,162,43,136,186,62,41,118,97,69,253,236,163,176,142,56,175,83,215,196,198,14,58,210,8,206,80,102,68,16,54,233,241,145,224,183,80,54,167,127,101,226,234,164,117,36,67,35,63,190,143,137,67,191,149,109,229,149,102,92,56,255,255,35,130,126,23,193,12,220,28,39,160,40,202,174,108,152,16,98,97,152,255,119,135,64,248,141,220,241,2,174,184,29,174,226,137,192,68,196,164,87,28,75,111,40,116,0,244,184,224,184,67,248,128,195,45,129,233,27,222,160,76,215,163,129,155,50,39,95,195,41,138,244,199,236,135,235,0,153,82,125,4,28,237,92,224,252,212,206,78,61,14,61,224,145,242,20,21,187,124,208,17,250,194,136,196,32,32,168,121,242,140,42,67,135,223,155,108,246,54,237,138,193,186,176,51,182,79,102,254,171,166,95,112,230,132,115,30,63,57,16,86,5,150,141,58,150,56,1,18,181,161,15,58,17,231,8,220,84,18,131,60,71,171,124,54,138,33,185,239,225,146,147,121,62,200,121,206,104,48,24,24,168,110,90,108,105,119,221,186,13,172,167,251,165,25,15,103,186,86,204,220,27,63,49,48,137,114,35,108,46,71,118,63,223,236,19,113,206,220,219,140,25,12,166,255,138,214,3,248,23,237,192,217,60,42,104,124,123,54,221,102,231,15,42,97,0,252,99,67,237,200,200,116,73,108,178,245,187,254,200,142,169,183,124,39,48,75,55,247,14,148,188,138,15,191,80,14,12,149,122,128,235,218,135,40,14,245,130,20,217,47,17,152,17,172,220,60,103,30,241,227,145,63,148,152,10,158,20,107,168,149,144,133,80,239,66,52,171,183,80,61,67,101,33,171,165,76,117,80,237,192,239,18,2,117,159,255,144,255,25,18,137,54,129,67,33,238,89,225,17,225,62,94,72,40,112,213,139,180,77,156,251,252,206,167,135,2,170,209,141,76,238,169,26,240,224,34,67,29,227,27,190,141,39,69,72,154,53,183,87,52,175,162,218,67,129,125,64,231,232,216,13,23,162,108,212,70,11,0,85,197,33,163,250,67,41,189,113,141,180,109,143,2,28,19,241,226,176,231,38,139,9,213,94,149,141,37,110,32,10,78,93,230,238,203,248,220,10,230,91,53,174,63,170,26,90,199,143,226,223,104,249,158,191,39,236,238,60,221,41,249,204,207,45,225,105,6,45,188,236,85,200,238,105,205,171,221,188,207,63,68,40,201,179,27,97,223,9,219,120,56,51,209,235,117,89,78,210,203,223,58,57,6,168,49,102,84,71,221,17,247,197,71,89,164,130,102,173,251,215,137,84,240,7,29,224,248,66,45,148,246,251,67,9,27,152,111,88,186,201,80,111,155,251,130,29,98,230,147,48,65,11,181,111,0,133,236,206,137,175,184,240,189,188,171,50,93,110,17,242,170,235,84,159,80,169,217,31,184,230,76,129,79,170,104,83,103,178,75,141,32,49,107,170,240,97,173,191,231,44,157,145,77,103,140,213,0,77,73,53,110,201,148,155,167,82,119,113,113,172,54,130,121,203,230,245,203,188,43,168,21,72,131,169,162,158,85,23,209,243,192,60,172,79,57,206,50,37,6,11,62,251,121,156,217,196,18,116,106,226,161,147,49,183,178,98,126,102,62,37,167,176,1,228,192,220,197,226,27,199,108,56,45,205,245,178,132,118,12,142,63,234,217,31,116,34,205,118,170,135,252,143,152,81,243,193,228,113,156,208,184,228,129,109,212,232,140,114,229,40,190,220,121,115,66,192,63,215,163,70,196,199,133,124,160,61,70,112,19,182,73,60,69,85,81,228,138,20,35,38,59,98,177,39,180,54,16,106,104,84,138,119,106,15,52,213,107,99,231,197,149,242,178,5,219,225,195,147,97,122,1,241,90,76,192,99,218,228,244,213,107,137,191,188,139,204,154,229,56,124,56,69,111,124,7,99,86,171,173,204,103,185,42,215,119,235,32,251,159,136,6,232,100,151,144,169,6,21,164,109,34,221,118,46,12,66,97,83,54,116,83,86,194,225,97,71,192,243,212,107,64,213,20,120,4,191,138,13,255,243,89,57,166,17,199,245,166,10,193,7,243,63,51,214,5,159,39,61,32,121,171,29,144,242,55,119,179,65,196,94,42,155,155,246,191,183,29,199,209,41,246,79,27,148,6,237,79,147,173,232,245,96,101,241,183,50,19,151,176,212,160,62,26,178,197,77,217,175,153,206,30,203,251,144,200,10,88,136,109,169,94,17,129,165,87,3,217,107,210,125,27,110,245,92,162,228,212,117,181,39,111,45,187,133,247,166,69,157,206,235,137,198,123,119,111,211,187,151,68,82,218,62,212,239,22,71,225,115,62,192,118,145,156,171,97,86,7,126,217,83,46,124,54,90,204,66,87,71,225,152,179,225,70,142,2,132,242,48,21,61,184,104,125,142,194,61,176,121,165,182,125,114,202,4,23,75,56,103,177,62,78,169,148,94,121,141,135,88,108,255,190,140,84,90,179,116,69,78,64,59,30,184,49,80,30,190,137,243,195,176,47,49,55,189,123,70,185,150,250,194,134,152,72,251,25,213,49,75,58,92,45,77,3,181,136,32,70,11,249,13,141,74,178,241,32,163,222,192,125,26,223,3,146,72,120,122,112,87,47,247,13,64,240,220,122,29,210,16,102,125,18,147,161,175,13,38,38,207,144,242,77,21,254,63,30,142,195,106,155,152,202,158,57,198,133,97,115,232,113,76,220,150,253,109,78,145,158,15,156,245,189,25,242,195,53,160,54,67,169,20,40,61,44,141,19,40,0,104,115,176,152,120,74,8,59,73,180,72,179,220,116,18,175,59,236,67,201,202,160,150,169,205,239,50,108,29,139,57,165,38,232,68,211,36,18,15,42,202,78,152,191,211,145,235,73,112,31,119,176,77,179,103,241,69,128,138,126,112,20,153,10,227,110,188,82,154,64,6,23,58,246,172,214,220,147,150,243,5,255,195,172,210,68,147,10,195,193,44,120,132,166,113,234,71,46,255,149,111,162,208,125,248,23,120,89,104,85,82,171,26,219,41,84,105,177,126,73,169,241,102,208,194,140,9,116,22,80,64,82,29,248,197,103,221,131,211,252,0,168,222,138,118,105,13,48,132,92,159,193,127,160,113,194,13,52,68,140,33,237,73,112,225,178,124,31,7,249,161,155,204,61,181,40,79,141,3,141,104,23,57,254,215,233,29,118,242,30,165,213,39,127,238,183,74,130,180,69,106,213,123,250,120,62,116,141,37,98,48,235,153,130,191,225,34,221,17,70,197,202,218,106,87,239,201,129,68,210,0,72,185,76,214,150,148,255,168,125,221,38,114,137,123,88,85,141,195,139,96,116,238,82,222,48,251,178,61,146,98,59,219,198,105,11,81,190,121,180,233,207,97,98,253,169,202,210,166,251,38,126,246,9,32,128,247,151,84,222,25,223,216,112,25,134,233,116,3,184,36,104,222,167,248,39,19,120,200,248,67,86,159,177,101,166,20,218,84,218,172,219,136,97,244,81,160,183,227,194,124,223,138,9,158,17,60,161,175,235,73,255,58,191,23,111,250,25,194,162,180,223,25,113,42,177,76,231,7,11,83,203,14,75,91,95,110,37,62,135,105,144,174,202,46,43,44,20,156,222,97,158,174,61,127,233,149,36,59,118,163,65,117,65,170,2,230,205,119,230,73,173,139,40,18,113,241,88,252,150,76,163,246,108,176,64,116,216,77,50,255,98,218,123,27,60,97,146,91,147,75,254,179,75,5,250,212,168,101,70,2,144,221,175,199,190,249,12,233,155,190,127,213,231,231,73,198,204,58,155,205,90,56,162,48,158,64,173,193,184,196,168,174,214,35,160,24,231,160,165,10,79,201,112,8,148,93,187,33,23,232,75,83,191,106,44,51,33,201,138,224,248,93,135,128,233,69,212,42,65,233,211,241,123,247,206,75,191,222,86,205,29,119,246,19,36,193,247,57,220,173,185,172,250,101,247,216,205,142,93,23,202,101,3,67,137,31,116,94,172,191,172,67,149,97,210,163,240,95,27,172,59,120,6,158,226,241,143,83,234,156,56,165,16,162,210,118,232,179,77,166,104,29,35,11,242,9,77,254,126,29,24,60,227,137,34,99,116,94,171,243,190,178,242,138,107,150,190,186,39,226,106,167,25,213,125,157,104,240,243,71,8,176,94,55,113,113,243,60,218,92,25,251,175,94,139,230,250,165,91,15,101,70,48,247,31,242,217,210,116,23,169,54,164,163,152,248,5,12,201,85,62,253,32,201,144,52,17,212,195,141,53,150,55,208,222,59,84,198,37,201,230,152,0,70,219,251,37,252,33,138,64,204,44,28,169,221,6,33,3,91,202,201,60,150,82,4,44,67,13,32,189,107,134,29,190,16,121,114,199,92,131,151,27,115,128,56,242,157,11,186,200,232,221,168,133,77,117,164,246,7,15,255,122,216,102,109,175,27,125,182,232,113,18,230,20,82,155,37,16,32,70,159,162,149,140,182,83,97,254,152,135,75,116,129,154,110,25,203,179,29,218,167,166,224,196,141,184,221,55,110,115,227,58,105,86,211,116,102,106,186,24,80,109,80,170,65,95,244,39,175,236,121,17,23,212,21,23,110,24,190,189,95,207,33,142,15,150,244,143,143,84,171,31,105,90,223,170,240,192,108,222,234,184,13,247,73,153,79,90,26,147,129,54,39,168,123,57,216,27,89,216,142,94,29,195,71,146,57,206,109,216,143,249,196,209,159,157,172,164,142,6,155,237,168,212,177,68,7,46,69,179,195,79,235,86,89,1,46,222,36,144,168,102,17,36,189,162,248,7,23,191,135,55,96,107,116,87,40,94,79,184,83,198,241,145,152,21,226,13,39,40,193,158,12,172,20,69,113,169,108,124,155,113,106,69,55,193,131,29,88,110,28,72,173,173,151,124,134,170,78,11,56,101,252,153,14,1,52,77,242,54,196,35,195,65,74,83,30,1,127,191,110,44,33,97,136,180,58,128,143,213,171,206,90,18,101,220,189,10,111,4,117,235,19,220,80,147,109,146,103,181,163,106,74,29,103,5,247,83,43,205,242,158,117,212,176,235,92,22,111,216,27,62,111,150,102,134,20,101,222,79,190,86,56,85,199,43,19,130,162,29,135,130,49,231,198,89,89,186,245,209,165,208,37,60,26,37,65,50,44,154,39,194,194,167,19,45,243,197,160,126,118,193,144,194,148,114,174,236,225,144,164,162,252,159,82,221,248,160,80,38,112,17,120,113,161,77,203,70,151,14,90,129,18,79,118,115,9,14,94,212,73,19,165,221,250,218,23,157,152,129,98,118,148,141,244,202,189,229,10,115,232,207,146,166,48,82,154,121,128,38,245,15,115,26,207,230,214,87,251,182,21,129,165,44,10,63,181,112,253,112,134,133,156,40,93,95,234,72,99,104,198,86,173,153,13,202,161,165,85,16,84,24,142,173,98,72,64,185,218,168,246,232,154,223,38,85,20,149,169,36,234,89,79,247,167,178,169,100,33,152,181,240,21,79,143,96,164,202,84,208,32,171,179,212,242,189,255,175,233,134,23,165,171,108,130,92,4,92,79,46,243,54,87,242,196,124,49,57,255,35,39,19,75,216,201,25,129,197,138,213,189,226,134,9,169,86,224,196,158,33,152,96,39,41,46,212,177,197,159,207,231,42,184,112,11,105,93,173,184,60,248,113,156,72,192,191,200,114,59,136,61,79,247,207,200,120,231,213,49,94,173,242,146,252,112,118,196,72,199,97,128,135,107,247,41,209,51,205,154,35,223,64,13,164,123,223,95,141,239,26,182,216,132,217,31,72,21,195,41,69,115,231,131,37,212,111,23,242,233,56,209,115,226,89,238,6,106,13,101,128,95,60,98,254,20,95,57,7,81,238,25,214,182,166,85,202,37,35,9,73,234,212,120,178,212,35,194,180,120,114,157,1,231,20,4,65,55,213,38,140,240,186,155,135,108,28,198,73,60,77,31,12,61,107,163,203,159,117,16,28,214,231,127,152,137,4,161,131,147,61,183,36,74,109,0,157,90,61,146,106,47,170,171,21,134,249,92,17,244,134,139,129,201,253,129,141,5,99,223,120,11,162,99,251,95,64,191,4,91,201,17,88,61,187,168,160,26,197,148,188,193,85,34,11,90,139,86,208,164,44,212,199,175,118,251,178,122,161,46,207,34,16,183,198,241,117,9,75,51,11,202,51,226,10,80,238,79,131,101,253,208,139,121,64,9,192,165,48,73,91,220,199,12,221,167,84,69,31,204,94,111,227,102,190,112,229,244,98,86,249,47,127,177,127,94,236,204,132,68,205,21,186,108,20,110,154,254,210,46,139,75,82,26,20,83,169,75,78,114,154,183,109,42,176,113,89,114,10,186,222,233,90,157,255,111,70,163,250,202,242,14,19,171,163,103,93,131,205,191,173,40,243,7,36,217,155,173,200,112,8,32,17,60,199,165,93,92,98,243,145,8,154,39,173,115,242,94,95,113,195,19,146,35,135,93,101,80,166,71,63,245,29,6,188,47,127,132,99,233,143,30,67,198,66,180,114,54,255,156,73,177,234,255,125,51,31,34,218,18,115,44,230,182,113,255,22,207,174,247,216,252,81,170,88,181,16,140,138,74,228,76,217,40,182,181,237,179,163,44,203,92,130,57,31,252,51,202,35,60,202,126,6,92,141,146,94,119,205,251,141,33,156,226,22,16,79,101,255,183,184,122,134,105,196,104,210,147,18,32,248,81,164,18,115,119,174,132,88,32,224,212,199,141,163,150,46,196,247,33,110,128,233,222,14,212,31,132,39,77,42,41,82,239,181,57,88,242,240,132,229,72,216,20,64,50,162,244,141,70,32,160,77,157,136,23,128,164,43,151,241,148,39,43,168,159,184,230,154,86,215,236,144,10,211,221,7,20,11,242,164,197,147,67,166,53,196,146,106,158,163,7,127,227,160,139,74,164,244,77,123,62,206,206,175,103,76,116,18,176,15,40,112,106,123,118,52,87,155,36,80,220,183,81,187,252,220,88,249,102,33,194,94,131,143,27,81,61,119,31,68,115,63,36,24,12,74,242,98,221,157,107,63,246,221,230,40,208,83,239,147,184,80,48,195,40,127,254,131,119,127,225,78,127,5,23,241,100,129,117,247,61,55,149,90,12,12,72,126,152,225,215,167,172,120,73,137,2,216,27,110,34,225,67,186,93,195,103,93,11,102,13,145,143,49,92,141,73,18,98,129,115,195,140,71,211,253,159,174,156,30,32,249,24,100,95,203,252,86,142,240,93,193,36,50,154,130,102,128,10,11,9,35,182,85,205,121,132,116,38,155,228,131,35,83,238,156,81,41,100,253,157,189,215,76,151,86,129,212,130,136,125,181,144,76,121,208,4,94,84,172,28,250,106,149,78,203,230,185,223,176,161,6,152,121,67,247,167,200,248,198,148,147,58,184,13,149,122,43,134,161,184,158,198,215,97,37,210,174,62,8,146,242,179,28,48,4,112,80,107,38,105,176,52,105,128,198,156,235,120,223,217,188,186,15,180,35,132,53,143,83,255,169,122,134,96,80,244,44,117,233,136,87,139,90,173,197,222,184,174,164,205,177,67,156,123,49,245,63,71,142,76,57,241,249,251,76,197,73,180,53,176,180,125,81,122,89,143,239,239,203,184,70,73,31,146,173,139,97,229,250,101,209,88,244,197,205,37,74,10,73,244,182,20,88,236,113,167,65,191,122,54,51,211,137,69,238,143,178,69,35,27,157,189,150,61,62,12,171,231,135,57,163,59,13,25,105,84,183,120,25,174,197,35,1,246,140,250,237,40,104,167,239,225,224,121,122,166,51,193,246,73,82,73,165,15,232,196,22,166,146,59,136,189,185,217,239,9,233,235,44,106,225,214,45,239,235,9,255,214,101,201,126,47,239,191,246,223,237,74,224,9,2,76,145,154,27,237,251,85,72,116,253,164,139,134,126,227,240,34,217,129,119,69,49,207,28,84,41,187,117,165,65,183,47,3,188,86,202,75,145,172,193,49,44,156,219,163,229,103,211,109,131,83,22,102,171,24,47,251,35,122,82,239,63,1,66,98,60,114,192,68,244,84,77,149,185,146,2,66,167,92,177,60,15,170,30,119,78,40,103,175,128,236,229,227,180,197,79,176,30,163,234,240,75,94,157,56,56,245,34,122,39,116,191,253,155,95,106,179,140,233,120,193,137,205,170,211,55,195,63,174,193,152,223,201,20,134,114,135,180,92,19,234,144,28,15,212,140,231,129,51,146,137,38,42,83,218,133,113,29,174,52,183,149,125,23,230,130,114,207,14,116,33,131,106,116,144,14,143,118,172,206,78,185,5,101,65,209,0,190,55,148,18,11,108,88,179,16,138,254,15,239,228,17,252,239,120,8,73,104,46,196,34,196,164,250,186,165,246,107,95,254,228,97,114,222,234,232,96,96,20,174,246,169,223,138,34,167,220,89,30,45,254,137,100,135,32,186,250,57,213,0,193,5,250,76,118,172,184,139,108,136,97,210,58,63,117,88,39,70,48,239,224,185,195,28,8,207,169,107,157,196,239,226,227,4,61,52,17,25,152,8,114,153,172,180,223,12,62,189,11,102,112,59,138,55,193,221,198,14,35,128,254,74,59,208,234,187,147,81,147,153,197,172,209,82,60,77,224,36,252,169,133,56,105,76,70,15,142,242,151,225,188,233,44,160,173,109,142,126,12,248,88,241,164,171,97,201,134,81,178,106,104,38,76,96,47,193,137,121,61,217,57,76,219,181,36,206,118,234,14,143,105,247,106,142,135,34,99,62,65,52,84,165,20,236,115,216,94,23,137,185,212,48,13,68,96,172,154,154,10,223,18,48,205,194,150,185,171,143,55,122,53,222,232,85,77,244,232,3,54,239,48,246,189,30,191,255,193,122,234,62,178,154,180,52,101,234,61,141,82,198,72,97,119,136,166,91,78,66,92,131,225,127,119,25,205,251,184,120,194,214,81,234,52,94,80,105,11,144,221,56,189,37,4,66,141,239,149,148,184,106,75,39,50,84,58,97,145,213,62,127,140,167,241,175,86,65,195,210,125,247,185,164,189,125,117,43,187,203,90,43,35,184,139,125,47,234,227,138,253,164,245,15,134,8,214,216,19,241,209,171,12,195,1,105,35,215,161,59,17,181,38,2,55,129,116,95,15,158,163,170,239,157,233,123,168,124,4,1,136,141,105,3,4,135,184,70,137,250,73,4,128,208,178,172,110,206,240,232,45,27,235,24,134,38,61,49,158,134,64,208,90,68,203,101,20,95,245,103,117,144,62,253,178,57,76,175,211,217,20,167,253,219,166,194,8,23,103,97,96,141,121,14,163,2,179,43,21,127,216,111,165,200,84,144,250,219,249,24,229,135,235,10,58,54,230,222,177,227,145,121,69,11,236,19,175,236,71,230,139,144,168,8,45,237,217,80,4,246,53,150,36,192,210,182,210,101,237,19,76,41,144,61,145,213,217,99,173,229,138,84,98,193,189,35,202,253,176,185,20,128,190,249,88,13,25,111,59,214,19,87,154,196,157,244,152,101,248,198,83,7,162,69,200,254,115,125,58,91,141,240,96,110,47,174,149,169,97,21,197,158,75,204,63,182,18,21,68,39,97,182,200,162,39,189,99,81,92,27,23,1,241,78,113,92,194,69,26,33,22,47,110,114,142,142,131,104,26,6,22,90,141,23,152,153,200,83,221,98,3,74,105,99,199,21,185,230,143,231,254,250,62,146,133,43,175,97,43,35,68,77,68,126,37,16,42,111,70,107,76,123,200,19,92,64,241,63,184,160,126,152,157,50,117,27,34,77,1,242,101,85,215,158,97,205,220,84,112,85,110,208,210,220,166,249,152,34,76,82,154,242,177,51,122,80,45,246,101,247,81,74,188,177,162,125,247,147,200,62,83,96,71,209,201,100,93,29,238,144,51,151,255,139,46,174,196,140,6,243,186,118,241,181,53,112,204,74,212,177,17,209,217,203,203,104,172,127,35,162,77,61,64,168,39,183,108,202,96,18,114,253,153,122,149,102,136,129,236,235,222,177,107,139,9,202,247,92,179,229,207,137,152,163,234,21,27,196,63,168,170,90,42,165,156,11,144,251,165,173,165,102,247,192,84,247,203,110,27,2,25,66,56,130,191,132,142,148,176,147,56,114,95,100,118,197,173,150,176,128,38,88,255,6,123,26,75,106,235,246,21,29,212,240,186,36,89,79,87,116,200,52,133,123,89,121,24,112,184,178,115,81,17,76,11,207,181,29,5,217,87,165,27,173,204,42,238,251,189,62,132,44,141,40,84,143,109,245,118,59,204,103,161,47,47,163,168,110,101,7,188,157,226,50,115,151,109,99,1,180,54,52,71,192,180,207,203,16,147,26,204,223,137,47,93,83,50,117,29,67,171,220,125,135,247,163,80,152,99,156,100,150,29,89,90,216,117,158,44,208,172,180,204,76,235,157,151,21,172,34,80,30,61,78,29,46,95,177,36,130,99,107,152,35,147,98,108,47,124,162,137,177,235,165,238,255,44,142,42,125,73,35,47,80,215,115,158,13,221,91,243,2,124,34,49,208,98,246,143,129,167,126,104,175,125,106,181,215,113,125,42,21,144,7,203,194,56,74,8,70,57,137,73,43,199,117,144,190,197,196,126,140,130,28,146,29,68,198,139,210,253,93,138,210,193,10,194,184,112,139,55,253,108,26,188,212,167,192,63,76,223,255,8,115,67,89,220,209,22,112,221,254,30,198,207,60,53,207,188,150,176,89,220,181,156,161,109,42,157,35,200,52,208,52,206,15,145,89,136,71,152,137,43,82,250,180,74,74,146,68,243,219,131,252,230,173,208,24,34,246,192,201,105,235,15,254,70,221,167,32,179,221,33,63,37,182,82,212,63,194,215,173,100,125,36,147,161,68,160,106,96,115,19,162,203,225,196,23,103,131,191,71,177,239,224,94,116,243,124,83,148,0,222,198,216,187,23,187,248,117,162,170,178,91,217,16,203,136,101,55,247,109,211,54,126,68,82,212,72,86,140,147,33,144,218,124,201,87,228,174,195,12,11,25,160,200,214,117,4,31,237,219,40,112,116,0,253,245,109,51,254,176,232,33,225,166,77,40,223,238,224,70,23,167,92,64,21,95,170,231,166,42,13,101,160,78,185,179,193,59,109,167,23,180,24,0,54,122,19,33,151,57,132,174,113,2,2,178,87,209,30,252,220,108,177,33,122,18,58,98,22,187,206,26,26,80,94,225,76,35,97,198,208,192,223,34,164,254,173,36,17,135,144,2,156,166,42,112,89,184,54,160,191,38,235,105,157,175,113,220,55,22,229,25,35,192,31,150,186,98,89,246,109,80,202,35,63,70,165,21,63,141,153,153,184,155,72,199,240,176,6,217,216,168,224,77,52,132,155,130,48,191,100,75,165,13,200,229,203,244,61,126,98,28,61,127,163,152,18,135,227,250,3,252,92,239,69,110,100,70,137,20,0,0,122,7,213,179,185,90,134,189,118,107,52,57,54}; + +static const uint32_t MOTA_VEC_LEN = 5371; +static const uint32_t EXP_TARGET_ID = 0x11223344u; +static const uint32_t EXP_FW_VERSION = 0x01100000u; +static const uint32_t EXP_IMAGE_SIZE = 5273u; +static const uint32_t EXP_PAYLOAD_SIZE = 5273u; +static const uint32_t EXP_BLOCK_COUNT = 6u; +static const uint8_t EXP_BLOCK_SIZE_LOG2 = 10; +static const uint8_t EXP_CODEC_ID = 0; +static const uint8_t EXP_MERKLE_ROOT[4] = {175,252,9,108}; + +static const uint8_t EXP_IMAGE_HASH[32] = {103,145,119,80,26,44,231,124,151,8,14,131,41,90,36,227,134,142,35,227,246,136,63,104,34,211,80,66,55,32,79,153}; + +static const uint32_t PROOF_INDEX = 2u; +static const uint8_t PROOF_NSIB = 3; +static const uint8_t PROOF_SIBLINGS[12] = {47,111,103,108,64,78,181,132,227,162,219,122}; + +static const uint32_t T0_COUNT = 5u; +static const uint8_t T0_LEAVES[20] = {123,202,230,102,137,105,224,241,146,163,254,76,91,140,169,45,125,31,146,228}; + +static const uint8_t T0_ROOT[4] = {37,197,150,102}; + +static const uint16_t T0_POFF[5] = {0,12,24,36,48}; +static const uint8_t T0_PNSIB[5] = {3,3,3,3,1}; +static const uint8_t T0_PBLOB[52] = {137,105,224,241,76,132,127,136,125,31,146,228,123,202,230,102,76,132,127,136,125,31,146,228,91,140,169,45,113,104,86,244,125,31,146,228,146,163,254,76,113,104,86,244,125,31,146,228,32,57,180,151}; + +static const uint32_t T1_COUNT = 7u; +static const uint8_t T1_LEAVES[28] = {219,8,204,214,152,44,227,79,189,183,207,45,57,133,249,14,162,50,142,13,114,148,72,187,230,159,8,8}; + +static const uint8_t T1_ROOT[4] = {124,128,11,251}; + +static const uint16_t T1_POFF[7] = {0,12,24,36,48,60,72}; +static const uint8_t T1_PNSIB[7] = {3,3,3,3,3,3,2}; +static const uint8_t T1_PBLOB[80] = {152,44,227,79,6,192,47,1,91,61,26,208,219,8,204,214,6,192,47,1,91,61,26,208,57,133,249,14,208,126,61,254,91,61,26,208,189,183,207,45,208,126,61,254,91,61,26,208,114,148,72,187,230,159,8,8,21,58,53,95,162,50,142,13,230,159,8,8,21,58,53,95,50,200,22,34,21,58,53,95}; + +static const uint32_t T2_COUNT = 8u; +static const uint8_t T2_LEAVES[32] = {133,246,245,113,87,20,181,196,236,26,12,243,169,182,211,36,223,172,151,252,28,151,88,171,2,138,202,222,83,66,56,124}; + +static const uint8_t T2_ROOT[4] = {230,153,143,58}; + +static const uint16_t T2_POFF[8] = {0,12,24,36,48,60,72,84}; +static const uint8_t T2_PNSIB[8] = {3,3,3,3,3,3,3,3}; +static const uint8_t T2_PBLOB[96] = {87,20,181,196,185,61,131,58,188,95,217,235,133,246,245,113,185,61,131,58,188,95,217,235,169,182,211,36,9,144,114,130,188,95,217,235,236,26,12,243,9,144,114,130,188,95,217,235,28,151,88,171,182,122,229,190,175,255,207,112,223,172,151,252,182,122,229,190,175,255,207,112,83,66,56,124,132,32,7,1,175,255,207,112,2,138,202,222,132,32,7,1,175,255,207,112}; + +static const uint32_t T3_COUNT = 65u; +static const uint8_t T3_LEAVES[260] = {228,177,31,222,213,237,94,231,50,158,16,45,240,127,150,228,208,46,120,34,47,80,173,66,32,115,193,214,60,40,73,37,201,143,198,211,67,231,14,145,23,231,206,70,151,82,247,207,115,74,95,131,213,33,121,147,248,20,31,29,209,72,70,23,241,87,234,199,6,202,12,131,134,233,213,230,82,81,214,46,254,186,73,15,59,134,85,179,59,184,245,117,217,237,95,37,157,89,225,249,112,43,154,184,72,149,130,37,183,19,100,29,203,32,180,97,196,69,203,119,144,197,108,45,1,133,113,168,36,18,78,210,75,94,150,40,116,19,98,94,169,42,12,32,192,70,6,142,125,129,126,57,25,67,158,181,8,30,134,82,88,158,230,175,192,176,37,16,195,177,239,43,191,233,217,203,1,13,34,120,83,7,204,235,200,216,114,244,220,22,118,100,210,158,109,213,107,142,73,151,244,88,103,219,166,50,243,79,183,246,47,87,18,1,5,200,72,44,140,231,168,129,59,227,6,227,103,55,54,204,4,229,59,253,227,61,20,147,26,54,186,174,38,57,191,247,231,13,192,44,230,40,192,252,173,250,93,54,156,198}; + +static const uint8_t T3_ROOT[4] = {143,88,46,110}; + +static const uint16_t T3_POFF[65] = {0,28,56,84,112,140,168,196,224,252,280,308,336,364,392,420,448,476,504,532,560,588,616,644,672,700,728,756,784,812,840,868,896,924,952,980,1008,1036,1064,1092,1120,1148,1176,1204,1232,1260,1288,1316,1344,1372,1400,1428,1456,1484,1512,1540,1568,1596,1624,1652,1680,1708,1736,1764,1792}; +static const uint8_t T3_PNSIB[65] = {7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,1}; +static const uint8_t T3_PBLOB[1796] = {213,237,94,231,226,151,15,25,186,191,115,46,87,73,67,144,23,152,173,217,71,3,57,148,93,54,156,198,228,177,31,222,226,151,15,25,186,191,115,46,87,73,67,144,23,152,173,217,71,3,57,148,93,54,156,198,240,127,150,228,171,21,9,181,186,191,115,46,87,73,67,144,23,152,173,217,71,3,57,148,93,54,156,198,50,158,16,45,171,21,9,181,186,191,115,46,87,73,67,144,23,152,173,217,71,3,57,148,93,54,156,198,47,80,173,66,142,196,71,64,14,98,188,211,87,73,67,144,23,152,173,217,71,3,57,148,93,54,156,198,208,46,120,34,142,196,71,64,14,98,188,211,87,73,67,144,23,152,173,217,71,3,57,148,93,54,156,198,60,40,73,37,50,228,24,222,14,98,188,211,87,73,67,144,23,152,173,217,71,3,57,148,93,54,156,198,32,115,193,214,50,228,24,222,14,98,188,211,87,73,67,144,23,152,173,217,71,3,57,148,93,54,156,198,67,231,14,145,71,231,4,206,205,182,67,188,47,60,59,137,23,152,173,217,71,3,57,148,93,54,156,198,201,143,198,211,71,231,4,206,205,182,67,188,47,60,59,137,23,152,173,217,71,3,57,148,93,54,156,198,151,82,247,207,124,211,70,56,205,182,67,188,47,60,59,137,23,152,173,217,71,3,57,148,93,54,156,198,23,231,206,70,124,211,70,56,205,182,67,188,47,60,59,137,23,152,173,217,71,3,57,148,93,54,156,198,213,33,121,147,47,185,242,240,12,185,30,58,47,60,59,137,23,152,173,217,71,3,57,148,93,54,156,198,115,74,95,131,47,185,242,240,12,185,30,58,47,60,59,137,23,152,173,217,71,3,57,148,93,54,156,198,209,72,70,23,198,222,217,47,12,185,30,58,47,60,59,137,23,152,173,217,71,3,57,148,93,54,156,198,248,20,31,29,198,222,217,47,12,185,30,58,47,60,59,137,23,152,173,217,71,3,57,148,93,54,156,198,6,202,12,131,37,124,190,231,133,59,19,106,100,204,178,18,126,106,252,248,71,3,57,148,93,54,156,198,241,87,234,199,37,124,190,231,133,59,19,106,100,204,178,18,126,106,252,248,71,3,57,148,93,54,156,198,82,81,214,46,25,35,102,192,133,59,19,106,100,204,178,18,126,106,252,248,71,3,57,148,93,54,156,198,134,233,213,230,25,35,102,192,133,59,19,106,100,204,178,18,126,106,252,248,71,3,57,148,93,54,156,198,59,134,85,179,180,250,218,148,37,137,24,30,100,204,178,18,126,106,252,248,71,3,57,148,93,54,156,198,254,186,73,15,180,250,218,148,37,137,24,30,100,204,178,18,126,106,252,248,71,3,57,148,93,54,156,198,217,237,95,37,163,134,12,115,37,137,24,30,100,204,178,18,126,106,252,248,71,3,57,148,93,54,156,198,59,184,245,117,163,134,12,115,37,137,24,30,100,204,178,18,126,106,252,248,71,3,57,148,93,54,156,198,112,43,154,184,171,186,157,117,95,173,243,202,15,216,163,10,126,106,252,248,71,3,57,148,93,54,156,198,157,89,225,249,171,186,157,117,95,173,243,202,15,216,163,10,126,106,252,248,71,3,57,148,93,54,156,198,183,19,100,29,117,33,228,235,95,173,243,202,15,216,163,10,126,106,252,248,71,3,57,148,93,54,156,198,72,149,130,37,117,33,228,235,95,173,243,202,15,216,163,10,126,106,252,248,71,3,57,148,93,54,156,198,196,69,203,119,109,168,123,61,165,127,162,224,15,216,163,10,126,106,252,248,71,3,57,148,93,54,156,198,203,32,180,97,109,168,123,61,165,127,162,224,15,216,163,10,126,106,252,248,71,3,57,148,93,54,156,198,1,133,113,168,19,170,184,98,165,127,162,224,15,216,163,10,126,106,252,248,71,3,57,148,93,54,156,198,144,197,108,45,19,170,184,98,165,127,162,224,15,216,163,10,126,106,252,248,71,3,57,148,93,54,156,198,75,94,150,40,133,173,206,145,50,201,231,4,30,94,224,127,107,72,112,56,161,178,183,96,93,54,156,198,36,18,78,210,133,173,206,145,50,201,231,4,30,94,224,127,107,72,112,56,161,178,183,96,93,54,156,198,169,42,12,32,206,234,112,47,50,201,231,4,30,94,224,127,107,72,112,56,161,178,183,96,93,54,156,198,116,19,98,94,206,234,112,47,50,201,231,4,30,94,224,127,107,72,112,56,161,178,183,96,93,54,156,198,125,129,126,57,170,111,85,90,106,61,62,225,30,94,224,127,107,72,112,56,161,178,183,96,93,54,156,198,192,70,6,142,170,111,85,90,106,61,62,225,30,94,224,127,107,72,112,56,161,178,183,96,93,54,156,198,8,30,134,82,84,11,55,210,106,61,62,225,30,94,224,127,107,72,112,56,161,178,183,96,93,54,156,198,25,67,158,181,84,11,55,210,106,61,62,225,30,94,224,127,107,72,112,56,161,178,183,96,93,54,156,198,192,176,37,16,73,215,44,124,75,85,11,61,81,85,123,40,107,72,112,56,161,178,183,96,93,54,156,198,88,158,230,175,73,215,44,124,75,85,11,61,81,85,123,40,107,72,112,56,161,178,183,96,93,54,156,198,191,233,217,203,104,27,130,168,75,85,11,61,81,85,123,40,107,72,112,56,161,178,183,96,93,54,156,198,195,177,239,43,104,27,130,168,75,85,11,61,81,85,123,40,107,72,112,56,161,178,183,96,93,54,156,198,83,7,204,235,77,162,227,126,184,101,244,22,81,85,123,40,107,72,112,56,161,178,183,96,93,54,156,198,1,13,34,120,77,162,227,126,184,101,244,22,81,85,123,40,107,72,112,56,161,178,183,96,93,54,156,198,220,22,118,100,132,206,108,94,184,101,244,22,81,85,123,40,107,72,112,56,161,178,183,96,93,54,156,198,200,216,114,244,132,206,108,94,184,101,244,22,81,85,123,40,107,72,112,56,161,178,183,96,93,54,156,198,107,142,73,151,74,78,179,108,244,44,244,141,152,179,15,151,5,193,169,14,161,178,183,96,93,54,156,198,210,158,109,213,74,78,179,108,244,44,244,141,152,179,15,151,5,193,169,14,161,178,183,96,93,54,156,198,166,50,243,79,186,100,96,255,244,44,244,141,152,179,15,151,5,193,169,14,161,178,183,96,93,54,156,198,244,88,103,219,186,100,96,255,244,44,244,141,152,179,15,151,5,193,169,14,161,178,183,96,93,54,156,198,18,1,5,200,112,90,214,108,23,138,61,93,152,179,15,151,5,193,169,14,161,178,183,96,93,54,156,198,183,246,47,87,112,90,214,108,23,138,61,93,152,179,15,151,5,193,169,14,161,178,183,96,93,54,156,198,168,129,59,227,68,36,234,87,23,138,61,93,152,179,15,151,5,193,169,14,161,178,183,96,93,54,156,198,72,44,140,231,68,36,234,87,23,138,61,93,152,179,15,151,5,193,169,14,161,178,183,96,93,54,156,198,54,204,4,229,190,153,32,138,33,227,4,234,139,152,195,249,5,193,169,14,161,178,183,96,93,54,156,198,6,227,103,55,190,153,32,138,33,227,4,234,139,152,195,249,5,193,169,14,161,178,183,96,93,54,156,198,20,147,26,54,96,205,181,224,33,227,4,234,139,152,195,249,5,193,169,14,161,178,183,96,93,54,156,198,59,253,227,61,96,205,181,224,33,227,4,234,139,152,195,249,5,193,169,14,161,178,183,96,93,54,156,198,191,247,231,13,229,50,248,97,89,252,12,65,139,152,195,249,5,193,169,14,161,178,183,96,93,54,156,198,186,174,38,57,229,50,248,97,89,252,12,65,139,152,195,249,5,193,169,14,161,178,183,96,93,54,156,198,192,252,173,250,142,15,70,253,89,252,12,65,139,152,195,249,5,193,169,14,161,178,183,96,93,54,156,198,192,44,230,40,142,15,70,253,89,252,12,65,139,152,195,249,5,193,169,14,161,178,183,96,93,54,156,198,149,210,13,187}; + +static const uint32_t T4_COUNT = 100u; +static const uint8_t T4_LEAVES[400] = {250,6,181,225,56,207,131,195,200,254,34,10,238,105,33,132,48,50,156,13,143,158,201,222,178,194,103,10,124,149,60,141,9,94,243,101,75,219,122,125,213,33,64,40,32,228,121,213,226,253,231,144,75,135,234,131,152,214,212,106,51,145,3,183,128,183,173,26,69,67,252,15,42,219,249,19,124,72,186,210,66,55,64,53,27,111,249,47,219,212,89,93,243,63,146,136,20,157,216,177,50,200,32,211,137,241,63,21,222,236,109,140,89,124,160,1,24,252,27,231,105,182,145,139,59,254,133,183,194,117,230,133,86,39,227,203,37,170,103,5,12,76,12,151,82,192,112,121,156,72,138,44,13,141,142,83,126,173,15,67,193,11,13,252,218,226,147,150,252,127,155,49,168,56,120,149,138,173,127,54,5,91,4,203,100,103,34,42,224,120,111,134,236,40,86,53,123,141,164,71,178,229,86,75,83,180,208,56,78,46,180,59,154,244,17,9,205,174,70,75,97,138,191,59,188,95,40,182,254,210,175,49,38,68,197,58,155,82,244,130,58,135,66,250,162,37,231,120,15,232,85,246,64,60,232,16,250,85,139,178,226,21,172,135,128,162,227,198,90,175,208,53,161,165,158,250,207,38,249,195,125,26,112,252,188,142,55,227,7,88,106,227,22,155,231,103,250,222,43,181,121,239,180,119,204,205,9,203,29,142,3,40,113,191,12,53,22,240,105,201,205,146,175,25,133,29,26,65,72,143,138,248,90,133,22,232,188,178,200,127,202,39,235,197,94,167,110,82,62,235,228,215,17,17,117,54,102,46,37,22,164,40,183,159,25,55,80,182,241,239,12,185,12,200,147,241,76,90,125,48,23,173,235,75,97,85,135,96,255,21,97,103,85,13,180,129,176,168,148,246}; + +static const uint8_t T4_ROOT[4] = {169,167,185,243}; + +static const uint16_t T4_POFF[100] = {0,28,56,84,112,140,168,196,224,252,280,308,336,364,392,420,448,476,504,532,560,588,616,644,672,700,728,756,784,812,840,868,896,924,952,980,1008,1036,1064,1092,1120,1148,1176,1204,1232,1260,1288,1316,1344,1372,1400,1428,1456,1484,1512,1540,1568,1596,1624,1652,1680,1708,1736,1764,1792,1820,1848,1876,1904,1932,1960,1988,2016,2044,2072,2100,2128,2156,2184,2212,2240,2268,2296,2324,2352,2380,2408,2436,2464,2492,2520,2548,2576,2604,2632,2660,2688,2704,2720,2736}; +static const uint8_t T4_PNSIB[100] = {7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,4,4,4,4}; +static const uint8_t T4_PBLOB[2752] = {56,207,131,195,144,4,34,70,211,98,27,29,229,111,37,122,45,29,32,241,1,82,253,243,18,149,46,121,250,6,181,225,144,4,34,70,211,98,27,29,229,111,37,122,45,29,32,241,1,82,253,243,18,149,46,121,238,105,33,132,21,3,59,222,211,98,27,29,229,111,37,122,45,29,32,241,1,82,253,243,18,149,46,121,200,254,34,10,21,3,59,222,211,98,27,29,229,111,37,122,45,29,32,241,1,82,253,243,18,149,46,121,143,158,201,222,96,188,135,52,159,141,76,84,229,111,37,122,45,29,32,241,1,82,253,243,18,149,46,121,48,50,156,13,96,188,135,52,159,141,76,84,229,111,37,122,45,29,32,241,1,82,253,243,18,149,46,121,124,149,60,141,98,199,71,87,159,141,76,84,229,111,37,122,45,29,32,241,1,82,253,243,18,149,46,121,178,194,103,10,98,199,71,87,159,141,76,84,229,111,37,122,45,29,32,241,1,82,253,243,18,149,46,121,75,219,122,125,169,14,251,128,243,113,185,185,54,115,204,16,45,29,32,241,1,82,253,243,18,149,46,121,9,94,243,101,169,14,251,128,243,113,185,185,54,115,204,16,45,29,32,241,1,82,253,243,18,149,46,121,32,228,121,213,35,96,203,98,243,113,185,185,54,115,204,16,45,29,32,241,1,82,253,243,18,149,46,121,213,33,64,40,35,96,203,98,243,113,185,185,54,115,204,16,45,29,32,241,1,82,253,243,18,149,46,121,75,135,234,131,212,196,76,34,41,226,170,218,54,115,204,16,45,29,32,241,1,82,253,243,18,149,46,121,226,253,231,144,212,196,76,34,41,226,170,218,54,115,204,16,45,29,32,241,1,82,253,243,18,149,46,121,51,145,3,183,204,217,111,117,41,226,170,218,54,115,204,16,45,29,32,241,1,82,253,243,18,149,46,121,152,214,212,106,204,217,111,117,41,226,170,218,54,115,204,16,45,29,32,241,1,82,253,243,18,149,46,121,69,67,252,15,57,132,159,140,174,142,2,180,177,111,134,244,146,34,242,217,1,82,253,243,18,149,46,121,128,183,173,26,57,132,159,140,174,142,2,180,177,111,134,244,146,34,242,217,1,82,253,243,18,149,46,121,124,72,186,210,8,162,138,25,174,142,2,180,177,111,134,244,146,34,242,217,1,82,253,243,18,149,46,121,42,219,249,19,8,162,138,25,174,142,2,180,177,111,134,244,146,34,242,217,1,82,253,243,18,149,46,121,27,111,249,47,19,49,135,113,74,129,59,240,177,111,134,244,146,34,242,217,1,82,253,243,18,149,46,121,66,55,64,53,19,49,135,113,74,129,59,240,177,111,134,244,146,34,242,217,1,82,253,243,18,149,46,121,243,63,146,136,75,253,36,162,74,129,59,240,177,111,134,244,146,34,242,217,1,82,253,243,18,149,46,121,219,212,89,93,75,253,36,162,74,129,59,240,177,111,134,244,146,34,242,217,1,82,253,243,18,149,46,121,50,200,32,211,196,6,205,75,139,127,232,79,21,35,67,161,146,34,242,217,1,82,253,243,18,149,46,121,20,157,216,177,196,6,205,75,139,127,232,79,21,35,67,161,146,34,242,217,1,82,253,243,18,149,46,121,222,236,109,140,155,133,32,81,139,127,232,79,21,35,67,161,146,34,242,217,1,82,253,243,18,149,46,121,137,241,63,21,155,133,32,81,139,127,232,79,21,35,67,161,146,34,242,217,1,82,253,243,18,149,46,121,24,252,27,231,16,192,89,45,184,230,20,55,21,35,67,161,146,34,242,217,1,82,253,243,18,149,46,121,89,124,160,1,16,192,89,45,184,230,20,55,21,35,67,161,146,34,242,217,1,82,253,243,18,149,46,121,59,254,133,183,58,26,222,255,184,230,20,55,21,35,67,161,146,34,242,217,1,82,253,243,18,149,46,121,105,182,145,139,58,26,222,255,184,230,20,55,21,35,67,161,146,34,242,217,1,82,253,243,18,149,46,121,86,39,227,203,37,93,129,116,201,76,71,155,177,116,125,30,199,197,197,5,236,20,11,118,18,149,46,121,194,117,230,133,37,93,129,116,201,76,71,155,177,116,125,30,199,197,197,5,236,20,11,118,18,149,46,121,12,76,12,151,171,23,55,158,201,76,71,155,177,116,125,30,199,197,197,5,236,20,11,118,18,149,46,121,37,170,103,5,171,23,55,158,201,76,71,155,177,116,125,30,199,197,197,5,236,20,11,118,18,149,46,121,156,72,138,44,178,196,184,248,236,104,3,167,177,116,125,30,199,197,197,5,236,20,11,118,18,149,46,121,82,192,112,121,178,196,184,248,236,104,3,167,177,116,125,30,199,197,197,5,236,20,11,118,18,149,46,121,126,173,15,67,148,162,128,54,236,104,3,167,177,116,125,30,199,197,197,5,236,20,11,118,18,149,46,121,13,141,142,83,148,162,128,54,236,104,3,167,177,116,125,30,199,197,197,5,236,20,11,118,18,149,46,121,218,226,147,150,0,21,170,193,76,191,87,153,39,66,60,32,199,197,197,5,236,20,11,118,18,149,46,121,193,11,13,252,0,21,170,193,76,191,87,153,39,66,60,32,199,197,197,5,236,20,11,118,18,149,46,121,168,56,120,149,17,155,65,75,76,191,87,153,39,66,60,32,199,197,197,5,236,20,11,118,18,149,46,121,252,127,155,49,17,155,65,75,76,191,87,153,39,66,60,32,199,197,197,5,236,20,11,118,18,149,46,121,5,91,4,203,81,193,61,17,63,43,120,95,39,66,60,32,199,197,197,5,236,20,11,118,18,149,46,121,138,173,127,54,81,193,61,17,63,43,120,95,39,66,60,32,199,197,197,5,236,20,11,118,18,149,46,121,224,120,111,134,197,230,31,135,63,43,120,95,39,66,60,32,199,197,197,5,236,20,11,118,18,149,46,121,100,103,34,42,197,230,31,135,63,43,120,95,39,66,60,32,199,197,197,5,236,20,11,118,18,149,46,121,123,141,164,71,92,60,129,206,78,17,61,245,190,40,237,164,62,10,40,12,236,20,11,118,18,149,46,121,236,40,86,53,92,60,129,206,78,17,61,245,190,40,237,164,62,10,40,12,236,20,11,118,18,149,46,121,83,180,208,56,113,215,5,130,78,17,61,245,190,40,237,164,62,10,40,12,236,20,11,118,18,149,46,121,178,229,86,75,113,215,5,130,78,17,61,245,190,40,237,164,62,10,40,12,236,20,11,118,18,149,46,121,154,244,17,9,241,185,75,36,120,5,10,203,190,40,237,164,62,10,40,12,236,20,11,118,18,149,46,121,78,46,180,59,241,185,75,36,120,5,10,203,190,40,237,164,62,10,40,12,236,20,11,118,18,149,46,121,97,138,191,59,36,148,140,82,120,5,10,203,190,40,237,164,62,10,40,12,236,20,11,118,18,149,46,121,205,174,70,75,36,148,140,82,120,5,10,203,190,40,237,164,62,10,40,12,236,20,11,118,18,149,46,121,254,210,175,49,58,244,150,107,254,225,141,150,117,212,105,39,62,10,40,12,236,20,11,118,18,149,46,121,188,95,40,182,58,244,150,107,254,225,141,150,117,212,105,39,62,10,40,12,236,20,11,118,18,149,46,121,155,82,244,130,3,23,222,243,254,225,141,150,117,212,105,39,62,10,40,12,236,20,11,118,18,149,46,121,38,68,197,58,3,23,222,243,254,225,141,150,117,212,105,39,62,10,40,12,236,20,11,118,18,149,46,121,162,37,231,120,111,3,141,159,193,86,116,188,117,212,105,39,62,10,40,12,236,20,11,118,18,149,46,121,58,135,66,250,111,3,141,159,193,86,116,188,117,212,105,39,62,10,40,12,236,20,11,118,18,149,46,121,64,60,232,16,135,211,9,178,193,86,116,188,117,212,105,39,62,10,40,12,236,20,11,118,18,149,46,121,15,232,85,246,135,211,9,178,193,86,116,188,117,212,105,39,62,10,40,12,236,20,11,118,18,149,46,121,226,21,172,135,111,71,248,116,217,80,60,219,188,142,11,51,245,200,136,13,137,103,31,19,18,72,110,254,250,85,139,178,111,71,248,116,217,80,60,219,188,142,11,51,245,200,136,13,137,103,31,19,18,72,110,254,90,175,208,53,118,128,133,179,217,80,60,219,188,142,11,51,245,200,136,13,137,103,31,19,18,72,110,254,128,162,227,198,118,128,133,179,217,80,60,219,188,142,11,51,245,200,136,13,137,103,31,19,18,72,110,254,207,38,249,195,37,131,44,47,77,82,94,73,188,142,11,51,245,200,136,13,137,103,31,19,18,72,110,254,161,165,158,250,37,131,44,47,77,82,94,73,188,142,11,51,245,200,136,13,137,103,31,19,18,72,110,254,188,142,55,227,180,0,66,204,77,82,94,73,188,142,11,51,245,200,136,13,137,103,31,19,18,72,110,254,125,26,112,252,180,0,66,204,77,82,94,73,188,142,11,51,245,200,136,13,137,103,31,19,18,72,110,254,22,155,231,103,9,180,25,20,71,242,120,146,238,91,144,22,245,200,136,13,137,103,31,19,18,72,110,254,7,88,106,227,9,180,25,20,71,242,120,146,238,91,144,22,245,200,136,13,137,103,31,19,18,72,110,254,121,239,180,119,106,219,202,18,71,242,120,146,238,91,144,22,245,200,136,13,137,103,31,19,18,72,110,254,250,222,43,181,106,219,202,18,71,242,120,146,238,91,144,22,245,200,136,13,137,103,31,19,18,72,110,254,29,142,3,40,29,164,246,98,55,204,76,233,238,91,144,22,245,200,136,13,137,103,31,19,18,72,110,254,204,205,9,203,29,164,246,98,55,204,76,233,238,91,144,22,245,200,136,13,137,103,31,19,18,72,110,254,22,240,105,201,197,130,110,101,55,204,76,233,238,91,144,22,245,200,136,13,137,103,31,19,18,72,110,254,113,191,12,53,197,130,110,101,55,204,76,233,238,91,144,22,245,200,136,13,137,103,31,19,18,72,110,254,133,29,26,65,5,22,180,187,234,247,123,20,249,90,67,149,254,114,155,109,137,103,31,19,18,72,110,254,205,146,175,25,5,22,180,187,234,247,123,20,249,90,67,149,254,114,155,109,137,103,31,19,18,72,110,254,90,133,22,232,144,41,193,104,234,247,123,20,249,90,67,149,254,114,155,109,137,103,31,19,18,72,110,254,72,143,138,248,144,41,193,104,234,247,123,20,249,90,67,149,254,114,155,109,137,103,31,19,18,72,110,254,202,39,235,197,208,130,10,34,23,254,125,179,249,90,67,149,254,114,155,109,137,103,31,19,18,72,110,254,188,178,200,127,208,130,10,34,23,254,125,179,249,90,67,149,254,114,155,109,137,103,31,19,18,72,110,254,62,235,228,215,5,218,223,93,23,254,125,179,249,90,67,149,254,114,155,109,137,103,31,19,18,72,110,254,94,167,110,82,5,218,223,93,23,254,125,179,249,90,67,149,254,114,155,109,137,103,31,19,18,72,110,254,102,46,37,22,127,3,61,253,120,198,248,114,124,220,117,120,254,114,155,109,137,103,31,19,18,72,110,254,17,17,117,54,127,3,61,253,120,198,248,114,124,220,117,120,254,114,155,109,137,103,31,19,18,72,110,254,25,55,80,182,112,68,206,98,120,198,248,114,124,220,117,120,254,114,155,109,137,103,31,19,18,72,110,254,164,40,183,159,112,68,206,98,120,198,248,114,124,220,117,120,254,114,155,109,137,103,31,19,18,72,110,254,12,200,147,241,240,129,157,149,214,175,107,55,124,220,117,120,254,114,155,109,137,103,31,19,18,72,110,254,241,239,12,185,240,129,157,149,214,175,107,55,124,220,117,120,254,114,155,109,137,103,31,19,18,72,110,254,23,173,235,75,247,5,120,126,214,175,107,55,124,220,117,120,254,114,155,109,137,103,31,19,18,72,110,254,76,90,125,48,247,5,120,126,214,175,107,55,124,220,117,120,254,114,155,109,137,103,31,19,18,72,110,254,255,21,97,103,4,191,4,227,166,66,253,139,18,72,110,254,97,85,135,96,4,191,4,227,166,66,253,139,18,72,110,254,176,168,148,246,118,100,253,16,166,66,253,139,18,72,110,254,85,13,180,129,118,100,253,16,166,66,253,139,18,72,110,254}; + +static const uint32_t T5_COUNT = 255u; +static const uint8_t T5_LEAVES[1020] = {99,92,33,97,215,30,198,101,110,35,237,220,30,114,79,78,74,29,101,121,222,43,226,84,17,236,91,25,47,133,95,23,43,24,222,170,190,97,229,65,76,57,46,5,125,175,81,95,129,156,155,63,139,113,160,112,9,231,52,203,252,7,66,123,48,252,216,98,69,222,198,113,178,127,158,162,81,195,118,245,12,104,14,245,195,109,80,118,156,90,70,58,116,110,104,111,98,218,154,9,0,246,201,54,21,252,233,206,122,167,217,33,27,229,83,158,113,117,40,232,181,76,250,165,202,26,5,178,24,172,180,91,248,103,94,4,159,132,24,153,192,212,194,197,91,144,28,217,220,138,69,32,88,62,37,53,222,242,192,136,108,15,206,62,175,37,53,189,122,8,1,151,251,79,38,134,218,146,209,8,209,69,107,224,101,240,227,215,64,22,211,222,200,168,86,206,166,14,152,208,136,237,24,102,21,75,241,208,229,172,0,114,82,123,56,244,139,243,123,44,152,92,225,42,18,138,48,214,248,103,199,37,85,246,102,71,235,63,48,78,118,250,138,255,183,97,23,93,190,117,84,232,123,112,151,77,179,219,237,53,190,42,18,93,107,191,176,137,124,122,74,20,142,110,105,47,54,175,181,107,222,49,152,50,17,253,130,64,26,56,103,115,55,250,154,108,68,73,225,98,107,219,25,215,125,6,206,178,170,145,250,144,206,100,175,127,158,19,247,69,153,200,212,164,188,45,2,3,199,126,46,239,154,24,194,145,219,53,73,113,240,97,62,59,45,184,154,234,46,208,65,46,153,97,185,189,80,96,1,217,222,177,59,129,129,235,150,104,135,177,190,75,226,233,82,45,254,83,235,151,179,166,244,74,106,234,100,179,159,56,71,63,37,219,60,174,72,210,203,202,117,29,49,129,193,83,137,16,54,223,126,253,200,222,108,14,42,75,153,9,237,246,91,48,20,82,162,31,204,182,70,123,170,142,148,194,92,104,120,163,114,162,123,143,133,175,134,57,34,19,231,207,204,211,133,58,121,174,231,52,136,250,88,177,238,169,212,146,91,241,161,135,170,251,202,84,228,237,231,18,165,178,89,96,218,135,59,199,248,202,177,222,234,229,141,108,120,41,139,215,36,65,173,224,128,97,225,251,106,100,234,202,236,222,20,34,145,135,162,162,186,253,245,44,87,151,161,224,84,113,162,206,100,154,175,183,109,12,155,201,93,121,19,131,120,4,60,73,19,222,160,237,167,13,195,180,108,243,228,36,28,72,216,0,231,211,112,6,34,247,115,50,7,25,79,120,224,173,218,66,44,6,104,153,101,176,42,94,72,63,198,240,243,34,3,7,51,230,53,223,243,118,3,198,52,105,9,172,174,40,34,202,177,224,62,9,73,147,168,22,37,254,205,200,135,62,227,125,114,160,12,107,0,103,126,218,5,89,61,218,9,212,71,125,200,61,131,83,235,30,236,207,185,131,237,198,225,61,208,112,102,57,123,99,71,28,42,28,25,186,35,229,158,90,56,92,133,16,82,153,52,230,163,184,45,20,64,214,172,131,94,26,249,52,201,130,242,21,132,147,240,84,64,243,51,52,187,146,246,205,26,211,105,175,61,221,209,21,230,137,88,0,176,105,144,150,137,124,228,223,2,180,169,190,150,90,170,98,205,172,40,91,71,138,242,213,140,102,41,178,241,149,113,112,82,60,194,159,187,87,136,172,169,188,177,199,28,202,9,199,205,7,162,249,44,225,132,229,226,98,158,22,105,45,129,185,103,208,200,45,189,25,202,91,179,42,213,54,2,126,142,224,63,170,137,186,245,105,74,26,77,175,88,126,216,140,18,235,136,40,156,192,38,255,28,233,255,161,39,109,33,227,82,143,178,188,149,99,100,231,41,39,133,234,183,230,240,243,133,198,56,49,198,172,6,104,36,29,157,93,82,16,201,114,111,225,187,176,227,62,90,123,100,22,147,98,176,102,96,95,228,116,217,35,147,129,183,208,131,237,195,100,89,243,185,43,17,209,44,159,179,151,84,159,167,187,80,191,118,89,121,54,37,82,106,170,109,95,140,81,224,108,165,161,252,226,92,204,207,129,169,31,4,27,80,174,208,127,241,68,233,224,38,93,233,162,74,54,164,245,145,211,22,15,219,10,211,93,159,62,71,14,45,19,225,239,69,93,176,167,66,95,75,158,79,2,221,52,115,89,137,108,87,155,209,12,100,54,71,201,170,17,132,15,135,37,31,139,240,132,99,151,51,84,187,224,195,234,66,91,87,129,171,22,20,242,205,129,26,178}; + +static const uint8_t T5_ROOT[4] = {125,131,30,129}; + +static const uint16_t T5_POFF[255] = {0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,480,512,544,576,608,640,672,704,736,768,800,832,864,896,928,960,992,1024,1056,1088,1120,1152,1184,1216,1248,1280,1312,1344,1376,1408,1440,1472,1504,1536,1568,1600,1632,1664,1696,1728,1760,1792,1824,1856,1888,1920,1952,1984,2016,2048,2080,2112,2144,2176,2208,2240,2272,2304,2336,2368,2400,2432,2464,2496,2528,2560,2592,2624,2656,2688,2720,2752,2784,2816,2848,2880,2912,2944,2976,3008,3040,3072,3104,3136,3168,3200,3232,3264,3296,3328,3360,3392,3424,3456,3488,3520,3552,3584,3616,3648,3680,3712,3744,3776,3808,3840,3872,3904,3936,3968,4000,4032,4064,4096,4128,4160,4192,4224,4256,4288,4320,4352,4384,4416,4448,4480,4512,4544,4576,4608,4640,4672,4704,4736,4768,4800,4832,4864,4896,4928,4960,4992,5024,5056,5088,5120,5152,5184,5216,5248,5280,5312,5344,5376,5408,5440,5472,5504,5536,5568,5600,5632,5664,5696,5728,5760,5792,5824,5856,5888,5920,5952,5984,6016,6048,6080,6112,6144,6176,6208,6240,6272,6304,6336,6368,6400,6432,6464,6496,6528,6560,6592,6624,6656,6688,6720,6752,6784,6816,6848,6880,6912,6944,6976,7008,7040,7072,7104,7136,7168,7200,7232,7264,7296,7328,7360,7392,7424,7456,7488,7520,7552,7584,7616,7648,7680,7712,7744,7776,7808,7840,7872,7904,7936,7968,8000,8032,8064,8096,8128}; +static const uint8_t T5_PNSIB[255] = {8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,7}; +static const uint8_t T5_PBLOB[8156] = {215,30,198,101,190,120,224,191,253,188,254,181,170,224,185,20,232,113,80,94,246,33,129,111,238,68,253,253,233,186,40,163,99,92,33,97,190,120,224,191,253,188,254,181,170,224,185,20,232,113,80,94,246,33,129,111,238,68,253,253,233,186,40,163,30,114,79,78,121,1,165,22,253,188,254,181,170,224,185,20,232,113,80,94,246,33,129,111,238,68,253,253,233,186,40,163,110,35,237,220,121,1,165,22,253,188,254,181,170,224,185,20,232,113,80,94,246,33,129,111,238,68,253,253,233,186,40,163,222,43,226,84,141,8,229,42,34,203,204,102,170,224,185,20,232,113,80,94,246,33,129,111,238,68,253,253,233,186,40,163,74,29,101,121,141,8,229,42,34,203,204,102,170,224,185,20,232,113,80,94,246,33,129,111,238,68,253,253,233,186,40,163,47,133,95,23,206,109,234,200,34,203,204,102,170,224,185,20,232,113,80,94,246,33,129,111,238,68,253,253,233,186,40,163,17,236,91,25,206,109,234,200,34,203,204,102,170,224,185,20,232,113,80,94,246,33,129,111,238,68,253,253,233,186,40,163,190,97,229,65,204,162,212,253,158,221,103,119,94,138,252,177,232,113,80,94,246,33,129,111,238,68,253,253,233,186,40,163,43,24,222,170,204,162,212,253,158,221,103,119,94,138,252,177,232,113,80,94,246,33,129,111,238,68,253,253,233,186,40,163,125,175,81,95,207,138,11,26,158,221,103,119,94,138,252,177,232,113,80,94,246,33,129,111,238,68,253,253,233,186,40,163,76,57,46,5,207,138,11,26,158,221,103,119,94,138,252,177,232,113,80,94,246,33,129,111,238,68,253,253,233,186,40,163,139,113,160,112,70,90,148,231,170,148,193,238,94,138,252,177,232,113,80,94,246,33,129,111,238,68,253,253,233,186,40,163,129,156,155,63,70,90,148,231,170,148,193,238,94,138,252,177,232,113,80,94,246,33,129,111,238,68,253,253,233,186,40,163,252,7,66,123,221,141,84,219,170,148,193,238,94,138,252,177,232,113,80,94,246,33,129,111,238,68,253,253,233,186,40,163,9,231,52,203,221,141,84,219,170,148,193,238,94,138,252,177,232,113,80,94,246,33,129,111,238,68,253,253,233,186,40,163,69,222,198,113,5,81,192,127,46,0,223,26,254,26,86,132,76,178,240,104,246,33,129,111,238,68,253,253,233,186,40,163,48,252,216,98,5,81,192,127,46,0,223,26,254,26,86,132,76,178,240,104,246,33,129,111,238,68,253,253,233,186,40,163,81,195,118,245,162,175,255,73,46,0,223,26,254,26,86,132,76,178,240,104,246,33,129,111,238,68,253,253,233,186,40,163,178,127,158,162,162,175,255,73,46,0,223,26,254,26,86,132,76,178,240,104,246,33,129,111,238,68,253,253,233,186,40,163,195,109,80,118,127,36,23,8,63,65,173,128,254,26,86,132,76,178,240,104,246,33,129,111,238,68,253,253,233,186,40,163,12,104,14,245,127,36,23,8,63,65,173,128,254,26,86,132,76,178,240,104,246,33,129,111,238,68,253,253,233,186,40,163,116,110,104,111,201,228,150,142,63,65,173,128,254,26,86,132,76,178,240,104,246,33,129,111,238,68,253,253,233,186,40,163,156,90,70,58,201,228,150,142,63,65,173,128,254,26,86,132,76,178,240,104,246,33,129,111,238,68,253,253,233,186,40,163,0,246,201,54,128,178,99,183,233,161,204,65,5,250,190,144,76,178,240,104,246,33,129,111,238,68,253,253,233,186,40,163,98,218,154,9,128,178,99,183,233,161,204,65,5,250,190,144,76,178,240,104,246,33,129,111,238,68,253,253,233,186,40,163,122,167,217,33,47,15,253,93,233,161,204,65,5,250,190,144,76,178,240,104,246,33,129,111,238,68,253,253,233,186,40,163,21,252,233,206,47,15,253,93,233,161,204,65,5,250,190,144,76,178,240,104,246,33,129,111,238,68,253,253,233,186,40,163,113,117,40,232,216,234,112,92,191,252,51,40,5,250,190,144,76,178,240,104,246,33,129,111,238,68,253,253,233,186,40,163,27,229,83,158,216,234,112,92,191,252,51,40,5,250,190,144,76,178,240,104,246,33,129,111,238,68,253,253,233,186,40,163,202,26,5,178,170,64,23,82,191,252,51,40,5,250,190,144,76,178,240,104,246,33,129,111,238,68,253,253,233,186,40,163,181,76,250,165,170,64,23,82,191,252,51,40,5,250,190,144,76,178,240,104,246,33,129,111,238,68,253,253,233,186,40,163,248,103,94,4,94,81,198,48,105,236,187,128,230,97,25,120,232,125,159,24,124,247,176,24,238,68,253,253,233,186,40,163,24,172,180,91,94,81,198,48,105,236,187,128,230,97,25,120,232,125,159,24,124,247,176,24,238,68,253,253,233,186,40,163,192,212,194,197,113,91,163,95,105,236,187,128,230,97,25,120,232,125,159,24,124,247,176,24,238,68,253,253,233,186,40,163,159,132,24,153,113,91,163,95,105,236,187,128,230,97,25,120,232,125,159,24,124,247,176,24,238,68,253,253,233,186,40,163,220,138,69,32,74,123,206,252,86,172,110,251,230,97,25,120,232,125,159,24,124,247,176,24,238,68,253,253,233,186,40,163,91,144,28,217,74,123,206,252,86,172,110,251,230,97,25,120,232,125,159,24,124,247,176,24,238,68,253,253,233,186,40,163,222,242,192,136,14,210,218,85,86,172,110,251,230,97,25,120,232,125,159,24,124,247,176,24,238,68,253,253,233,186,40,163,88,62,37,53,14,210,218,85,86,172,110,251,230,97,25,120,232,125,159,24,124,247,176,24,238,68,253,253,233,186,40,163,175,37,53,189,128,163,232,24,76,245,254,84,247,196,73,244,232,125,159,24,124,247,176,24,238,68,253,253,233,186,40,163,108,15,206,62,128,163,232,24,76,245,254,84,247,196,73,244,232,125,159,24,124,247,176,24,238,68,253,253,233,186,40,163,251,79,38,134,1,113,206,77,76,245,254,84,247,196,73,244,232,125,159,24,124,247,176,24,238,68,253,253,233,186,40,163,122,8,1,151,1,113,206,77,76,245,254,84,247,196,73,244,232,125,159,24,124,247,176,24,238,68,253,253,233,186,40,163,209,69,107,224,183,59,60,223,198,229,132,188,247,196,73,244,232,125,159,24,124,247,176,24,238,68,253,253,233,186,40,163,218,146,209,8,183,59,60,223,198,229,132,188,247,196,73,244,232,125,159,24,124,247,176,24,238,68,253,253,233,186,40,163,64,22,211,222,205,108,8,132,198,229,132,188,247,196,73,244,232,125,159,24,124,247,176,24,238,68,253,253,233,186,40,163,101,240,227,215,205,108,8,132,198,229,132,188,247,196,73,244,232,125,159,24,124,247,176,24,238,68,253,253,233,186,40,163,166,14,152,208,48,91,11,92,204,141,206,246,20,174,189,54,137,23,246,178,124,247,176,24,238,68,253,253,233,186,40,163,200,168,86,206,48,91,11,92,204,141,206,246,20,174,189,54,137,23,246,178,124,247,176,24,238,68,253,253,233,186,40,163,21,75,241,208,63,55,43,68,204,141,206,246,20,174,189,54,137,23,246,178,124,247,176,24,238,68,253,253,233,186,40,163,136,237,24,102,63,55,43,68,204,141,206,246,20,174,189,54,137,23,246,178,124,247,176,24,238,68,253,253,233,186,40,163,82,123,56,244,254,79,71,92,116,172,104,147,20,174,189,54,137,23,246,178,124,247,176,24,238,68,253,253,233,186,40,163,229,172,0,114,254,79,71,92,116,172,104,147,20,174,189,54,137,23,246,178,124,247,176,24,238,68,253,253,233,186,40,163,152,92,225,42,240,171,219,118,116,172,104,147,20,174,189,54,137,23,246,178,124,247,176,24,238,68,253,253,233,186,40,163,139,243,123,44,240,171,219,118,116,172,104,147,20,174,189,54,137,23,246,178,124,247,176,24,238,68,253,253,233,186,40,163,248,103,199,37,16,8,159,27,191,1,63,178,89,185,204,8,137,23,246,178,124,247,176,24,238,68,253,253,233,186,40,163,18,138,48,214,16,8,159,27,191,1,63,178,89,185,204,8,137,23,246,178,124,247,176,24,238,68,253,253,233,186,40,163,235,63,48,78,217,233,2,26,191,1,63,178,89,185,204,8,137,23,246,178,124,247,176,24,238,68,253,253,233,186,40,163,85,246,102,71,217,233,2,26,191,1,63,178,89,185,204,8,137,23,246,178,124,247,176,24,238,68,253,253,233,186,40,163,183,97,23,93,130,244,157,66,116,89,239,252,89,185,204,8,137,23,246,178,124,247,176,24,238,68,253,253,233,186,40,163,118,250,138,255,130,244,157,66,116,89,239,252,89,185,204,8,137,23,246,178,124,247,176,24,238,68,253,253,233,186,40,163,123,112,151,77,120,114,184,129,116,89,239,252,89,185,204,8,137,23,246,178,124,247,176,24,238,68,253,253,233,186,40,163,190,117,84,232,120,114,184,129,116,89,239,252,89,185,204,8,137,23,246,178,124,247,176,24,238,68,253,253,233,186,40,163,190,42,18,93,115,230,103,83,250,214,185,120,6,177,124,63,141,126,172,97,186,114,88,100,190,95,10,234,233,186,40,163,179,219,237,53,115,230,103,83,250,214,185,120,6,177,124,63,141,126,172,97,186,114,88,100,190,95,10,234,233,186,40,163,124,122,74,20,97,4,88,76,250,214,185,120,6,177,124,63,141,126,172,97,186,114,88,100,190,95,10,234,233,186,40,163,107,191,176,137,97,4,88,76,250,214,185,120,6,177,124,63,141,126,172,97,186,114,88,100,190,95,10,234,233,186,40,163,54,175,181,107,59,91,192,30,154,31,201,64,6,177,124,63,141,126,172,97,186,114,88,100,190,95,10,234,233,186,40,163,142,110,105,47,59,91,192,30,154,31,201,64,6,177,124,63,141,126,172,97,186,114,88,100,190,95,10,234,233,186,40,163,17,253,130,64,228,124,158,215,154,31,201,64,6,177,124,63,141,126,172,97,186,114,88,100,190,95,10,234,233,186,40,163,222,49,152,50,228,124,158,215,154,31,201,64,6,177,124,63,141,126,172,97,186,114,88,100,190,95,10,234,233,186,40,163,55,250,154,108,155,128,129,53,134,36,253,166,16,136,239,89,141,126,172,97,186,114,88,100,190,95,10,234,233,186,40,163,26,56,103,115,155,128,129,53,134,36,253,166,16,136,239,89,141,126,172,97,186,114,88,100,190,95,10,234,233,186,40,163,107,219,25,215,247,173,148,218,134,36,253,166,16,136,239,89,141,126,172,97,186,114,88,100,190,95,10,234,233,186,40,163,68,73,225,98,247,173,148,218,134,36,253,166,16,136,239,89,141,126,172,97,186,114,88,100,190,95,10,234,233,186,40,163,170,145,250,144,247,90,52,20,72,129,76,253,16,136,239,89,141,126,172,97,186,114,88,100,190,95,10,234,233,186,40,163,125,6,206,178,247,90,52,20,72,129,76,253,16,136,239,89,141,126,172,97,186,114,88,100,190,95,10,234,233,186,40,163,158,19,247,69,247,95,164,102,72,129,76,253,16,136,239,89,141,126,172,97,186,114,88,100,190,95,10,234,233,186,40,163,206,100,175,127,247,95,164,102,72,129,76,253,16,136,239,89,141,126,172,97,186,114,88,100,190,95,10,234,233,186,40,163,188,45,2,3,212,87,54,4,182,96,47,198,19,88,25,99,179,154,15,154,186,114,88,100,190,95,10,234,233,186,40,163,153,200,212,164,212,87,54,4,182,96,47,198,19,88,25,99,179,154,15,154,186,114,88,100,190,95,10,234,233,186,40,163,154,24,194,145,102,246,218,123,182,96,47,198,19,88,25,99,179,154,15,154,186,114,88,100,190,95,10,234,233,186,40,163,199,126,46,239,102,246,218,123,182,96,47,198,19,88,25,99,179,154,15,154,186,114,88,100,190,95,10,234,233,186,40,163,240,97,62,59,132,77,109,128,30,168,105,2,19,88,25,99,179,154,15,154,186,114,88,100,190,95,10,234,233,186,40,163,219,53,73,113,132,77,109,128,30,168,105,2,19,88,25,99,179,154,15,154,186,114,88,100,190,95,10,234,233,186,40,163,46,208,65,46,219,116,26,133,30,168,105,2,19,88,25,99,179,154,15,154,186,114,88,100,190,95,10,234,233,186,40,163,45,184,154,234,219,116,26,133,30,168,105,2,19,88,25,99,179,154,15,154,186,114,88,100,190,95,10,234,233,186,40,163,80,96,1,217,107,212,205,186,49,177,100,105,12,28,250,33,179,154,15,154,186,114,88,100,190,95,10,234,233,186,40,163,153,97,185,189,107,212,205,186,49,177,100,105,12,28,250,33,179,154,15,154,186,114,88,100,190,95,10,234,233,186,40,163,129,235,150,104,41,140,2,32,49,177,100,105,12,28,250,33,179,154,15,154,186,114,88,100,190,95,10,234,233,186,40,163,222,177,59,129,41,140,2,32,49,177,100,105,12,28,250,33,179,154,15,154,186,114,88,100,190,95,10,234,233,186,40,163,226,233,82,45,227,209,206,178,239,122,214,94,12,28,250,33,179,154,15,154,186,114,88,100,190,95,10,234,233,186,40,163,135,177,190,75,227,209,206,178,239,122,214,94,12,28,250,33,179,154,15,154,186,114,88,100,190,95,10,234,233,186,40,163,179,166,244,74,27,220,156,60,239,122,214,94,12,28,250,33,179,154,15,154,186,114,88,100,190,95,10,234,233,186,40,163,254,83,235,151,27,220,156,60,239,122,214,94,12,28,250,33,179,154,15,154,186,114,88,100,190,95,10,234,233,186,40,163,159,56,71,63,104,144,222,36,82,146,142,92,101,134,175,232,4,224,138,85,173,101,8,17,190,95,10,234,233,186,40,163,106,234,100,179,104,144,222,36,82,146,142,92,101,134,175,232,4,224,138,85,173,101,8,17,190,95,10,234,233,186,40,163,72,210,203,202,139,83,253,41,82,146,142,92,101,134,175,232,4,224,138,85,173,101,8,17,190,95,10,234,233,186,40,163,37,219,60,174,139,83,253,41,82,146,142,92,101,134,175,232,4,224,138,85,173,101,8,17,190,95,10,234,233,186,40,163,193,83,137,16,92,24,45,75,67,119,114,64,101,134,175,232,4,224,138,85,173,101,8,17,190,95,10,234,233,186,40,163,117,29,49,129,92,24,45,75,67,119,114,64,101,134,175,232,4,224,138,85,173,101,8,17,190,95,10,234,233,186,40,163,200,222,108,14,198,219,28,222,67,119,114,64,101,134,175,232,4,224,138,85,173,101,8,17,190,95,10,234,233,186,40,163,54,223,126,253,198,219,28,222,67,119,114,64,101,134,175,232,4,224,138,85,173,101,8,17,190,95,10,234,233,186,40,163,237,246,91,48,198,140,193,108,237,129,4,58,185,251,175,87,4,224,138,85,173,101,8,17,190,95,10,234,233,186,40,163,42,75,153,9,198,140,193,108,237,129,4,58,185,251,175,87,4,224,138,85,173,101,8,17,190,95,10,234,233,186,40,163,204,182,70,123,33,124,19,168,237,129,4,58,185,251,175,87,4,224,138,85,173,101,8,17,190,95,10,234,233,186,40,163,20,82,162,31,33,124,19,168,237,129,4,58,185,251,175,87,4,224,138,85,173,101,8,17,190,95,10,234,233,186,40,163,92,104,120,163,233,43,141,148,84,198,210,114,185,251,175,87,4,224,138,85,173,101,8,17,190,95,10,234,233,186,40,163,170,142,148,194,233,43,141,148,84,198,210,114,185,251,175,87,4,224,138,85,173,101,8,17,190,95,10,234,233,186,40,163,133,175,134,57,188,213,87,64,84,198,210,114,185,251,175,87,4,224,138,85,173,101,8,17,190,95,10,234,233,186,40,163,114,162,123,143,188,213,87,64,84,198,210,114,185,251,175,87,4,224,138,85,173,101,8,17,190,95,10,234,233,186,40,163,204,211,133,58,172,141,153,40,4,89,108,160,216,249,136,47,136,111,137,202,173,101,8,17,190,95,10,234,233,186,40,163,34,19,231,207,172,141,153,40,4,89,108,160,216,249,136,47,136,111,137,202,173,101,8,17,190,95,10,234,233,186,40,163,136,250,88,177,28,20,32,234,4,89,108,160,216,249,136,47,136,111,137,202,173,101,8,17,190,95,10,234,233,186,40,163,121,174,231,52,28,20,32,234,4,89,108,160,216,249,136,47,136,111,137,202,173,101,8,17,190,95,10,234,233,186,40,163,91,241,161,135,1,39,136,34,213,47,116,54,216,249,136,47,136,111,137,202,173,101,8,17,190,95,10,234,233,186,40,163,238,169,212,146,1,39,136,34,213,47,116,54,216,249,136,47,136,111,137,202,173,101,8,17,190,95,10,234,233,186,40,163,228,237,231,18,80,230,21,186,213,47,116,54,216,249,136,47,136,111,137,202,173,101,8,17,190,95,10,234,233,186,40,163,170,251,202,84,80,230,21,186,213,47,116,54,216,249,136,47,136,111,137,202,173,101,8,17,190,95,10,234,233,186,40,163,218,135,59,199,10,168,54,220,255,52,36,199,124,230,161,37,136,111,137,202,173,101,8,17,190,95,10,234,233,186,40,163,165,178,89,96,10,168,54,220,255,52,36,199,124,230,161,37,136,111,137,202,173,101,8,17,190,95,10,234,233,186,40,163,234,229,141,108,8,71,93,60,255,52,36,199,124,230,161,37,136,111,137,202,173,101,8,17,190,95,10,234,233,186,40,163,248,202,177,222,8,71,93,60,255,52,36,199,124,230,161,37,136,111,137,202,173,101,8,17,190,95,10,234,233,186,40,163,36,65,173,224,188,66,117,38,117,207,98,22,124,230,161,37,136,111,137,202,173,101,8,17,190,95,10,234,233,186,40,163,120,41,139,215,188,66,117,38,117,207,98,22,124,230,161,37,136,111,137,202,173,101,8,17,190,95,10,234,233,186,40,163,106,100,234,202,132,221,207,206,117,207,98,22,124,230,161,37,136,111,137,202,173,101,8,17,190,95,10,234,233,186,40,163,128,97,225,251,132,221,207,206,117,207,98,22,124,230,161,37,136,111,137,202,173,101,8,17,190,95,10,234,233,186,40,163,145,135,162,162,128,226,107,222,27,245,169,183,60,125,13,147,237,58,98,217,159,169,75,34,212,98,161,137,210,194,150,8,236,222,20,34,128,226,107,222,27,245,169,183,60,125,13,147,237,58,98,217,159,169,75,34,212,98,161,137,210,194,150,8,87,151,161,224,245,250,197,106,27,245,169,183,60,125,13,147,237,58,98,217,159,169,75,34,212,98,161,137,210,194,150,8,186,253,245,44,245,250,197,106,27,245,169,183,60,125,13,147,237,58,98,217,159,169,75,34,212,98,161,137,210,194,150,8,100,154,175,183,44,49,136,235,120,234,96,37,60,125,13,147,237,58,98,217,159,169,75,34,212,98,161,137,210,194,150,8,84,113,162,206,44,49,136,235,120,234,96,37,60,125,13,147,237,58,98,217,159,169,75,34,212,98,161,137,210,194,150,8,93,121,19,131,91,181,125,107,120,234,96,37,60,125,13,147,237,58,98,217,159,169,75,34,212,98,161,137,210,194,150,8,109,12,155,201,91,181,125,107,120,234,96,37,60,125,13,147,237,58,98,217,159,169,75,34,212,98,161,137,210,194,150,8,19,222,160,237,67,77,22,132,180,72,145,62,24,52,46,163,237,58,98,217,159,169,75,34,212,98,161,137,210,194,150,8,120,4,60,73,67,77,22,132,180,72,145,62,24,52,46,163,237,58,98,217,159,169,75,34,212,98,161,137,210,194,150,8,108,243,228,36,222,239,160,173,180,72,145,62,24,52,46,163,237,58,98,217,159,169,75,34,212,98,161,137,210,194,150,8,167,13,195,180,222,239,160,173,180,72,145,62,24,52,46,163,237,58,98,217,159,169,75,34,212,98,161,137,210,194,150,8,231,211,112,6,114,90,52,194,139,74,217,121,24,52,46,163,237,58,98,217,159,169,75,34,212,98,161,137,210,194,150,8,28,72,216,0,114,90,52,194,139,74,217,121,24,52,46,163,237,58,98,217,159,169,75,34,212,98,161,137,210,194,150,8,7,25,79,120,146,163,201,62,139,74,217,121,24,52,46,163,237,58,98,217,159,169,75,34,212,98,161,137,210,194,150,8,34,247,115,50,146,163,201,62,139,74,217,121,24,52,46,163,237,58,98,217,159,169,75,34,212,98,161,137,210,194,150,8,44,6,104,153,56,87,29,135,154,198,105,207,151,164,231,88,37,0,202,214,159,169,75,34,212,98,161,137,210,194,150,8,224,173,218,66,56,87,29,135,154,198,105,207,151,164,231,88,37,0,202,214,159,169,75,34,212,98,161,137,210,194,150,8,72,63,198,240,65,166,63,192,154,198,105,207,151,164,231,88,37,0,202,214,159,169,75,34,212,98,161,137,210,194,150,8,101,176,42,94,65,166,63,192,154,198,105,207,151,164,231,88,37,0,202,214,159,169,75,34,212,98,161,137,210,194,150,8,51,230,53,223,28,211,208,130,48,96,188,16,151,164,231,88,37,0,202,214,159,169,75,34,212,98,161,137,210,194,150,8,243,34,3,7,28,211,208,130,48,96,188,16,151,164,231,88,37,0,202,214,159,169,75,34,212,98,161,137,210,194,150,8,52,105,9,172,54,211,161,218,48,96,188,16,151,164,231,88,37,0,202,214,159,169,75,34,212,98,161,137,210,194,150,8,243,118,3,198,54,211,161,218,48,96,188,16,151,164,231,88,37,0,202,214,159,169,75,34,212,98,161,137,210,194,150,8,177,224,62,9,59,205,5,114,107,174,125,168,56,155,1,160,37,0,202,214,159,169,75,34,212,98,161,137,210,194,150,8,174,40,34,202,59,205,5,114,107,174,125,168,56,155,1,160,37,0,202,214,159,169,75,34,212,98,161,137,210,194,150,8,37,254,205,200,44,209,46,252,107,174,125,168,56,155,1,160,37,0,202,214,159,169,75,34,212,98,161,137,210,194,150,8,73,147,168,22,44,209,46,252,107,174,125,168,56,155,1,160,37,0,202,214,159,169,75,34,212,98,161,137,210,194,150,8,114,160,12,107,235,25,189,36,233,112,219,6,56,155,1,160,37,0,202,214,159,169,75,34,212,98,161,137,210,194,150,8,135,62,227,125,235,25,189,36,233,112,219,6,56,155,1,160,37,0,202,214,159,169,75,34,212,98,161,137,210,194,150,8,5,89,61,218,90,114,53,227,233,112,219,6,56,155,1,160,37,0,202,214,159,169,75,34,212,98,161,137,210,194,150,8,0,103,126,218,90,114,53,227,233,112,219,6,56,155,1,160,37,0,202,214,159,169,75,34,212,98,161,137,210,194,150,8,200,61,131,83,40,49,219,14,254,162,186,146,250,26,34,183,72,63,60,250,171,254,193,32,212,98,161,137,210,194,150,8,9,212,71,125,40,49,219,14,254,162,186,146,250,26,34,183,72,63,60,250,171,254,193,32,212,98,161,137,210,194,150,8,185,131,237,198,15,92,26,171,254,162,186,146,250,26,34,183,72,63,60,250,171,254,193,32,212,98,161,137,210,194,150,8,235,30,236,207,15,92,26,171,254,162,186,146,250,26,34,183,72,63,60,250,171,254,193,32,212,98,161,137,210,194,150,8,102,57,123,99,144,130,249,100,143,201,181,210,250,26,34,183,72,63,60,250,171,254,193,32,212,98,161,137,210,194,150,8,225,61,208,112,144,130,249,100,143,201,181,210,250,26,34,183,72,63,60,250,171,254,193,32,212,98,161,137,210,194,150,8,25,186,35,229,160,164,55,211,143,201,181,210,250,26,34,183,72,63,60,250,171,254,193,32,212,98,161,137,210,194,150,8,71,28,42,28,160,164,55,211,143,201,181,210,250,26,34,183,72,63,60,250,171,254,193,32,212,98,161,137,210,194,150,8,133,16,82,153,87,90,43,2,165,219,251,178,104,250,180,234,72,63,60,250,171,254,193,32,212,98,161,137,210,194,150,8,158,90,56,92,87,90,43,2,165,219,251,178,104,250,180,234,72,63,60,250,171,254,193,32,212,98,161,137,210,194,150,8,45,20,64,214,230,188,131,193,165,219,251,178,104,250,180,234,72,63,60,250,171,254,193,32,212,98,161,137,210,194,150,8,52,230,163,184,230,188,131,193,165,219,251,178,104,250,180,234,72,63,60,250,171,254,193,32,212,98,161,137,210,194,150,8,249,52,201,130,77,104,95,217,211,245,148,89,104,250,180,234,72,63,60,250,171,254,193,32,212,98,161,137,210,194,150,8,172,131,94,26,77,104,95,217,211,245,148,89,104,250,180,234,72,63,60,250,171,254,193,32,212,98,161,137,210,194,150,8,240,84,64,243,170,132,164,23,211,245,148,89,104,250,180,234,72,63,60,250,171,254,193,32,212,98,161,137,210,194,150,8,242,21,132,147,170,132,164,23,211,245,148,89,104,250,180,234,72,63,60,250,171,254,193,32,212,98,161,137,210,194,150,8,246,205,26,211,15,174,31,185,55,209,133,81,85,74,56,148,53,81,124,127,171,254,193,32,212,98,161,137,210,194,150,8,51,52,187,146,15,174,31,185,55,209,133,81,85,74,56,148,53,81,124,127,171,254,193,32,212,98,161,137,210,194,150,8,209,21,230,137,138,228,144,223,55,209,133,81,85,74,56,148,53,81,124,127,171,254,193,32,212,98,161,137,210,194,150,8,105,175,61,221,138,228,144,223,55,209,133,81,85,74,56,148,53,81,124,127,171,254,193,32,212,98,161,137,210,194,150,8,144,150,137,124,174,229,97,187,146,63,122,179,85,74,56,148,53,81,124,127,171,254,193,32,212,98,161,137,210,194,150,8,88,0,176,105,174,229,97,187,146,63,122,179,85,74,56,148,53,81,124,127,171,254,193,32,212,98,161,137,210,194,150,8,169,190,150,90,126,224,170,11,146,63,122,179,85,74,56,148,53,81,124,127,171,254,193,32,212,98,161,137,210,194,150,8,228,223,2,180,126,224,170,11,146,63,122,179,85,74,56,148,53,81,124,127,171,254,193,32,212,98,161,137,210,194,150,8,40,91,71,138,119,136,244,54,130,225,71,203,216,9,61,7,53,81,124,127,171,254,193,32,212,98,161,137,210,194,150,8,170,98,205,172,119,136,244,54,130,225,71,203,216,9,61,7,53,81,124,127,171,254,193,32,212,98,161,137,210,194,150,8,41,178,241,149,28,208,76,222,130,225,71,203,216,9,61,7,53,81,124,127,171,254,193,32,212,98,161,137,210,194,150,8,242,213,140,102,28,208,76,222,130,225,71,203,216,9,61,7,53,81,124,127,171,254,193,32,212,98,161,137,210,194,150,8,194,159,187,87,57,72,39,59,255,196,49,66,216,9,61,7,53,81,124,127,171,254,193,32,212,98,161,137,210,194,150,8,113,112,82,60,57,72,39,59,255,196,49,66,216,9,61,7,53,81,124,127,171,254,193,32,212,98,161,137,210,194,150,8,177,199,28,202,169,232,218,239,255,196,49,66,216,9,61,7,53,81,124,127,171,254,193,32,212,98,161,137,210,194,150,8,136,172,169,188,169,232,218,239,255,196,49,66,216,9,61,7,53,81,124,127,171,254,193,32,212,98,161,137,210,194,150,8,162,249,44,225,249,165,132,202,234,33,63,146,60,224,153,145,218,21,199,202,42,98,34,4,214,10,216,155,210,194,150,8,9,199,205,7,249,165,132,202,234,33,63,146,60,224,153,145,218,21,199,202,42,98,34,4,214,10,216,155,210,194,150,8,158,22,105,45,34,213,230,145,234,33,63,146,60,224,153,145,218,21,199,202,42,98,34,4,214,10,216,155,210,194,150,8,132,229,226,98,34,213,230,145,234,33,63,146,60,224,153,145,218,21,199,202,42,98,34,4,214,10,216,155,210,194,150,8,200,45,189,25,171,54,42,114,248,169,247,187,60,224,153,145,218,21,199,202,42,98,34,4,214,10,216,155,210,194,150,8,129,185,103,208,171,54,42,114,248,169,247,187,60,224,153,145,218,21,199,202,42,98,34,4,214,10,216,155,210,194,150,8,213,54,2,126,251,67,226,96,248,169,247,187,60,224,153,145,218,21,199,202,42,98,34,4,214,10,216,155,210,194,150,8,202,91,179,42,251,67,226,96,248,169,247,187,60,224,153,145,218,21,199,202,42,98,34,4,214,10,216,155,210,194,150,8,137,186,245,105,144,131,210,251,112,70,110,179,241,127,216,216,218,21,199,202,42,98,34,4,214,10,216,155,210,194,150,8,142,224,63,170,144,131,210,251,112,70,110,179,241,127,216,216,218,21,199,202,42,98,34,4,214,10,216,155,210,194,150,8,88,126,216,140,30,32,172,94,112,70,110,179,241,127,216,216,218,21,199,202,42,98,34,4,214,10,216,155,210,194,150,8,74,26,77,175,30,32,172,94,112,70,110,179,241,127,216,216,218,21,199,202,42,98,34,4,214,10,216,155,210,194,150,8,156,192,38,255,33,149,144,107,38,82,1,94,241,127,216,216,218,21,199,202,42,98,34,4,214,10,216,155,210,194,150,8,18,235,136,40,33,149,144,107,38,82,1,94,241,127,216,216,218,21,199,202,42,98,34,4,214,10,216,155,210,194,150,8,39,109,33,227,5,87,237,247,38,82,1,94,241,127,216,216,218,21,199,202,42,98,34,4,214,10,216,155,210,194,150,8,28,233,255,161,5,87,237,247,38,82,1,94,241,127,216,216,218,21,199,202,42,98,34,4,214,10,216,155,210,194,150,8,149,99,100,231,97,200,185,0,114,180,59,25,239,193,60,24,220,94,32,174,42,98,34,4,214,10,216,155,210,194,150,8,82,143,178,188,97,200,185,0,114,180,59,25,239,193,60,24,220,94,32,174,42,98,34,4,214,10,216,155,210,194,150,8,183,230,240,243,50,21,131,25,114,180,59,25,239,193,60,24,220,94,32,174,42,98,34,4,214,10,216,155,210,194,150,8,41,39,133,234,50,21,131,25,114,180,59,25,239,193,60,24,220,94,32,174,42,98,34,4,214,10,216,155,210,194,150,8,198,172,6,104,123,204,219,26,132,238,138,31,239,193,60,24,220,94,32,174,42,98,34,4,214,10,216,155,210,194,150,8,133,198,56,49,123,204,219,26,132,238,138,31,239,193,60,24,220,94,32,174,42,98,34,4,214,10,216,155,210,194,150,8,82,16,201,114,49,236,17,124,132,238,138,31,239,193,60,24,220,94,32,174,42,98,34,4,214,10,216,155,210,194,150,8,36,29,157,93,49,236,17,124,132,238,138,31,239,193,60,24,220,94,32,174,42,98,34,4,214,10,216,155,210,194,150,8,227,62,90,123,221,253,182,84,222,147,38,189,212,95,114,28,220,94,32,174,42,98,34,4,214,10,216,155,210,194,150,8,111,225,187,176,221,253,182,84,222,147,38,189,212,95,114,28,220,94,32,174,42,98,34,4,214,10,216,155,210,194,150,8,176,102,96,95,99,160,74,245,222,147,38,189,212,95,114,28,220,94,32,174,42,98,34,4,214,10,216,155,210,194,150,8,100,22,147,98,99,160,74,245,222,147,38,189,212,95,114,28,220,94,32,174,42,98,34,4,214,10,216,155,210,194,150,8,147,129,183,208,5,201,11,116,231,37,94,179,212,95,114,28,220,94,32,174,42,98,34,4,214,10,216,155,210,194,150,8,228,116,217,35,5,201,11,116,231,37,94,179,212,95,114,28,220,94,32,174,42,98,34,4,214,10,216,155,210,194,150,8,89,243,185,43,160,179,19,124,231,37,94,179,212,95,114,28,220,94,32,174,42,98,34,4,214,10,216,155,210,194,150,8,131,237,195,100,160,179,19,124,231,37,94,179,212,95,114,28,220,94,32,174,42,98,34,4,214,10,216,155,210,194,150,8,179,151,84,159,236,194,218,247,95,184,14,182,203,139,195,106,178,241,150,166,105,50,201,221,214,10,216,155,210,194,150,8,17,209,44,159,236,194,218,247,95,184,14,182,203,139,195,106,178,241,150,166,105,50,201,221,214,10,216,155,210,194,150,8,118,89,121,54,244,73,190,177,95,184,14,182,203,139,195,106,178,241,150,166,105,50,201,221,214,10,216,155,210,194,150,8,167,187,80,191,244,73,190,177,95,184,14,182,203,139,195,106,178,241,150,166,105,50,201,221,214,10,216,155,210,194,150,8,109,95,140,81,42,92,129,234,198,169,156,133,203,139,195,106,178,241,150,166,105,50,201,221,214,10,216,155,210,194,150,8,37,82,106,170,42,92,129,234,198,169,156,133,203,139,195,106,178,241,150,166,105,50,201,221,214,10,216,155,210,194,150,8,252,226,92,204,108,153,157,227,198,169,156,133,203,139,195,106,178,241,150,166,105,50,201,221,214,10,216,155,210,194,150,8,224,108,165,161,108,153,157,227,198,169,156,133,203,139,195,106,178,241,150,166,105,50,201,221,214,10,216,155,210,194,150,8,4,27,80,174,163,50,171,76,217,92,112,26,68,158,170,93,178,241,150,166,105,50,201,221,214,10,216,155,210,194,150,8,207,129,169,31,163,50,171,76,217,92,112,26,68,158,170,93,178,241,150,166,105,50,201,221,214,10,216,155,210,194,150,8,233,224,38,93,241,7,162,27,217,92,112,26,68,158,170,93,178,241,150,166,105,50,201,221,214,10,216,155,210,194,150,8,208,127,241,68,241,7,162,27,217,92,112,26,68,158,170,93,178,241,150,166,105,50,201,221,214,10,216,155,210,194,150,8,164,245,145,211,206,131,153,180,248,164,93,124,68,158,170,93,178,241,150,166,105,50,201,221,214,10,216,155,210,194,150,8,233,162,74,54,206,131,153,180,248,164,93,124,68,158,170,93,178,241,150,166,105,50,201,221,214,10,216,155,210,194,150,8,211,93,159,62,43,141,136,237,248,164,93,124,68,158,170,93,178,241,150,166,105,50,201,221,214,10,216,155,210,194,150,8,22,15,219,10,43,141,136,237,248,164,93,124,68,158,170,93,178,241,150,166,105,50,201,221,214,10,216,155,210,194,150,8,225,239,69,93,58,174,252,226,253,62,176,21,120,148,23,214,214,159,130,226,105,50,201,221,214,10,216,155,210,194,150,8,71,14,45,19,58,174,252,226,253,62,176,21,120,148,23,214,214,159,130,226,105,50,201,221,214,10,216,155,210,194,150,8,75,158,79,2,212,250,126,95,253,62,176,21,120,148,23,214,214,159,130,226,105,50,201,221,214,10,216,155,210,194,150,8,176,167,66,95,212,250,126,95,253,62,176,21,120,148,23,214,214,159,130,226,105,50,201,221,214,10,216,155,210,194,150,8,137,108,87,155,235,72,9,223,219,78,116,24,120,148,23,214,214,159,130,226,105,50,201,221,214,10,216,155,210,194,150,8,221,52,115,89,235,72,9,223,219,78,116,24,120,148,23,214,214,159,130,226,105,50,201,221,214,10,216,155,210,194,150,8,71,201,170,17,200,40,165,177,219,78,116,24,120,148,23,214,214,159,130,226,105,50,201,221,214,10,216,155,210,194,150,8,209,12,100,54,200,40,165,177,219,78,116,24,120,148,23,214,214,159,130,226,105,50,201,221,214,10,216,155,210,194,150,8,31,139,240,132,128,92,178,133,95,12,184,178,84,135,163,76,214,159,130,226,105,50,201,221,214,10,216,155,210,194,150,8,132,15,135,37,128,92,178,133,95,12,184,178,84,135,163,76,214,159,130,226,105,50,201,221,214,10,216,155,210,194,150,8,187,224,195,234,23,150,210,86,95,12,184,178,84,135,163,76,214,159,130,226,105,50,201,221,214,10,216,155,210,194,150,8,99,151,51,84,23,150,210,86,95,12,184,178,84,135,163,76,214,159,130,226,105,50,201,221,214,10,216,155,210,194,150,8,171,22,20,242,205,129,26,178,175,20,10,249,84,135,163,76,214,159,130,226,105,50,201,221,214,10,216,155,210,194,150,8,66,91,87,129,205,129,26,178,175,20,10,249,84,135,163,76,214,159,130,226,105,50,201,221,214,10,216,155,210,194,150,8,58,200,104,48,175,20,10,249,84,135,163,76,214,159,130,226,105,50,201,221,214,10,216,155,210,194,150,8}; + +static const uint32_t T6_COUNT = 256u; +static const uint8_t T6_LEAVES[1024] = {140,30,28,139,112,181,234,109,189,248,70,203,91,147,16,79,160,214,180,247,227,25,226,179,212,216,74,183,29,218,24,245,174,86,29,161,246,98,88,70,57,109,6,63,2,118,183,123,179,170,29,164,240,42,222,210,44,221,150,34,140,58,40,230,34,40,243,221,70,200,235,154,222,245,150,142,26,73,239,10,149,160,70,187,237,164,108,108,255,97,11,186,38,207,198,61,150,120,191,174,140,118,171,135,98,134,144,226,134,125,154,211,144,180,156,190,239,161,238,1,12,58,108,180,205,156,159,243,234,143,100,122,48,2,67,107,16,94,165,53,11,230,64,17,156,108,188,173,215,106,87,57,40,134,87,49,84,165,105,235,101,114,152,214,254,223,71,12,185,117,113,106,204,140,153,139,17,99,242,43,219,162,40,74,235,167,12,139,193,244,177,243,168,241,35,162,3,244,187,19,8,31,0,50,125,10,54,219,128,111,71,120,98,85,123,253,109,114,78,90,28,74,181,193,78,119,202,25,52,9,11,168,104,102,167,195,163,38,76,46,244,20,10,239,73,42,118,156,35,123,44,47,169,77,215,85,5,158,206,234,117,78,236,245,199,90,190,233,27,94,66,124,125,216,144,206,176,49,232,25,240,96,71,166,1,188,162,37,52,170,95,214,202,140,244,166,202,197,31,175,200,200,184,216,89,16,76,83,0,214,101,183,191,58,24,205,152,185,199,196,158,173,204,151,167,47,174,166,166,219,25,142,153,242,69,204,213,20,122,92,72,139,123,35,174,151,61,167,16,59,48,232,207,11,215,131,29,237,20,185,230,248,162,116,73,249,8,193,17,122,162,2,30,200,86,10,211,163,22,176,91,129,245,134,24,251,148,174,99,121,51,181,210,28,91,5,71,206,178,16,152,119,134,224,1,52,173,144,205,28,218,5,29,48,99,59,245,188,174,66,8,246,1,98,79,31,195,126,195,166,105,161,123,146,111,78,186,171,239,170,88,104,171,67,123,130,25,202,36,167,154,238,225,39,215,152,97,177,82,216,38,134,48,83,21,132,181,183,97,164,142,64,211,142,129,160,76,126,37,4,25,250,177,60,206,78,107,67,60,48,109,132,22,42,14,35,31,107,190,9,174,168,77,194,252,126,158,68,230,216,74,121,1,20,130,34,13,230,124,118,63,95,167,113,32,221,48,116,23,12,240,245,169,207,209,44,166,94,127,63,210,175,232,112,46,53,192,226,14,93,152,208,26,123,5,72,249,48,209,230,232,165,48,43,179,126,101,6,6,32,139,240,34,165,130,235,221,88,169,2,237,233,7,132,223,53,21,123,154,157,80,249,223,99,236,125,24,201,152,48,250,227,240,143,9,96,140,175,122,0,252,131,89,231,69,134,174,100,160,121,169,149,133,58,128,154,161,213,253,115,9,78,17,162,36,204,144,39,254,0,202,10,231,163,145,210,170,33,99,34,51,113,149,65,26,52,97,21,41,130,29,16,14,2,224,227,56,163,187,59,211,67,23,244,16,132,64,72,173,170,144,118,245,143,49,245,173,189,32,108,165,42,182,244,204,65,246,192,37,126,173,189,141,102,42,108,224,229,243,79,2,122,50,173,7,109,74,193,250,241,62,119,175,121,191,252,45,227,1,178,83,242,47,51,38,223,124,72,20,198,43,190,11,27,198,20,180,133,14,88,191,54,150,173,116,181,86,153,4,30,100,151,106,29,221,6,67,158,161,230,124,15,88,83,106,181,58,198,103,66,16,18,120,121,81,131,20,21,57,196,42,236,254,91,123,120,206,207,152,201,32,88,58,158,182,125,143,86,40,251,141,75,187,57,85,90,76,77,62,226,116,232,18,4,109,141,210,161,216,65,104,210,138,24,186,238,238,226,2,92,49,35,250,235,8,158,91,229,28,150,74,93,54,112,81,75,223,55,136,55,155,189,6,233,116,184,247,71,111,154,180,28,143,63,60,255,75,249,199,13,97,69,87,36,175,246,44,216,61,168,118,195,204,131,86,83,83,32,90,221,132,118,242,32,60,215,255,231,227,69,26,211,0,238,219,13,201,242,27,183,76,251,117,59,5,13,224,73,11,245,144,125,225,219,27,34,56,188,153,118,49,119,131,237,26,122,26,117,25,247,250,36,237,13,251,160,154,108,50,14,128,55,252,79,241,90,51,108,77,162,87,54,209,217,193,197,84,169,7,120,34,68,123,53,198,185,26,150,54,8,118,114,189,199,0,181,43,2,251,13,79,53,110,102,8,8,189,31,22,138,15,158,160,16,43,19,128,242,89,33,244,11,70,181}; + +static const uint8_t T6_ROOT[4] = {10,218,78,212}; + +static const uint16_t T6_POFF[256] = {0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,480,512,544,576,608,640,672,704,736,768,800,832,864,896,928,960,992,1024,1056,1088,1120,1152,1184,1216,1248,1280,1312,1344,1376,1408,1440,1472,1504,1536,1568,1600,1632,1664,1696,1728,1760,1792,1824,1856,1888,1920,1952,1984,2016,2048,2080,2112,2144,2176,2208,2240,2272,2304,2336,2368,2400,2432,2464,2496,2528,2560,2592,2624,2656,2688,2720,2752,2784,2816,2848,2880,2912,2944,2976,3008,3040,3072,3104,3136,3168,3200,3232,3264,3296,3328,3360,3392,3424,3456,3488,3520,3552,3584,3616,3648,3680,3712,3744,3776,3808,3840,3872,3904,3936,3968,4000,4032,4064,4096,4128,4160,4192,4224,4256,4288,4320,4352,4384,4416,4448,4480,4512,4544,4576,4608,4640,4672,4704,4736,4768,4800,4832,4864,4896,4928,4960,4992,5024,5056,5088,5120,5152,5184,5216,5248,5280,5312,5344,5376,5408,5440,5472,5504,5536,5568,5600,5632,5664,5696,5728,5760,5792,5824,5856,5888,5920,5952,5984,6016,6048,6080,6112,6144,6176,6208,6240,6272,6304,6336,6368,6400,6432,6464,6496,6528,6560,6592,6624,6656,6688,6720,6752,6784,6816,6848,6880,6912,6944,6976,7008,7040,7072,7104,7136,7168,7200,7232,7264,7296,7328,7360,7392,7424,7456,7488,7520,7552,7584,7616,7648,7680,7712,7744,7776,7808,7840,7872,7904,7936,7968,8000,8032,8064,8096,8128,8160}; +static const uint8_t T6_PNSIB[256] = {8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8}; +static const uint8_t T6_PBLOB[8192] = {112,181,234,109,61,1,12,254,33,187,164,242,39,169,252,55,151,142,28,255,20,91,128,192,110,91,64,66,11,50,110,46,140,30,28,139,61,1,12,254,33,187,164,242,39,169,252,55,151,142,28,255,20,91,128,192,110,91,64,66,11,50,110,46,91,147,16,79,201,152,239,203,33,187,164,242,39,169,252,55,151,142,28,255,20,91,128,192,110,91,64,66,11,50,110,46,189,248,70,203,201,152,239,203,33,187,164,242,39,169,252,55,151,142,28,255,20,91,128,192,110,91,64,66,11,50,110,46,227,25,226,179,173,126,189,138,120,56,19,180,39,169,252,55,151,142,28,255,20,91,128,192,110,91,64,66,11,50,110,46,160,214,180,247,173,126,189,138,120,56,19,180,39,169,252,55,151,142,28,255,20,91,128,192,110,91,64,66,11,50,110,46,29,218,24,245,135,255,44,188,120,56,19,180,39,169,252,55,151,142,28,255,20,91,128,192,110,91,64,66,11,50,110,46,212,216,74,183,135,255,44,188,120,56,19,180,39,169,252,55,151,142,28,255,20,91,128,192,110,91,64,66,11,50,110,46,246,98,88,70,236,139,173,115,56,194,247,25,44,119,165,187,151,142,28,255,20,91,128,192,110,91,64,66,11,50,110,46,174,86,29,161,236,139,173,115,56,194,247,25,44,119,165,187,151,142,28,255,20,91,128,192,110,91,64,66,11,50,110,46,2,118,183,123,101,83,76,9,56,194,247,25,44,119,165,187,151,142,28,255,20,91,128,192,110,91,64,66,11,50,110,46,57,109,6,63,101,83,76,9,56,194,247,25,44,119,165,187,151,142,28,255,20,91,128,192,110,91,64,66,11,50,110,46,240,42,222,210,128,122,161,242,120,133,88,237,44,119,165,187,151,142,28,255,20,91,128,192,110,91,64,66,11,50,110,46,179,170,29,164,128,122,161,242,120,133,88,237,44,119,165,187,151,142,28,255,20,91,128,192,110,91,64,66,11,50,110,46,140,58,40,230,12,204,61,48,120,133,88,237,44,119,165,187,151,142,28,255,20,91,128,192,110,91,64,66,11,50,110,46,44,221,150,34,12,204,61,48,120,133,88,237,44,119,165,187,151,142,28,255,20,91,128,192,110,91,64,66,11,50,110,46,70,200,235,154,155,192,177,60,14,228,38,70,8,205,156,178,175,42,80,15,20,91,128,192,110,91,64,66,11,50,110,46,34,40,243,221,155,192,177,60,14,228,38,70,8,205,156,178,175,42,80,15,20,91,128,192,110,91,64,66,11,50,110,46,26,73,239,10,164,82,17,53,14,228,38,70,8,205,156,178,175,42,80,15,20,91,128,192,110,91,64,66,11,50,110,46,222,245,150,142,164,82,17,53,14,228,38,70,8,205,156,178,175,42,80,15,20,91,128,192,110,91,64,66,11,50,110,46,237,164,108,108,158,68,74,89,73,205,248,16,8,205,156,178,175,42,80,15,20,91,128,192,110,91,64,66,11,50,110,46,149,160,70,187,158,68,74,89,73,205,248,16,8,205,156,178,175,42,80,15,20,91,128,192,110,91,64,66,11,50,110,46,38,207,198,61,89,73,17,146,73,205,248,16,8,205,156,178,175,42,80,15,20,91,128,192,110,91,64,66,11,50,110,46,255,97,11,186,89,73,17,146,73,205,248,16,8,205,156,178,175,42,80,15,20,91,128,192,110,91,64,66,11,50,110,46,140,118,171,135,145,169,118,133,95,35,5,66,212,234,181,1,175,42,80,15,20,91,128,192,110,91,64,66,11,50,110,46,150,120,191,174,145,169,118,133,95,35,5,66,212,234,181,1,175,42,80,15,20,91,128,192,110,91,64,66,11,50,110,46,134,125,154,211,163,128,131,192,95,35,5,66,212,234,181,1,175,42,80,15,20,91,128,192,110,91,64,66,11,50,110,46,98,134,144,226,163,128,131,192,95,35,5,66,212,234,181,1,175,42,80,15,20,91,128,192,110,91,64,66,11,50,110,46,239,161,238,1,158,10,68,7,182,80,56,25,212,234,181,1,175,42,80,15,20,91,128,192,110,91,64,66,11,50,110,46,144,180,156,190,158,10,68,7,182,80,56,25,212,234,181,1,175,42,80,15,20,91,128,192,110,91,64,66,11,50,110,46,205,156,159,243,136,66,47,18,182,80,56,25,212,234,181,1,175,42,80,15,20,91,128,192,110,91,64,66,11,50,110,46,12,58,108,180,136,66,47,18,182,80,56,25,212,234,181,1,175,42,80,15,20,91,128,192,110,91,64,66,11,50,110,46,48,2,67,107,248,10,239,42,204,110,154,236,208,111,42,78,192,126,161,111,46,33,156,185,110,91,64,66,11,50,110,46,234,143,100,122,248,10,239,42,204,110,154,236,208,111,42,78,192,126,161,111,46,33,156,185,110,91,64,66,11,50,110,46,11,230,64,17,246,211,40,32,204,110,154,236,208,111,42,78,192,126,161,111,46,33,156,185,110,91,64,66,11,50,110,46,16,94,165,53,246,211,40,32,204,110,154,236,208,111,42,78,192,126,161,111,46,33,156,185,110,91,64,66,11,50,110,46,215,106,87,57,152,82,220,235,137,94,251,72,208,111,42,78,192,126,161,111,46,33,156,185,110,91,64,66,11,50,110,46,156,108,188,173,152,82,220,235,137,94,251,72,208,111,42,78,192,126,161,111,46,33,156,185,110,91,64,66,11,50,110,46,84,165,105,235,140,26,155,3,137,94,251,72,208,111,42,78,192,126,161,111,46,33,156,185,110,91,64,66,11,50,110,46,40,134,87,49,140,26,155,3,137,94,251,72,208,111,42,78,192,126,161,111,46,33,156,185,110,91,64,66,11,50,110,46,254,223,71,12,132,90,115,110,234,251,207,116,249,181,216,28,192,126,161,111,46,33,156,185,110,91,64,66,11,50,110,46,101,114,152,214,132,90,115,110,234,251,207,116,249,181,216,28,192,126,161,111,46,33,156,185,110,91,64,66,11,50,110,46,204,140,153,139,137,178,153,49,234,251,207,116,249,181,216,28,192,126,161,111,46,33,156,185,110,91,64,66,11,50,110,46,185,117,113,106,137,178,153,49,234,251,207,116,249,181,216,28,192,126,161,111,46,33,156,185,110,91,64,66,11,50,110,46,219,162,40,74,69,228,101,168,223,175,27,248,249,181,216,28,192,126,161,111,46,33,156,185,110,91,64,66,11,50,110,46,17,99,242,43,69,228,101,168,223,175,27,248,249,181,216,28,192,126,161,111,46,33,156,185,110,91,64,66,11,50,110,46,193,244,177,243,125,249,212,200,223,175,27,248,249,181,216,28,192,126,161,111,46,33,156,185,110,91,64,66,11,50,110,46,235,167,12,139,125,249,212,200,223,175,27,248,249,181,216,28,192,126,161,111,46,33,156,185,110,91,64,66,11,50,110,46,3,244,187,19,94,151,244,19,107,129,211,179,113,136,247,118,185,81,249,212,46,33,156,185,110,91,64,66,11,50,110,46,168,241,35,162,94,151,244,19,107,129,211,179,113,136,247,118,185,81,249,212,46,33,156,185,110,91,64,66,11,50,110,46,125,10,54,219,202,36,212,89,107,129,211,179,113,136,247,118,185,81,249,212,46,33,156,185,110,91,64,66,11,50,110,46,8,31,0,50,202,36,212,89,107,129,211,179,113,136,247,118,185,81,249,212,46,33,156,185,110,91,64,66,11,50,110,46,98,85,123,253,26,197,238,204,90,36,254,87,113,136,247,118,185,81,249,212,46,33,156,185,110,91,64,66,11,50,110,46,128,111,71,120,26,197,238,204,90,36,254,87,113,136,247,118,185,81,249,212,46,33,156,185,110,91,64,66,11,50,110,46,28,74,181,193,127,14,113,74,90,36,254,87,113,136,247,118,185,81,249,212,46,33,156,185,110,91,64,66,11,50,110,46,109,114,78,90,127,14,113,74,90,36,254,87,113,136,247,118,185,81,249,212,46,33,156,185,110,91,64,66,11,50,110,46,52,9,11,168,169,44,254,214,66,244,207,42,59,29,246,54,185,81,249,212,46,33,156,185,110,91,64,66,11,50,110,46,78,119,202,25,169,44,254,214,66,244,207,42,59,29,246,54,185,81,249,212,46,33,156,185,110,91,64,66,11,50,110,46,163,38,76,46,97,244,180,89,66,244,207,42,59,29,246,54,185,81,249,212,46,33,156,185,110,91,64,66,11,50,110,46,104,102,167,195,97,244,180,89,66,244,207,42,59,29,246,54,185,81,249,212,46,33,156,185,110,91,64,66,11,50,110,46,73,42,118,156,79,101,8,131,93,71,48,66,59,29,246,54,185,81,249,212,46,33,156,185,110,91,64,66,11,50,110,46,244,20,10,239,79,101,8,131,93,71,48,66,59,29,246,54,185,81,249,212,46,33,156,185,110,91,64,66,11,50,110,46,169,77,215,85,169,129,185,199,93,71,48,66,59,29,246,54,185,81,249,212,46,33,156,185,110,91,64,66,11,50,110,46,35,123,44,47,169,129,185,199,93,71,48,66,59,29,246,54,185,81,249,212,46,33,156,185,110,91,64,66,11,50,110,46,117,78,236,245,235,154,192,111,240,18,230,218,49,34,73,109,181,47,173,43,69,90,182,188,13,119,131,192,11,50,110,46,5,158,206,234,235,154,192,111,240,18,230,218,49,34,73,109,181,47,173,43,69,90,182,188,13,119,131,192,11,50,110,46,27,94,66,124,233,56,213,248,240,18,230,218,49,34,73,109,181,47,173,43,69,90,182,188,13,119,131,192,11,50,110,46,199,90,190,233,233,56,213,248,240,18,230,218,49,34,73,109,181,47,173,43,69,90,182,188,13,119,131,192,11,50,110,46,176,49,232,25,220,128,15,34,230,236,117,104,49,34,73,109,181,47,173,43,69,90,182,188,13,119,131,192,11,50,110,46,125,216,144,206,220,128,15,34,230,236,117,104,49,34,73,109,181,47,173,43,69,90,182,188,13,119,131,192,11,50,110,46,1,188,162,37,203,208,234,75,230,236,117,104,49,34,73,109,181,47,173,43,69,90,182,188,13,119,131,192,11,50,110,46,240,96,71,166,203,208,234,75,230,236,117,104,49,34,73,109,181,47,173,43,69,90,182,188,13,119,131,192,11,50,110,46,202,140,244,166,94,202,124,71,119,27,207,13,109,180,97,35,181,47,173,43,69,90,182,188,13,119,131,192,11,50,110,46,52,170,95,214,94,202,124,71,119,27,207,13,109,180,97,35,181,47,173,43,69,90,182,188,13,119,131,192,11,50,110,46,200,200,184,216,10,151,94,242,119,27,207,13,109,180,97,35,181,47,173,43,69,90,182,188,13,119,131,192,11,50,110,46,202,197,31,175,10,151,94,242,119,27,207,13,109,180,97,35,181,47,173,43,69,90,182,188,13,119,131,192,11,50,110,46,0,214,101,183,3,71,78,156,125,228,213,164,109,180,97,35,181,47,173,43,69,90,182,188,13,119,131,192,11,50,110,46,89,16,76,83,3,71,78,156,125,228,213,164,109,180,97,35,181,47,173,43,69,90,182,188,13,119,131,192,11,50,110,46,152,185,199,196,153,28,104,1,125,228,213,164,109,180,97,35,181,47,173,43,69,90,182,188,13,119,131,192,11,50,110,46,191,58,24,205,153,28,104,1,125,228,213,164,109,180,97,35,181,47,173,43,69,90,182,188,13,119,131,192,11,50,110,46,167,47,174,166,133,151,46,226,42,176,96,146,69,205,119,196,160,113,89,231,69,90,182,188,13,119,131,192,11,50,110,46,158,173,204,151,133,151,46,226,42,176,96,146,69,205,119,196,160,113,89,231,69,90,182,188,13,119,131,192,11,50,110,46,153,242,69,204,33,228,244,127,42,176,96,146,69,205,119,196,160,113,89,231,69,90,182,188,13,119,131,192,11,50,110,46,166,219,25,142,33,228,244,127,42,176,96,146,69,205,119,196,160,113,89,231,69,90,182,188,13,119,131,192,11,50,110,46,72,139,123,35,150,96,253,48,201,147,77,187,69,205,119,196,160,113,89,231,69,90,182,188,13,119,131,192,11,50,110,46,213,20,122,92,150,96,253,48,201,147,77,187,69,205,119,196,160,113,89,231,69,90,182,188,13,119,131,192,11,50,110,46,16,59,48,232,68,185,0,27,201,147,77,187,69,205,119,196,160,113,89,231,69,90,182,188,13,119,131,192,11,50,110,46,174,151,61,167,68,185,0,27,201,147,77,187,69,205,119,196,160,113,89,231,69,90,182,188,13,119,131,192,11,50,110,46,29,237,20,185,229,214,255,73,23,131,74,221,194,223,146,194,160,113,89,231,69,90,182,188,13,119,131,192,11,50,110,46,207,11,215,131,229,214,255,73,23,131,74,221,194,223,146,194,160,113,89,231,69,90,182,188,13,119,131,192,11,50,110,46,73,249,8,193,142,154,168,78,23,131,74,221,194,223,146,194,160,113,89,231,69,90,182,188,13,119,131,192,11,50,110,46,230,248,162,116,142,154,168,78,23,131,74,221,194,223,146,194,160,113,89,231,69,90,182,188,13,119,131,192,11,50,110,46,30,200,86,10,109,134,40,163,227,231,120,235,194,223,146,194,160,113,89,231,69,90,182,188,13,119,131,192,11,50,110,46,17,122,162,2,109,134,40,163,227,231,120,235,194,223,146,194,160,113,89,231,69,90,182,188,13,119,131,192,11,50,110,46,91,129,245,134,86,43,234,76,227,231,120,235,194,223,146,194,160,113,89,231,69,90,182,188,13,119,131,192,11,50,110,46,211,163,22,176,86,43,234,76,227,231,120,235,194,223,146,194,160,113,89,231,69,90,182,188,13,119,131,192,11,50,110,46,99,121,51,181,87,196,85,145,88,254,58,219,68,225,163,175,55,106,44,53,36,70,252,155,13,119,131,192,11,50,110,46,24,251,148,174,87,196,85,145,88,254,58,219,68,225,163,175,55,106,44,53,36,70,252,155,13,119,131,192,11,50,110,46,71,206,178,16,95,6,2,213,88,254,58,219,68,225,163,175,55,106,44,53,36,70,252,155,13,119,131,192,11,50,110,46,210,28,91,5,95,6,2,213,88,254,58,219,68,225,163,175,55,106,44,53,36,70,252,155,13,119,131,192,11,50,110,46,1,52,173,144,237,118,80,254,72,165,67,184,68,225,163,175,55,106,44,53,36,70,252,155,13,119,131,192,11,50,110,46,152,119,134,224,237,118,80,254,72,165,67,184,68,225,163,175,55,106,44,53,36,70,252,155,13,119,131,192,11,50,110,46,29,48,99,59,197,94,52,174,72,165,67,184,68,225,163,175,55,106,44,53,36,70,252,155,13,119,131,192,11,50,110,46,205,28,218,5,197,94,52,174,72,165,67,184,68,225,163,175,55,106,44,53,36,70,252,155,13,119,131,192,11,50,110,46,8,246,1,98,170,186,235,80,84,146,181,196,215,183,224,153,55,106,44,53,36,70,252,155,13,119,131,192,11,50,110,46,245,188,174,66,170,186,235,80,84,146,181,196,215,183,224,153,55,106,44,53,36,70,252,155,13,119,131,192,11,50,110,46,195,166,105,161,202,194,96,248,84,146,181,196,215,183,224,153,55,106,44,53,36,70,252,155,13,119,131,192,11,50,110,46,79,31,195,126,202,194,96,248,84,146,181,196,215,183,224,153,55,106,44,53,36,70,252,155,13,119,131,192,11,50,110,46,186,171,239,170,244,54,50,8,202,28,238,117,215,183,224,153,55,106,44,53,36,70,252,155,13,119,131,192,11,50,110,46,123,146,111,78,244,54,50,8,202,28,238,117,215,183,224,153,55,106,44,53,36,70,252,155,13,119,131,192,11,50,110,46,123,130,25,202,234,2,144,51,202,28,238,117,215,183,224,153,55,106,44,53,36,70,252,155,13,119,131,192,11,50,110,46,88,104,171,67,234,2,144,51,202,28,238,117,215,183,224,153,55,106,44,53,36,70,252,155,13,119,131,192,11,50,110,46,225,39,215,152,207,242,127,202,42,74,199,130,90,255,186,65,253,241,16,85,36,70,252,155,13,119,131,192,11,50,110,46,36,167,154,238,207,242,127,202,42,74,199,130,90,255,186,65,253,241,16,85,36,70,252,155,13,119,131,192,11,50,110,46,38,134,48,83,104,123,243,232,42,74,199,130,90,255,186,65,253,241,16,85,36,70,252,155,13,119,131,192,11,50,110,46,97,177,82,216,104,123,243,232,42,74,199,130,90,255,186,65,253,241,16,85,36,70,252,155,13,119,131,192,11,50,110,46,97,164,142,64,126,233,6,137,167,204,43,160,90,255,186,65,253,241,16,85,36,70,252,155,13,119,131,192,11,50,110,46,21,132,181,183,126,233,6,137,167,204,43,160,90,255,186,65,253,241,16,85,36,70,252,155,13,119,131,192,11,50,110,46,76,126,37,4,200,38,101,97,167,204,43,160,90,255,186,65,253,241,16,85,36,70,252,155,13,119,131,192,11,50,110,46,211,142,129,160,200,38,101,97,167,204,43,160,90,255,186,65,253,241,16,85,36,70,252,155,13,119,131,192,11,50,110,46,206,78,107,67,79,168,90,42,1,107,110,249,76,250,12,7,253,241,16,85,36,70,252,155,13,119,131,192,11,50,110,46,25,250,177,60,79,168,90,42,1,107,110,249,76,250,12,7,253,241,16,85,36,70,252,155,13,119,131,192,11,50,110,46,22,42,14,35,225,182,62,86,1,107,110,249,76,250,12,7,253,241,16,85,36,70,252,155,13,119,131,192,11,50,110,46,60,48,109,132,225,182,62,86,1,107,110,249,76,250,12,7,253,241,16,85,36,70,252,155,13,119,131,192,11,50,110,46,174,168,77,194,114,252,130,194,92,96,157,227,76,250,12,7,253,241,16,85,36,70,252,155,13,119,131,192,11,50,110,46,31,107,190,9,114,252,130,194,92,96,157,227,76,250,12,7,253,241,16,85,36,70,252,155,13,119,131,192,11,50,110,46,230,216,74,121,36,64,151,125,92,96,157,227,76,250,12,7,253,241,16,85,36,70,252,155,13,119,131,192,11,50,110,46,252,126,158,68,36,64,151,125,92,96,157,227,76,250,12,7,253,241,16,85,36,70,252,155,13,119,131,192,11,50,110,46,13,230,124,118,17,156,215,202,183,49,105,235,215,51,80,145,139,170,98,148,168,231,5,161,169,49,25,41,205,145,153,193,1,20,130,34,17,156,215,202,183,49,105,235,215,51,80,145,139,170,98,148,168,231,5,161,169,49,25,41,205,145,153,193,32,221,48,116,36,250,74,15,183,49,105,235,215,51,80,145,139,170,98,148,168,231,5,161,169,49,25,41,205,145,153,193,63,95,167,113,36,250,74,15,183,49,105,235,215,51,80,145,139,170,98,148,168,231,5,161,169,49,25,41,205,145,153,193,169,207,209,44,18,101,254,55,135,197,25,81,215,51,80,145,139,170,98,148,168,231,5,161,169,49,25,41,205,145,153,193,23,12,240,245,18,101,254,55,135,197,25,81,215,51,80,145,139,170,98,148,168,231,5,161,169,49,25,41,205,145,153,193,210,175,232,112,231,137,163,84,135,197,25,81,215,51,80,145,139,170,98,148,168,231,5,161,169,49,25,41,205,145,153,193,166,94,127,63,231,137,163,84,135,197,25,81,215,51,80,145,139,170,98,148,168,231,5,161,169,49,25,41,205,145,153,193,14,93,152,208,42,201,125,57,85,173,43,21,129,232,137,147,139,170,98,148,168,231,5,161,169,49,25,41,205,145,153,193,46,53,192,226,42,201,125,57,85,173,43,21,129,232,137,147,139,170,98,148,168,231,5,161,169,49,25,41,205,145,153,193,249,48,209,230,27,246,163,220,85,173,43,21,129,232,137,147,139,170,98,148,168,231,5,161,169,49,25,41,205,145,153,193,26,123,5,72,27,246,163,220,85,173,43,21,129,232,137,147,139,170,98,148,168,231,5,161,169,49,25,41,205,145,153,193,179,126,101,6,84,20,12,42,18,232,75,10,129,232,137,147,139,170,98,148,168,231,5,161,169,49,25,41,205,145,153,193,232,165,48,43,84,20,12,42,18,232,75,10,129,232,137,147,139,170,98,148,168,231,5,161,169,49,25,41,205,145,153,193,34,165,130,235,198,44,224,37,18,232,75,10,129,232,137,147,139,170,98,148,168,231,5,161,169,49,25,41,205,145,153,193,6,32,139,240,198,44,224,37,18,232,75,10,129,232,137,147,139,170,98,148,168,231,5,161,169,49,25,41,205,145,153,193,237,233,7,132,34,148,45,218,205,140,108,59,178,172,196,88,124,98,133,186,168,231,5,161,169,49,25,41,205,145,153,193,221,88,169,2,34,148,45,218,205,140,108,59,178,172,196,88,124,98,133,186,168,231,5,161,169,49,25,41,205,145,153,193,154,157,80,249,138,243,53,229,205,140,108,59,178,172,196,88,124,98,133,186,168,231,5,161,169,49,25,41,205,145,153,193,223,53,21,123,138,243,53,229,205,140,108,59,178,172,196,88,124,98,133,186,168,231,5,161,169,49,25,41,205,145,153,193,24,201,152,48,77,11,238,45,47,105,248,72,178,172,196,88,124,98,133,186,168,231,5,161,169,49,25,41,205,145,153,193,223,99,236,125,77,11,238,45,47,105,248,72,178,172,196,88,124,98,133,186,168,231,5,161,169,49,25,41,205,145,153,193,9,96,140,175,11,32,118,46,47,105,248,72,178,172,196,88,124,98,133,186,168,231,5,161,169,49,25,41,205,145,153,193,250,227,240,143,11,32,118,46,47,105,248,72,178,172,196,88,124,98,133,186,168,231,5,161,169,49,25,41,205,145,153,193,89,231,69,134,170,55,205,13,225,0,77,244,145,124,231,103,124,98,133,186,168,231,5,161,169,49,25,41,205,145,153,193,122,0,252,131,170,55,205,13,225,0,77,244,145,124,231,103,124,98,133,186,168,231,5,161,169,49,25,41,205,145,153,193,169,149,133,58,8,52,141,54,225,0,77,244,145,124,231,103,124,98,133,186,168,231,5,161,169,49,25,41,205,145,153,193,174,100,160,121,8,52,141,54,225,0,77,244,145,124,231,103,124,98,133,186,168,231,5,161,169,49,25,41,205,145,153,193,253,115,9,78,253,6,237,133,242,199,92,176,145,124,231,103,124,98,133,186,168,231,5,161,169,49,25,41,205,145,153,193,128,154,161,213,253,6,237,133,242,199,92,176,145,124,231,103,124,98,133,186,168,231,5,161,169,49,25,41,205,145,153,193,144,39,254,0,138,111,241,58,242,199,92,176,145,124,231,103,124,98,133,186,168,231,5,161,169,49,25,41,205,145,153,193,17,162,36,204,138,111,241,58,242,199,92,176,145,124,231,103,124,98,133,186,168,231,5,161,169,49,25,41,205,145,153,193,145,210,170,33,178,23,38,4,249,81,224,211,160,149,210,156,151,119,3,93,184,170,201,21,169,49,25,41,205,145,153,193,202,10,231,163,178,23,38,4,249,81,224,211,160,149,210,156,151,119,3,93,184,170,201,21,169,49,25,41,205,145,153,193,149,65,26,52,53,143,130,185,249,81,224,211,160,149,210,156,151,119,3,93,184,170,201,21,169,49,25,41,205,145,153,193,99,34,51,113,53,143,130,185,249,81,224,211,160,149,210,156,151,119,3,93,184,170,201,21,169,49,25,41,205,145,153,193,29,16,14,2,184,254,73,5,136,27,21,135,160,149,210,156,151,119,3,93,184,170,201,21,169,49,25,41,205,145,153,193,97,21,41,130,184,254,73,5,136,27,21,135,160,149,210,156,151,119,3,93,184,170,201,21,169,49,25,41,205,145,153,193,187,59,211,67,136,22,241,209,136,27,21,135,160,149,210,156,151,119,3,93,184,170,201,21,169,49,25,41,205,145,153,193,224,227,56,163,136,22,241,209,136,27,21,135,160,149,210,156,151,119,3,93,184,170,201,21,169,49,25,41,205,145,153,193,64,72,173,170,66,192,129,16,61,45,220,54,214,237,112,183,151,119,3,93,184,170,201,21,169,49,25,41,205,145,153,193,23,244,16,132,66,192,129,16,61,45,220,54,214,237,112,183,151,119,3,93,184,170,201,21,169,49,25,41,205,145,153,193,49,245,173,189,156,87,248,186,61,45,220,54,214,237,112,183,151,119,3,93,184,170,201,21,169,49,25,41,205,145,153,193,144,118,245,143,156,87,248,186,61,45,220,54,214,237,112,183,151,119,3,93,184,170,201,21,169,49,25,41,205,145,153,193,182,244,204,65,238,158,136,206,182,187,182,151,214,237,112,183,151,119,3,93,184,170,201,21,169,49,25,41,205,145,153,193,32,108,165,42,238,158,136,206,182,187,182,151,214,237,112,183,151,119,3,93,184,170,201,21,169,49,25,41,205,145,153,193,173,189,141,102,147,196,149,176,182,187,182,151,214,237,112,183,151,119,3,93,184,170,201,21,169,49,25,41,205,145,153,193,246,192,37,126,147,196,149,176,182,187,182,151,214,237,112,183,151,119,3,93,184,170,201,21,169,49,25,41,205,145,153,193,243,79,2,122,92,204,93,90,204,88,124,201,60,130,205,15,127,121,251,11,184,170,201,21,169,49,25,41,205,145,153,193,42,108,224,229,92,204,93,90,204,88,124,201,60,130,205,15,127,121,251,11,184,170,201,21,169,49,25,41,205,145,153,193,74,193,250,241,54,84,165,173,204,88,124,201,60,130,205,15,127,121,251,11,184,170,201,21,169,49,25,41,205,145,153,193,50,173,7,109,54,84,165,173,204,88,124,201,60,130,205,15,127,121,251,11,184,170,201,21,169,49,25,41,205,145,153,193,191,252,45,227,225,175,9,37,8,140,232,231,60,130,205,15,127,121,251,11,184,170,201,21,169,49,25,41,205,145,153,193,62,119,175,121,225,175,9,37,8,140,232,231,60,130,205,15,127,121,251,11,184,170,201,21,169,49,25,41,205,145,153,193,47,51,38,223,133,178,119,244,8,140,232,231,60,130,205,15,127,121,251,11,184,170,201,21,169,49,25,41,205,145,153,193,1,178,83,242,133,178,119,244,8,140,232,231,60,130,205,15,127,121,251,11,184,170,201,21,169,49,25,41,205,145,153,193,43,190,11,27,44,226,61,236,100,101,157,254,86,254,208,218,127,121,251,11,184,170,201,21,169,49,25,41,205,145,153,193,124,72,20,198,44,226,61,236,100,101,157,254,86,254,208,218,127,121,251,11,184,170,201,21,169,49,25,41,205,145,153,193,14,88,191,54,173,170,78,64,100,101,157,254,86,254,208,218,127,121,251,11,184,170,201,21,169,49,25,41,205,145,153,193,198,20,180,133,173,170,78,64,100,101,157,254,86,254,208,218,127,121,251,11,184,170,201,21,169,49,25,41,205,145,153,193,86,153,4,30,192,111,140,96,80,35,205,94,86,254,208,218,127,121,251,11,184,170,201,21,169,49,25,41,205,145,153,193,150,173,116,181,192,111,140,96,80,35,205,94,86,254,208,218,127,121,251,11,184,170,201,21,169,49,25,41,205,145,153,193,221,6,67,158,137,248,10,193,80,35,205,94,86,254,208,218,127,121,251,11,184,170,201,21,169,49,25,41,205,145,153,193,100,151,106,29,137,248,10,193,80,35,205,94,86,254,208,218,127,121,251,11,184,170,201,21,169,49,25,41,205,145,153,193,88,83,106,181,176,46,58,108,158,69,241,23,131,185,71,239,249,90,208,219,50,56,128,16,39,36,114,140,205,145,153,193,161,230,124,15,176,46,58,108,158,69,241,23,131,185,71,239,249,90,208,219,50,56,128,16,39,36,114,140,205,145,153,193,16,18,120,121,20,157,197,130,158,69,241,23,131,185,71,239,249,90,208,219,50,56,128,16,39,36,114,140,205,145,153,193,58,198,103,66,20,157,197,130,158,69,241,23,131,185,71,239,249,90,208,219,50,56,128,16,39,36,114,140,205,145,153,193,57,196,42,236,188,201,76,45,180,210,216,181,131,185,71,239,249,90,208,219,50,56,128,16,39,36,114,140,205,145,153,193,81,131,20,21,188,201,76,45,180,210,216,181,131,185,71,239,249,90,208,219,50,56,128,16,39,36,114,140,205,145,153,193,206,207,152,201,155,240,76,139,180,210,216,181,131,185,71,239,249,90,208,219,50,56,128,16,39,36,114,140,205,145,153,193,254,91,123,120,155,240,76,139,180,210,216,181,131,185,71,239,249,90,208,219,50,56,128,16,39,36,114,140,205,145,153,193,182,125,143,86,188,11,230,127,58,74,252,219,243,220,247,115,249,90,208,219,50,56,128,16,39,36,114,140,205,145,153,193,32,88,58,158,188,11,230,127,58,74,252,219,243,220,247,115,249,90,208,219,50,56,128,16,39,36,114,140,205,145,153,193,187,57,85,90,11,131,61,239,58,74,252,219,243,220,247,115,249,90,208,219,50,56,128,16,39,36,114,140,205,145,153,193,40,251,141,75,11,131,61,239,58,74,252,219,243,220,247,115,249,90,208,219,50,56,128,16,39,36,114,140,205,145,153,193,116,232,18,4,37,43,67,225,144,156,101,124,243,220,247,115,249,90,208,219,50,56,128,16,39,36,114,140,205,145,153,193,76,77,62,226,37,43,67,225,144,156,101,124,243,220,247,115,249,90,208,219,50,56,128,16,39,36,114,140,205,145,153,193,216,65,104,210,141,5,173,175,144,156,101,124,243,220,247,115,249,90,208,219,50,56,128,16,39,36,114,140,205,145,153,193,109,141,210,161,141,5,173,175,144,156,101,124,243,220,247,115,249,90,208,219,50,56,128,16,39,36,114,140,205,145,153,193,238,226,2,92,42,239,5,81,77,101,160,124,120,171,179,254,113,48,110,147,50,56,128,16,39,36,114,140,205,145,153,193,138,24,186,238,42,239,5,81,77,101,160,124,120,171,179,254,113,48,110,147,50,56,128,16,39,36,114,140,205,145,153,193,8,158,91,229,207,11,159,198,77,101,160,124,120,171,179,254,113,48,110,147,50,56,128,16,39,36,114,140,205,145,153,193,49,35,250,235,207,11,159,198,77,101,160,124,120,171,179,254,113,48,110,147,50,56,128,16,39,36,114,140,205,145,153,193,54,112,81,75,32,156,170,201,175,73,239,231,120,171,179,254,113,48,110,147,50,56,128,16,39,36,114,140,205,145,153,193,28,150,74,93,32,156,170,201,175,73,239,231,120,171,179,254,113,48,110,147,50,56,128,16,39,36,114,140,205,145,153,193,155,189,6,233,10,239,214,233,175,73,239,231,120,171,179,254,113,48,110,147,50,56,128,16,39,36,114,140,205,145,153,193,223,55,136,55,10,239,214,233,175,73,239,231,120,171,179,254,113,48,110,147,50,56,128,16,39,36,114,140,205,145,153,193,111,154,180,28,25,229,39,173,135,72,150,61,147,214,199,152,113,48,110,147,50,56,128,16,39,36,114,140,205,145,153,193,116,184,247,71,25,229,39,173,135,72,150,61,147,214,199,152,113,48,110,147,50,56,128,16,39,36,114,140,205,145,153,193,75,249,199,13,48,182,150,175,135,72,150,61,147,214,199,152,113,48,110,147,50,56,128,16,39,36,114,140,205,145,153,193,143,63,60,255,48,182,150,175,135,72,150,61,147,214,199,152,113,48,110,147,50,56,128,16,39,36,114,140,205,145,153,193,175,246,44,216,102,52,237,10,146,220,104,237,147,214,199,152,113,48,110,147,50,56,128,16,39,36,114,140,205,145,153,193,97,69,87,36,102,52,237,10,146,220,104,237,147,214,199,152,113,48,110,147,50,56,128,16,39,36,114,140,205,145,153,193,204,131,86,83,35,22,177,96,146,220,104,237,147,214,199,152,113,48,110,147,50,56,128,16,39,36,114,140,205,145,153,193,61,168,118,195,35,22,177,96,146,220,104,237,147,214,199,152,113,48,110,147,50,56,128,16,39,36,114,140,205,145,153,193,132,118,242,32,140,185,38,31,244,28,217,183,43,201,19,159,142,40,224,45,187,74,156,253,39,36,114,140,205,145,153,193,83,32,90,221,140,185,38,31,244,28,217,183,43,201,19,159,142,40,224,45,187,74,156,253,39,36,114,140,205,145,153,193,227,69,26,211,68,62,78,226,244,28,217,183,43,201,19,159,142,40,224,45,187,74,156,253,39,36,114,140,205,145,153,193,60,215,255,231,68,62,78,226,244,28,217,183,43,201,19,159,142,40,224,45,187,74,156,253,39,36,114,140,205,145,153,193,201,242,27,183,96,251,95,202,200,19,188,50,43,201,19,159,142,40,224,45,187,74,156,253,39,36,114,140,205,145,153,193,0,238,219,13,96,251,95,202,200,19,188,50,43,201,19,159,142,40,224,45,187,74,156,253,39,36,114,140,205,145,153,193,5,13,224,73,145,158,120,63,200,19,188,50,43,201,19,159,142,40,224,45,187,74,156,253,39,36,114,140,205,145,153,193,76,251,117,59,145,158,120,63,200,19,188,50,43,201,19,159,142,40,224,45,187,74,156,253,39,36,114,140,205,145,153,193,225,219,27,34,167,21,105,44,50,101,172,28,137,231,135,203,142,40,224,45,187,74,156,253,39,36,114,140,205,145,153,193,11,245,144,125,167,21,105,44,50,101,172,28,137,231,135,203,142,40,224,45,187,74,156,253,39,36,114,140,205,145,153,193,49,119,131,237,24,221,50,135,50,101,172,28,137,231,135,203,142,40,224,45,187,74,156,253,39,36,114,140,205,145,153,193,56,188,153,118,24,221,50,135,50,101,172,28,137,231,135,203,142,40,224,45,187,74,156,253,39,36,114,140,205,145,153,193,25,247,250,36,202,15,38,98,231,5,85,193,137,231,135,203,142,40,224,45,187,74,156,253,39,36,114,140,205,145,153,193,26,122,26,117,202,15,38,98,231,5,85,193,137,231,135,203,142,40,224,45,187,74,156,253,39,36,114,140,205,145,153,193,154,108,50,14,83,247,163,3,231,5,85,193,137,231,135,203,142,40,224,45,187,74,156,253,39,36,114,140,205,145,153,193,237,13,251,160,83,247,163,3,231,5,85,193,137,231,135,203,142,40,224,45,187,74,156,253,39,36,114,140,205,145,153,193,241,90,51,108,160,86,8,3,35,42,57,2,24,12,220,46,6,151,150,245,187,74,156,253,39,36,114,140,205,145,153,193,128,55,252,79,160,86,8,3,35,42,57,2,24,12,220,46,6,151,150,245,187,74,156,253,39,36,114,140,205,145,153,193,209,217,193,197,18,124,187,233,35,42,57,2,24,12,220,46,6,151,150,245,187,74,156,253,39,36,114,140,205,145,153,193,77,162,87,54,18,124,187,233,35,42,57,2,24,12,220,46,6,151,150,245,187,74,156,253,39,36,114,140,205,145,153,193,34,68,123,53,182,252,59,195,146,136,61,177,24,12,220,46,6,151,150,245,187,74,156,253,39,36,114,140,205,145,153,193,84,169,7,120,182,252,59,195,146,136,61,177,24,12,220,46,6,151,150,245,187,74,156,253,39,36,114,140,205,145,153,193,54,8,118,114,58,164,90,159,146,136,61,177,24,12,220,46,6,151,150,245,187,74,156,253,39,36,114,140,205,145,153,193,198,185,26,150,58,164,90,159,146,136,61,177,24,12,220,46,6,151,150,245,187,74,156,253,39,36,114,140,205,145,153,193,43,2,251,13,99,37,63,226,66,59,122,13,69,38,40,64,6,151,150,245,187,74,156,253,39,36,114,140,205,145,153,193,189,199,0,181,99,37,63,226,66,59,122,13,69,38,40,64,6,151,150,245,187,74,156,253,39,36,114,140,205,145,153,193,8,8,189,31,184,65,4,129,66,59,122,13,69,38,40,64,6,151,150,245,187,74,156,253,39,36,114,140,205,145,153,193,79,53,110,102,184,65,4,129,66,59,122,13,69,38,40,64,6,151,150,245,187,74,156,253,39,36,114,140,205,145,153,193,160,16,43,19,103,77,131,14,242,113,165,227,69,38,40,64,6,151,150,245,187,74,156,253,39,36,114,140,205,145,153,193,22,138,15,158,103,77,131,14,242,113,165,227,69,38,40,64,6,151,150,245,187,74,156,253,39,36,114,140,205,145,153,193,244,11,70,181,109,53,81,138,242,113,165,227,69,38,40,64,6,151,150,245,187,74,156,253,39,36,114,140,205,145,153,193,128,242,89,33,109,53,81,138,242,113,165,227,69,38,40,64,6,151,150,245,187,74,156,253,39,36,114,140,205,145,153,193}; + +struct ProofCase { uint32_t count; const uint8_t* leaves; const uint8_t* root; const uint16_t* poff; const uint8_t* pnsib; const uint8_t* pblob; }; +static const ProofCase PROOF_CASES[] = {{T0_COUNT,T0_LEAVES,T0_ROOT,T0_POFF,T0_PNSIB,T0_PBLOB},{T1_COUNT,T1_LEAVES,T1_ROOT,T1_POFF,T1_PNSIB,T1_PBLOB},{T2_COUNT,T2_LEAVES,T2_ROOT,T2_POFF,T2_PNSIB,T2_PBLOB},{T3_COUNT,T3_LEAVES,T3_ROOT,T3_POFF,T3_PNSIB,T3_PBLOB},{T4_COUNT,T4_LEAVES,T4_ROOT,T4_POFF,T4_PNSIB,T4_PBLOB},{T5_COUNT,T5_LEAVES,T5_ROOT,T5_POFF,T5_PNSIB,T5_PBLOB},{T6_COUNT,T6_LEAVES,T6_ROOT,T6_POFF,T6_PNSIB,T6_PBLOB}}; +static const int N_PROOF_CASES = 7; +// small-block signed .mota for the OtaManager host transfer simulation +static const uint8_t SIM_MOTA[2250] = {109,79,84,65,202,8,0,0,1,3,18,190,186,254,202,0,0,0,3,224,7,0,0,224,7,0,0,7,85,156,8,12,169,195,206,175,23,28,248,7,221,23,193,55,91,158,160,26,154,55,205,197,206,22,121,30,239,123,194,48,85,211,13,168,0,3,161,7,191,243,206,16,190,29,112,221,24,231,75,192,153,103,228,214,48,155,165,13,95,29,220,134,100,18,85,49,184,226,220,156,104,50,61,218,240,202,68,220,194,22,110,88,30,125,55,174,55,215,104,138,31,250,53,196,215,49,137,139,13,160,147,113,17,10,159,226,81,187,200,59,244,246,151,12,196,105,154,73,105,89,89,213,132,192,193,157,48,111,4,186,15,255,255,255,255,95,197,98,90,98,17,7,154,48,52,162,55,106,72,59,84,136,235,135,81,243,8,241,172,4,174,251,120,121,213,89,254,16,213,235,145,157,150,188,132,125,7,184,29,126,203,177,170,175,71,66,193,125,16,43,58,126,48,74,90,174,73,65,111,103,97,51,153,45,58,63,34,194,22,64,186,98,135,175,179,137,22,240,159,125,51,107,184,156,150,55,202,230,201,95,210,99,220,174,54,38,118,169,45,217,145,86,21,242,183,135,173,198,22,120,80,103,10,68,142,1,108,52,92,253,238,136,20,118,132,230,50,155,118,250,13,91,33,55,243,235,183,41,191,202,113,152,236,251,45,241,128,169,45,9,28,5,43,121,175,209,107,20,239,108,161,22,36,79,158,1,97,84,34,133,103,51,230,201,249,14,163,54,113,65,139,103,211,195,32,90,240,177,121,236,14,33,189,221,183,153,199,191,38,155,240,104,31,24,30,36,75,44,253,45,104,37,236,53,225,143,81,197,127,204,59,216,65,30,219,210,92,47,173,18,128,73,2,149,213,12,86,55,96,102,86,210,207,102,98,106,208,94,136,23,46,225,9,236,95,184,219,102,197,113,58,79,218,167,57,167,184,79,27,223,176,184,80,55,195,207,182,172,182,63,98,75,112,192,245,20,225,112,107,246,232,65,17,128,75,232,175,223,82,201,122,136,32,238,229,65,110,225,74,104,247,168,211,156,40,72,97,173,173,154,220,252,158,170,182,112,49,97,86,227,50,88,1,170,90,177,75,246,217,49,228,170,170,43,202,163,66,168,106,190,192,221,180,59,104,220,220,88,168,102,0,28,128,136,51,0,133,79,243,172,250,191,253,42,130,200,206,113,238,52,67,191,217,189,106,119,143,135,160,158,196,161,212,195,155,196,73,51,189,5,255,26,206,78,19,148,8,246,14,106,76,94,248,142,160,242,181,150,147,75,49,193,25,253,221,203,101,102,202,44,67,117,84,32,224,33,230,114,222,217,177,179,99,107,143,244,183,45,92,64,53,70,118,146,216,227,179,97,47,211,233,239,255,122,146,228,160,227,234,121,170,43,250,19,136,61,255,117,133,3,29,84,203,126,18,85,167,187,1,145,174,136,70,162,3,149,81,105,66,42,34,150,235,12,101,9,12,87,130,58,142,193,74,40,214,112,14,178,198,106,58,206,48,29,124,43,69,70,168,93,18,143,235,50,198,143,44,179,90,167,245,208,136,20,190,173,29,185,32,211,35,38,139,22,8,194,131,165,97,42,90,222,200,183,59,157,155,194,247,156,41,87,197,13,228,87,207,160,111,48,78,45,189,28,37,127,164,247,133,187,238,3,59,96,234,174,203,24,200,203,253,60,145,113,187,202,154,223,227,20,224,209,41,224,227,214,42,198,113,95,64,80,150,11,149,101,5,76,242,40,33,241,2,225,73,207,142,75,74,31,126,109,205,18,10,90,41,155,174,81,216,85,169,83,206,212,247,8,96,208,69,156,58,140,38,177,172,4,195,220,240,73,189,102,107,131,183,230,198,58,44,134,104,253,13,54,61,165,66,41,215,145,24,111,1,144,241,7,214,51,33,230,244,46,161,76,15,253,225,65,46,148,195,110,38,0,238,164,9,128,101,239,253,250,114,18,42,150,59,87,111,33,122,234,156,23,94,128,40,59,187,146,124,53,183,218,25,111,156,51,231,204,44,34,121,188,229,63,44,215,175,65,42,166,132,172,204,129,234,222,217,139,19,60,61,134,58,175,180,107,101,134,143,18,61,64,82,196,144,85,126,48,24,203,160,7,241,9,111,15,80,48,236,12,169,74,47,68,230,236,171,65,2,147,135,75,50,185,93,29,40,88,99,36,178,78,135,66,50,115,14,241,245,6,90,59,36,155,20,213,220,97,4,91,240,81,227,96,62,137,39,199,18,5,7,45,54,56,87,158,65,194,241,18,165,0,111,29,168,111,20,177,216,249,186,94,127,108,142,224,96,196,143,231,165,67,83,182,88,206,29,69,135,62,16,238,8,56,68,83,158,26,69,224,78,159,159,62,54,113,170,113,133,4,154,190,67,19,198,98,31,188,4,72,126,195,241,230,206,180,113,109,203,237,193,82,130,255,60,154,177,136,236,223,221,27,234,73,101,198,53,17,151,236,71,116,190,125,86,255,152,21,178,5,12,144,21,162,33,220,104,49,108,226,169,168,150,148,4,5,31,204,82,55,52,170,163,254,27,3,255,110,212,240,224,73,46,214,34,209,198,176,73,255,45,180,106,26,79,215,47,192,107,98,161,24,19,68,242,134,77,64,217,252,186,40,192,70,186,31,8,191,154,212,17,64,121,100,32,227,74,153,123,55,216,255,220,197,128,78,212,114,64,128,64,162,47,117,72,38,48,84,130,32,205,128,58,157,90,154,132,18,58,142,111,200,37,146,0,86,185,201,187,102,148,203,21,2,108,22,108,117,27,21,1,49,222,238,22,198,61,76,49,29,37,187,66,181,79,44,211,142,173,164,48,22,157,28,82,6,247,214,55,76,103,85,129,10,113,166,247,64,122,138,157,89,55,89,33,11,179,83,178,233,141,241,172,233,59,217,53,207,204,160,133,154,108,183,121,245,41,154,88,142,45,186,132,216,204,136,78,5,4,155,151,88,39,32,111,11,17,136,81,244,172,142,74,213,92,44,141,84,177,244,197,176,25,186,140,75,8,170,232,87,108,50,162,40,10,44,160,23,121,100,109,222,139,139,97,95,151,164,213,236,96,66,26,235,52,251,199,90,34,16,67,174,25,235,1,94,99,80,151,60,151,31,154,170,250,74,254,238,70,104,229,33,34,170,227,251,215,33,73,248,179,169,200,196,219,242,6,140,130,136,67,36,63,105,134,141,32,67,77,111,190,98,23,214,246,88,7,229,125,58,122,166,16,127,225,77,61,199,129,253,9,32,116,207,202,17,236,90,143,244,33,247,164,237,253,90,198,189,137,250,10,16,3,212,55,185,188,238,3,115,51,164,134,195,117,127,162,183,0,55,186,11,155,56,39,252,163,110,47,227,32,248,220,234,206,140,117,73,213,155,5,197,212,184,208,8,77,14,160,8,175,63,145,100,217,95,240,194,127,195,34,72,146,89,178,228,38,176,235,8,201,84,147,210,95,137,215,51,170,106,244,173,67,45,131,65,122,215,20,99,181,105,184,253,66,245,155,77,122,0,142,44,197,249,7,179,64,59,44,231,172,224,244,182,215,4,40,135,220,56,76,65,82,100,91,102,41,226,67,95,200,42,1,39,140,118,118,71,188,214,123,161,81,187,161,45,83,145,36,176,157,12,128,102,142,171,150,39,203,245,119,54,128,30,203,176,101,252,84,197,108,136,80,84,250,55,180,16,22,57,249,160,194,63,203,253,98,40,248,62,182,104,245,244,58,87,20,47,15,185,14,22,161,120,97,55,186,249,67,112,86,129,1,244,46,167,142,254,163,109,152,223,164,111,81,70,188,245,43,159,236,36,123,145,95,176,215,34,244,123,253,113,118,46,116,175,132,124,152,152,76,137,114,157,118,199,25,123,8,247,235,230,172,116,49,31,88,125,2,97,111,227,242,80,212,15,186,50,32,224,200,31,238,232,140,109,243,138,238,193,253,222,0,28,32,150,190,48,34,244,56,197,196,64,5,88,180,249,15,245,11,196,51,9,49,56,240,154,254,52,54,132,192,1,47,118,67,121,90,212,93,88,234,230,86,62,74,4,165,115,26,42,148,244,10,210,223,239,225,54,204,223,102,234,204,72,197,208,214,94,87,47,181,173,120,85,167,77,201,165,122,165,126,3,120,7,65,137,154,23,84,27,251,239,7,243,108,182,237,236,95,176,233,237,180,0,44,95,91,93,150,86,172,69,245,81,234,190,228,243,239,236,67,99,42,154,211,59,170,79,213,233,246,137,20,22,171,123,18,201,20,174,51,28,88,99,220,82,250,56,219,62,13,214,33,141,64,205,143,101,42,215,166,1,222,87,80,48,96,68,127,188,203,45,43,224,181,148,200,224,171,5,168,15,39,8,78,252,90,120,37,15,35,73,166,90,148,66,63,80,237,61,15,63,64,110,238,28,211,153,21,206,143,45,0,163,151,175,49,81,125,98,92,35,255,157,214,250,223,198,59,54,238,109,208,130,116,71,106,246,221,224,50,114,241,58,227,192,72,132,9,183,55,223,87,248,119,200,188,52,25,141,180,183,9,44,8,253,50,69,220,217,111,3,18,22,43,4,63,22,31,19,17,20,4,185,220,235,68,204,223,209,95,199,95,44,113,98,153,12,109,46,120,50,131,118,82,236,19,90,44,176,79,168,210,78,153,10,163,55,171,51,11,131,141,80,191,166,60,156,119,28,69,220,141,106,190,54,31,8,41,123,205,30,202,192,46,51,33,43,160,109,215,19,150,232,243,219,67,20,200,61,53,213,132,196,237,137,4,173,157,112,173,155,116,158,69,190,147,248,64,132,112,33,143,197,191,249,118,137,88,239,250,192,122,192,62,202,131,155,222,27,224,34,17,222,231,164,17,43,154,60,226,223,135,32,134,149,107,194,168,74,204,4,72,98,47,171,92,224,231,153,13,18,153,50,192,130,191,64,92,33,243,39,161,69,110,100,70,208,7,0,0,250,86,224,170,102,212,176,242,118,107,52,57,54}; + +static const uint32_t SIM_MOTA_LEN = 2250; +static const uint32_t SIM_TARGET_ID = 0xcafebabeu; +// detools sequential+crle delta: apply DT_PATCH to DT_BASE -> DT_TARGET +static const uint8_t DT_BASE[3016] = {120,46,186,148,77,51,227,185,104,193,183,194,67,136,62,162,208,188,127,90,106,134,186,157,246,55,79,139,180,84,132,19,187,198,255,221,52,176,192,186,119,236,181,212,223,167,37,136,54,222,105,250,14,197,89,160,106,119,31,185,190,35,195,83,99,84,88,203,51,83,109,106,81,145,54,231,222,104,58,52,10,191,57,195,4,248,221,66,216,129,81,197,245,145,205,180,107,157,28,84,217,167,155,199,59,60,254,118,93,34,51,94,126,152,214,160,36,67,99,159,86,85,240,181,255,182,119,220,43,175,178,196,220,33,84,236,52,148,175,16,25,240,215,44,1,230,38,112,180,60,89,58,20,50,205,72,61,177,118,154,67,123,134,225,111,169,248,106,51,215,18,77,77,71,34,144,169,187,64,134,25,126,55,228,50,200,99,45,131,217,57,81,90,192,179,228,204,11,148,230,238,173,139,96,239,189,184,243,162,18,30,58,14,132,32,241,212,53,232,162,157,236,22,242,129,44,60,124,149,204,187,42,41,22,32,158,26,207,241,152,143,207,254,154,161,7,152,27,143,46,139,178,80,0,31,71,7,46,15,26,162,219,159,172,158,187,53,148,53,165,48,118,47,121,80,69,187,116,162,112,213,183,206,210,55,102,150,221,114,221,107,152,179,34,225,53,41,79,101,50,192,45,91,116,175,3,30,85,172,0,197,57,192,129,107,168,249,9,54,155,118,141,127,140,206,12,110,85,2,130,87,141,249,230,240,224,65,170,187,40,57,154,248,27,211,203,216,110,17,245,225,34,44,6,220,86,78,242,234,64,126,41,92,213,119,237,111,241,125,157,83,40,9,197,31,30,93,108,162,244,46,129,180,24,13,168,111,228,6,207,233,227,240,69,59,30,81,139,222,145,35,54,145,149,27,110,26,241,153,180,178,68,111,143,40,195,59,240,0,131,31,50,96,137,146,104,114,146,201,44,212,165,236,63,141,235,195,74,230,208,70,151,91,115,163,28,103,101,194,72,81,128,133,58,60,210,199,206,91,58,107,201,119,146,79,73,245,172,175,236,49,119,165,138,13,64,97,211,166,53,67,105,132,34,167,80,72,176,137,206,241,34,195,23,129,56,118,155,71,75,63,165,132,99,189,72,244,47,246,228,233,247,122,206,93,247,7,152,165,96,177,16,193,185,231,34,25,108,156,82,48,255,196,244,244,19,194,148,65,8,206,163,198,66,171,217,133,48,241,218,204,111,42,49,182,120,211,68,17,118,31,25,151,68,33,191,98,200,250,150,212,165,25,57,193,149,60,42,75,166,229,35,209,177,238,206,98,175,27,249,33,86,105,84,161,179,85,140,210,192,60,5,154,71,97,35,145,68,43,129,204,230,65,55,225,230,140,33,179,109,189,138,48,32,208,33,172,177,59,64,6,163,157,173,28,251,108,135,107,8,121,39,70,182,92,118,88,74,123,227,173,148,114,232,141,165,8,174,233,72,47,98,166,229,126,163,92,128,124,93,240,40,16,129,188,248,249,141,68,50,46,112,119,83,159,1,36,188,29,108,12,72,193,168,191,20,181,224,21,171,122,118,241,53,134,128,172,191,216,59,171,163,169,80,167,131,162,77,124,75,148,14,173,54,172,186,138,121,182,62,79,166,255,80,64,84,10,35,159,137,202,138,66,88,88,38,13,89,3,158,80,192,80,50,47,239,72,206,116,135,12,251,40,25,201,123,163,67,100,13,105,51,174,34,34,93,3,115,92,192,106,231,63,31,87,218,156,115,83,1,188,172,152,212,206,133,105,183,160,198,113,51,32,213,29,130,254,173,239,245,104,126,255,101,127,115,186,56,236,11,217,178,248,114,215,168,163,134,209,19,244,68,229,206,194,29,94,160,221,137,217,92,17,83,227,202,123,155,151,121,220,191,199,202,241,205,234,70,164,104,185,211,96,128,163,65,230,28,4,63,219,134,230,237,183,181,229,109,41,192,38,48,3,205,3,69,15,27,85,41,249,58,60,229,132,70,199,19,39,247,213,215,10,64,234,230,209,250,91,21,134,94,143,43,27,92,64,200,3,206,207,147,130,194,137,110,114,158,192,62,200,9,173,197,36,195,222,90,68,139,0,117,232,231,62,93,112,14,37,53,86,17,150,199,31,117,20,200,185,21,58,0,193,20,229,210,41,161,119,53,225,179,50,59,120,212,104,243,159,148,20,197,167,106,194,135,192,98,133,68,210,210,227,227,161,183,180,36,62,92,189,50,39,122,5,177,246,255,185,32,70,105,173,233,213,61,174,111,102,212,174,40,98,96,113,237,146,163,30,186,69,139,67,157,37,83,30,161,170,92,20,88,126,31,148,49,32,133,35,206,117,26,155,154,102,119,184,196,144,146,104,188,112,247,229,124,82,237,146,81,215,157,134,60,215,44,249,56,157,202,243,236,105,6,130,117,190,131,93,11,78,190,251,41,176,233,229,147,70,59,56,145,165,174,147,111,242,160,241,201,203,118,88,132,232,178,22,117,168,133,225,76,86,240,4,246,158,51,191,121,245,52,164,72,112,109,131,246,21,37,123,166,143,107,76,69,128,184,87,200,23,39,48,21,138,248,161,140,8,151,174,127,172,55,217,205,164,191,41,40,21,65,102,38,231,245,193,137,82,79,200,219,185,83,168,181,87,125,247,47,112,61,195,99,115,20,131,186,193,11,9,106,72,43,175,52,216,11,212,247,27,151,35,162,53,237,20,208,201,47,218,30,26,86,4,185,26,142,255,250,59,219,212,222,152,155,131,117,49,122,51,2,78,187,225,215,90,99,196,255,71,7,19,218,205,190,132,213,178,193,114,94,81,104,244,138,51,254,116,38,23,73,190,214,169,31,220,41,172,172,205,57,159,93,237,199,135,219,63,101,42,222,90,192,55,33,98,171,182,18,47,63,214,206,6,76,251,28,178,150,174,154,63,127,87,170,172,107,230,55,12,253,165,15,70,55,191,80,114,158,203,222,55,160,212,2,147,59,21,136,61,126,64,193,123,132,221,255,44,162,133,94,13,143,204,9,115,165,105,81,111,100,16,20,44,80,204,209,171,181,144,67,195,225,253,33,209,171,193,155,183,25,142,150,232,170,231,94,127,241,58,4,97,162,46,174,96,95,35,15,98,225,46,228,222,216,179,230,92,153,238,220,253,247,20,57,60,168,242,236,186,70,34,78,184,1,123,95,111,107,91,156,13,3,71,10,125,232,250,215,249,13,223,48,132,48,156,61,129,121,46,148,222,129,228,3,189,217,19,25,6,29,125,180,13,205,152,222,79,198,73,205,97,155,156,229,15,195,235,128,254,9,137,30,63,146,187,223,104,18,14,16,87,252,21,24,8,109,65,199,160,20,169,27,205,213,146,211,166,109,129,203,224,196,235,198,154,251,215,11,228,122,121,254,118,28,97,98,132,36,48,194,97,45,127,162,252,193,144,38,51,235,54,12,75,128,236,76,100,184,5,223,113,92,189,7,229,144,154,126,214,210,14,217,255,3,240,73,227,232,82,220,33,93,55,112,7,32,76,178,247,134,245,50,157,101,68,55,237,191,114,53,220,235,56,124,190,163,8,192,216,1,26,101,153,181,14,174,1,213,255,111,61,46,65,109,153,124,203,111,65,13,206,176,115,10,245,43,152,144,152,119,211,90,150,27,127,253,111,51,198,47,247,9,224,245,233,188,100,81,18,83,249,180,142,84,249,109,215,178,75,102,203,163,250,154,185,204,231,27,239,194,79,230,120,16,249,205,89,189,108,124,23,74,196,171,118,88,103,14,22,9,250,115,195,156,95,122,201,54,2,16,21,23,8,89,182,241,168,220,85,132,206,169,166,61,26,8,208,0,235,19,187,18,27,197,95,7,147,13,48,151,71,243,218,70,3,189,109,122,228,228,24,168,225,138,111,120,81,184,200,93,61,118,227,105,174,242,24,113,22,201,161,12,120,202,28,112,5,43,46,172,168,46,226,229,44,48,20,91,10,59,228,230,236,190,97,0,217,185,27,113,192,7,100,14,89,93,107,66,95,179,55,124,165,105,168,237,38,3,204,188,169,189,35,154,233,228,249,76,132,68,255,95,118,181,182,132,234,105,157,178,176,132,195,183,64,62,170,40,20,135,99,6,238,75,241,135,101,245,19,28,239,79,117,251,11,161,103,148,230,156,97,82,245,248,36,29,77,232,24,61,115,206,10,85,185,234,141,140,183,158,86,215,114,111,36,207,92,252,210,111,241,157,99,97,115,199,170,90,232,213,30,185,191,109,54,216,177,170,225,232,90,48,170,58,215,86,94,202,54,39,191,150,47,189,120,234,161,59,91,219,95,38,160,190,181,2,118,54,254,2,89,50,63,108,80,84,9,115,125,140,243,178,117,100,5,38,237,219,200,62,254,182,2,127,93,146,47,79,13,150,88,221,178,75,232,71,43,46,81,92,163,53,180,207,202,243,40,229,120,166,221,150,6,20,155,26,22,24,92,163,85,117,214,210,191,112,30,227,92,77,38,111,245,111,152,252,10,35,129,158,153,153,20,150,170,209,86,177,142,81,169,6,55,32,170,218,25,23,47,109,30,135,107,196,169,200,79,178,229,12,27,72,239,20,61,227,166,234,174,86,177,184,197,72,161,19,162,9,20,149,29,178,171,11,75,218,23,33,14,131,126,156,122,48,129,25,221,92,199,2,61,248,38,79,17,27,76,70,170,222,166,233,192,96,154,178,148,64,21,136,4,86,222,208,52,174,19,163,74,215,130,27,118,18,129,120,192,188,202,75,171,253,246,36,163,136,10,247,100,10,119,218,67,158,103,253,48,145,245,89,110,244,154,197,9,238,221,133,47,78,160,159,204,225,4,169,209,223,136,127,179,234,176,178,32,244,109,158,123,117,143,142,122,200,37,130,29,87,226,67,209,10,52,142,29,49,199,189,107,161,95,142,101,182,230,0,3,181,31,212,100,211,194,68,88,239,122,38,199,231,62,143,195,76,198,217,49,152,198,98,169,6,111,161,208,229,37,157,26,175,58,235,194,12,130,208,47,58,96,40,177,162,78,24,167,121,80,70,17,255,86,54,223,60,120,243,107,155,52,51,131,168,140,230,94,207,167,67,162,146,199,15,19,23,147,144,161,222,118,19,234,109,214,155,142,65,191,43,26,239,28,102,167,172,222,237,26,148,36,49,105,211,242,73,60,84,80,26,221,208,83,138,48,87,250,120,37,33,218,171,246,97,171,6,247,86,59,64,65,8,160,226,204,127,68,59,77,12,12,224,140,114,52,166,11,25,44,233,41,5,3,182,252,41,105,251,215,25,153,44,189,144,150,73,1,142,202,175,232,240,68,218,90,200,93,130,163,19,144,168,137,22,244,71,172,30,111,151,230,127,28,105,156,154,140,249,220,201,203,136,93,28,117,47,165,141,138,122,248,160,217,119,113,167,209,245,182,1,252,150,25,54,36,62,194,197,136,4,190,12,126,29,230,234,246,252,254,36,99,6,237,175,42,95,94,164,0,220,157,137,123,47,38,152,42,140,245,31,54,38,211,14,178,164,2,211,234,250,121,117,69,98,190,78,20,238,181,194,96,144,209,138,137,232,226,205,225,115,24,57,129,221,89,191,35,96,149,25,151,73,139,213,145,7,162,175,53,142,12,248,115,235,28,27,222,249,253,184,233,157,176,24,138,48,219,6,132,62,41,213,69,73,110,37,72,178,91,57,202,151,2,180,18,216,169,25,181,105,47,144,143,2,187,101,193,163,107,54,42,18,254,14,108,234,166,29,33,219,251,212,119,76,247,204,242,159,49,224,204,189,75,30,135,183,182,147,252,14,187,185,86,6,228,160,111,45,122,79,195,199,30,106,201,237,247,201,72,29,186,226,152,54,181,133,122,118,173,45,197,146,66,247,83,25,176,166,117,207,96,149,25,198,175,41,154,59,109,225,184,107,236,150,25,34,202,13,43,92,96,249,85,73,54,22,172,123,6,174,149,76,59,19,63,195,115,254,12,143,253,116,104,204,9,75,118,200,248,23,178,80,193,61,48,128,232,47,156,18,18,158,148,238,118,11,167,70,41,112,70,48,178,1,174,82,110,22,16,245,110,154,81,216,72,211,37,132,72,185,141,157,102,134,78,205,218,30,109,59,236,102,25,38,221,165,122,246,131,120,66,137,225,50,184,190,74,195,26,32,155,162,132,142,156,141,32,2,209,213,139,198,59,168,211,168,199,46,189,74,203,219,127,108,221,43,124,185,228,187,250,239,195,8,221,31,84,161,24,80,133,244,168,50,199,166,173,233,92,225,38,1,54,145,7,160,122,105,0,32,234,2,89,146,61,78,132,101,85,31,164,226,151,123,53,197,167,56,212,124,226,220,252,7,161,98,26,46,140,40,136,42,158,224,188,172,96,245,228,231,224,19,99,127,123,162,38,111,213,4,169,9,38,253,178,208,83,237,62,224,131,82,90,104,235,13,88,191,215,199,182,210,225,25,64,216,125,102,252,136,181,125,116,52,11,149,169,37,139,183,73,178,160,210,184,143,164,202,70,104,52,141,189,138,2,105,15,214,66,15,180,136,205,72,233,248,103,141,34,179,176,190,125,226,194,107,143,147,164,226,90,12,17,135,137,31,15,146,15,78,151,177,88,61,19,157,195,159,16,1,144,109,100,25,169,117,64,189,36,176,62,18,199,120,137,164,82,192,209,137,235,200,110,117,175,96,198,31,20,251,69,110,100,70,184,11,0,0,206,99,32,159,72,64,138,233}; + +static const uint32_t DT_BASE_LEN = 3016; +static const uint8_t DT_PATCH[252] = {2,144,50,0,3,0,183,46,1,137,1,0,0,3,58,70,58,1,208,10,0,0,1,90,1,218,11,0,0,221,1,153,3,161,65,21,222,118,227,83,116,130,47,46,29,77,101,152,18,250,150,181,146,187,69,168,41,231,203,39,100,60,10,171,117,166,25,196,223,139,175,135,160,165,105,80,177,67,238,254,178,160,71,28,52,19,182,119,243,6,138,208,97,27,67,219,113,216,204,202,205,248,201,87,118,153,109,114,73,143,254,2,242,154,38,46,102,225,149,144,52,199,233,250,158,219,149,126,61,148,56,5,192,133,20,207,187,123,188,13,103,205,254,252,168,177,233,227,201,226,244,110,112,157,146,184,209,213,57,222,245,174,112,135,212,136,219,201,165,19,83,42,252,113,219,24,136,72,17,243,212,100,222,153,200,61,81,236,19,226,122,18,18,186,90,31,101,103,96,250,243,52,123,4,252,143,86,100,29,235,253,116,35,103,161,211,45,146,97,128,89,98,232,29,89,4,210,177,153,51,113,37,130,194,69,110,100,70,128,12,0,0,252,28,232,94,154,58,84,206,224,26}; + +static const uint32_t DT_PATCH_LEN = 252; +static const uint8_t DT_TARGET[3216] = {120,46,186,148,77,51,227,185,104,193,183,194,67,136,62,162,208,188,127,90,106,134,186,157,246,55,79,139,180,84,132,19,187,198,255,221,52,176,192,186,119,236,181,212,223,167,37,136,54,222,105,250,14,197,89,160,106,119,31,185,190,35,195,83,99,84,88,203,51,83,109,106,81,145,54,231,222,104,58,52,10,191,57,195,4,248,221,66,216,129,81,197,245,145,205,180,107,157,28,84,217,167,155,199,59,60,254,118,93,34,51,94,126,152,214,160,36,67,99,159,86,85,240,181,255,182,119,220,43,175,178,196,220,33,84,236,52,206,245,74,25,240,215,44,1,230,38,112,180,60,89,58,20,50,205,72,61,177,118,154,67,123,134,225,111,169,248,106,51,215,18,77,77,71,34,144,169,187,64,134,25,126,55,228,50,200,99,45,131,217,57,81,90,192,179,228,204,11,148,230,238,173,139,96,239,189,184,243,162,18,30,58,14,132,32,241,212,53,232,162,157,236,22,242,129,44,60,124,149,204,187,42,41,22,32,158,26,207,241,152,143,207,254,154,161,7,152,27,143,46,139,178,80,0,31,71,7,46,15,26,162,219,159,172,158,187,53,148,53,165,48,118,47,121,80,69,187,116,162,112,213,183,206,210,55,102,150,221,114,221,107,152,179,34,225,53,41,79,101,50,192,45,91,116,175,3,30,85,172,0,197,57,192,129,107,168,249,9,54,155,118,141,127,140,206,12,110,85,2,130,87,141,249,230,240,224,65,170,187,40,57,154,248,27,211,203,216,110,17,245,225,34,44,6,220,86,78,242,234,64,126,41,92,213,119,237,111,241,125,157,83,40,9,197,31,30,93,108,162,244,46,129,180,24,13,168,111,228,6,207,233,227,240,69,59,30,81,139,222,145,35,54,145,149,27,110,26,241,153,180,178,68,111,143,40,195,59,240,0,131,31,50,96,137,146,104,114,146,201,44,212,165,236,63,141,235,195,74,230,208,70,151,91,115,163,28,103,101,194,72,81,128,133,58,60,210,199,206,91,58,107,201,119,146,79,73,245,172,175,236,49,119,165,138,13,64,97,211,166,53,67,105,132,34,167,80,72,176,137,206,241,34,195,23,129,56,118,155,71,75,63,165,132,99,189,72,244,47,246,228,233,247,122,206,93,247,7,152,165,96,177,16,193,185,231,34,25,108,156,82,48,255,196,244,244,19,194,148,65,8,206,163,198,66,171,217,133,48,241,218,204,111,42,49,182,120,211,68,17,118,31,25,151,68,33,191,98,200,250,150,212,165,25,57,193,149,60,42,75,166,229,35,209,177,238,206,98,175,27,249,33,86,105,84,161,179,85,140,210,192,60,5,154,71,97,35,145,68,43,129,204,230,65,55,225,230,140,33,179,109,189,138,48,32,208,33,172,177,59,64,6,163,157,173,28,251,108,135,107,8,121,39,70,182,92,118,88,74,123,227,173,148,114,232,141,165,8,174,233,72,47,98,166,229,126,163,92,128,124,93,240,40,16,129,188,248,249,141,68,50,46,112,119,83,159,1,36,188,29,108,12,72,193,168,191,20,181,224,21,171,122,118,241,53,134,128,172,191,216,59,171,163,169,80,167,131,162,77,124,75,148,14,173,54,172,186,138,121,182,62,79,166,255,80,64,84,10,35,159,137,202,138,66,88,88,38,13,89,3,158,80,192,80,50,47,239,72,206,116,135,12,251,40,25,201,123,163,67,100,13,105,51,174,34,34,93,3,115,92,192,106,231,63,31,87,218,156,115,83,1,188,172,152,212,206,133,105,183,160,198,113,51,32,213,29,130,254,173,239,245,104,126,255,101,127,115,186,56,236,11,217,178,248,114,215,168,163,134,209,19,244,68,229,206,194,29,94,160,221,137,217,92,17,83,227,202,123,155,151,121,220,191,199,202,241,205,234,70,164,104,185,211,96,128,163,65,230,28,4,63,219,134,230,237,183,181,229,109,41,192,38,48,3,205,3,69,15,27,85,41,249,58,60,229,132,70,199,19,39,247,213,215,10,64,234,230,209,250,91,21,134,94,143,43,27,92,64,200,3,206,207,147,130,194,137,110,114,158,192,62,200,9,173,197,36,195,222,90,68,139,0,117,232,231,62,93,112,14,37,53,86,17,150,199,31,117,20,200,185,21,58,0,193,20,229,210,41,161,119,53,225,179,50,59,120,212,104,243,159,148,20,197,167,106,194,135,192,98,133,68,210,210,227,227,161,183,180,36,62,92,189,50,39,122,5,177,246,255,185,32,70,105,173,233,213,61,174,111,102,212,174,40,98,96,113,237,146,163,30,186,69,139,67,157,37,83,30,161,170,92,20,88,126,31,148,49,32,133,35,206,117,26,155,154,102,119,184,196,144,146,104,188,112,247,229,124,82,237,146,81,215,157,134,60,215,44,249,56,157,202,243,236,105,6,130,117,190,131,93,11,78,190,251,41,176,233,229,147,70,59,56,145,165,174,147,111,242,160,241,201,203,118,88,132,232,178,22,117,168,133,225,76,86,240,4,246,158,51,191,121,245,52,164,72,112,109,131,246,21,37,123,166,143,107,76,69,128,184,87,200,23,39,48,21,138,248,161,140,8,151,174,127,172,55,217,205,164,191,41,40,21,65,102,38,231,245,193,137,82,79,200,219,185,83,168,181,87,125,247,47,112,61,195,99,115,20,131,186,193,11,9,106,72,43,175,52,216,11,212,247,27,151,35,162,53,237,20,208,201,47,218,30,26,86,4,185,26,142,255,250,59,219,212,222,152,155,131,117,49,122,51,2,78,187,225,215,90,99,196,255,71,7,19,218,205,190,132,213,178,193,114,94,81,104,244,138,51,254,116,38,23,73,190,214,169,31,220,41,172,172,205,57,159,93,237,199,135,219,63,101,42,222,90,192,55,33,98,171,182,18,47,63,214,206,6,76,251,28,178,150,174,154,63,127,87,170,172,107,230,55,12,253,165,15,70,55,191,80,114,158,203,222,55,160,212,2,147,59,21,136,61,126,64,193,123,132,221,255,44,162,133,94,13,143,204,9,115,165,105,81,111,100,16,20,44,80,204,209,171,181,144,67,195,225,253,33,209,171,193,155,183,25,142,150,232,170,231,94,127,241,58,4,97,162,46,174,96,95,35,15,98,225,46,228,222,216,179,230,92,153,238,220,253,247,20,57,60,168,242,236,186,70,34,78,184,1,123,95,111,107,91,156,13,3,71,10,125,232,250,215,249,13,223,48,132,48,156,61,129,121,46,148,222,129,228,3,189,217,19,25,6,29,125,180,13,205,152,222,79,198,73,205,97,155,156,229,15,195,235,128,254,9,137,30,63,146,187,223,104,18,14,16,87,252,21,24,8,109,65,199,250,20,169,27,205,213,146,211,166,109,129,203,224,196,235,198,154,251,215,11,228,122,121,254,118,28,97,98,132,36,48,194,97,45,127,162,252,193,144,38,51,235,54,12,75,128,236,76,100,184,5,223,113,92,189,7,229,144,154,126,214,210,14,217,255,3,240,73,227,232,82,220,33,93,55,112,7,32,76,178,247,134,245,50,157,101,68,55,237,191,114,53,220,235,56,124,190,163,8,192,216,1,26,101,153,181,14,174,1,213,255,111,61,46,65,109,153,124,203,111,65,13,206,176,115,10,245,43,152,144,152,119,211,90,150,27,127,253,111,51,198,47,247,9,224,245,233,188,100,81,18,83,249,180,142,84,249,109,215,178,75,102,203,163,250,154,185,204,231,27,239,194,79,230,120,16,249,205,89,189,108,124,23,74,196,171,118,88,103,14,22,9,250,115,195,156,95,122,201,54,2,16,21,23,8,89,182,241,168,220,85,132,206,169,166,61,26,8,208,0,235,19,187,18,27,197,95,7,147,13,48,151,71,243,218,70,3,189,109,122,228,228,24,168,225,138,111,120,81,184,200,93,61,118,227,105,174,242,24,113,22,201,161,12,120,202,28,112,5,43,46,172,168,46,226,229,44,48,20,91,10,59,228,230,236,190,97,0,217,185,27,113,192,7,100,14,89,93,107,66,95,179,55,124,165,105,168,237,38,3,204,188,169,189,35,154,233,228,249,76,132,68,255,95,118,181,182,132,234,105,157,178,176,132,195,183,64,62,170,40,20,135,99,6,238,75,241,135,101,245,19,28,239,79,117,251,11,161,103,148,230,156,97,82,245,248,36,29,77,232,24,61,115,206,10,85,185,234,141,140,183,158,86,215,114,111,36,207,92,252,210,111,241,157,99,97,115,199,170,90,232,213,30,185,191,109,54,216,177,170,225,232,90,48,170,58,215,86,94,202,54,39,191,150,47,189,120,234,161,59,91,219,95,38,160,190,181,2,118,54,254,2,89,50,63,108,80,84,9,115,125,140,243,178,117,100,5,38,237,219,200,62,254,182,2,127,93,146,47,79,13,150,88,221,178,75,232,71,43,46,81,92,163,53,180,207,202,243,40,229,120,166,221,150,6,20,155,26,22,24,92,163,85,117,214,210,191,112,30,227,92,77,38,111,245,111,152,252,10,35,129,158,153,153,20,150,170,209,86,177,142,81,169,6,55,32,170,218,25,23,47,109,30,135,107,196,169,200,79,178,229,12,27,72,239,20,61,227,166,234,174,86,177,184,197,72,161,19,162,9,20,149,29,178,171,11,75,218,23,33,14,131,126,156,122,48,129,25,221,92,199,2,61,248,38,79,17,27,76,70,170,222,166,233,192,96,154,178,148,64,21,136,4,86,222,208,52,174,19,163,74,215,130,27,118,18,129,120,192,188,202,75,171,253,246,36,163,136,10,247,100,10,119,218,67,158,103,253,48,145,245,89,110,244,154,197,9,238,221,133,47,78,160,159,204,225,4,169,209,223,136,127,179,234,176,178,32,244,109,158,123,117,143,142,122,200,37,130,29,87,226,67,209,10,52,142,29,49,199,189,107,161,95,142,101,182,230,0,3,181,31,212,100,211,194,68,88,239,122,38,199,231,62,143,195,76,198,217,49,152,198,98,169,6,111,161,208,229,37,157,26,175,58,235,194,12,130,208,47,58,96,40,177,162,78,24,167,121,80,70,17,255,86,54,223,60,120,243,107,155,52,51,131,168,140,230,94,207,167,67,162,146,199,15,19,23,147,144,161,222,118,19,234,109,214,155,142,65,191,43,26,239,28,102,167,172,222,237,26,148,36,49,105,211,242,73,60,84,80,26,221,208,83,138,48,87,250,120,37,33,218,171,246,97,171,6,247,86,59,64,65,8,160,226,204,127,68,59,77,12,12,224,140,114,52,166,11,25,44,233,41,5,3,182,252,41,105,251,215,25,153,44,189,144,150,73,1,142,202,175,232,240,68,218,90,200,93,130,163,19,144,168,137,22,244,71,172,30,111,151,230,127,28,105,156,154,140,249,220,201,203,136,93,28,117,47,165,141,138,122,248,160,217,119,113,167,209,245,182,1,252,150,25,54,36,62,194,197,136,4,190,12,126,29,230,234,246,252,254,36,99,6,237,175,42,95,94,164,0,220,157,137,123,47,38,152,42,140,245,31,54,38,211,14,178,164,2,211,234,250,121,117,69,98,190,78,20,238,181,194,96,144,209,138,137,232,226,205,225,115,24,57,129,221,89,191,35,96,149,25,151,73,139,213,145,7,162,175,53,142,12,248,115,235,28,27,222,249,253,184,233,157,176,24,138,48,219,6,132,62,41,213,69,73,110,37,72,178,91,57,202,151,2,180,18,216,169,25,181,105,47,144,143,2,187,101,193,163,107,54,42,18,254,14,108,234,166,29,33,219,251,212,119,76,247,204,242,159,49,224,204,189,75,30,135,183,182,147,252,14,187,185,86,6,228,160,111,45,122,79,195,199,30,106,201,237,247,201,72,29,186,226,152,54,181,133,122,118,173,45,197,146,66,247,83,25,176,166,117,207,96,149,25,198,175,41,154,59,109,225,184,107,236,150,25,34,202,13,43,92,96,249,85,73,54,22,172,123,6,174,149,76,59,19,63,195,115,254,12,143,253,116,104,204,9,75,118,200,248,23,178,80,193,61,48,128,232,47,156,18,18,158,148,238,118,11,167,70,41,112,70,48,178,1,174,82,110,22,16,245,110,154,81,216,72,211,37,132,72,185,141,157,102,134,78,205,218,30,109,59,236,102,25,38,221,165,122,246,131,120,66,137,225,50,184,190,74,195,26,32,155,162,132,142,156,141,32,2,209,213,139,198,59,168,211,168,199,46,189,74,203,219,127,108,221,43,124,185,228,187,250,239,195,8,221,31,84,161,24,80,133,244,168,50,199,166,173,233,92,225,38,1,54,145,7,160,122,105,0,32,234,2,89,146,61,78,132,101,85,31,164,226,151,123,53,197,167,56,212,124,226,220,252,7,161,98,26,46,140,40,136,42,158,224,188,172,96,245,228,231,224,19,99,127,123,162,38,111,213,4,169,9,38,253,178,208,83,237,62,224,131,82,90,104,235,13,88,191,215,199,182,210,225,25,64,216,125,102,252,136,181,125,116,52,11,149,169,37,139,183,73,178,160,210,184,143,164,202,70,104,52,141,189,138,2,105,15,214,66,15,180,136,205,72,233,248,103,141,34,179,176,190,125,226,194,107,143,147,164,226,90,12,17,135,137,31,15,146,15,78,151,177,88,61,19,157,195,159,16,1,144,109,100,25,169,117,64,189,36,176,62,18,199,120,137,164,82,192,209,137,235,200,110,117,175,96,198,31,20,161,65,21,222,118,227,83,116,130,47,46,29,77,101,152,18,250,150,181,146,187,69,168,41,231,203,39,100,60,10,171,117,166,25,196,223,139,175,135,160,165,105,80,177,67,238,254,178,160,71,28,52,19,182,119,243,6,138,208,97,27,67,219,113,216,204,202,205,248,201,87,118,153,109,114,73,143,254,2,242,154,38,46,102,225,149,144,52,199,233,250,158,219,149,126,61,148,56,5,192,133,20,207,187,123,188,13,103,205,254,252,168,177,233,227,201,226,244,110,112,157,146,184,209,213,57,222,245,174,112,135,212,136,219,201,165,19,83,42,252,113,219,24,136,72,17,243,212,100,222,153,200,61,81,236,19,226,122,18,18,186,90,31,101,103,96,250,243,52,123,4,252,143,86,100,29,235,253,116,35,103,161,211,45,146,97,128,89,98,232,29,89,4,210,177,153,51,113,37,130,194,69,110,100,70,128,12,0,0,252,28,232,94,154,58,84,206}; + +static const uint32_t DT_TARGET_LEN = 3216; +// detools in-place+crle delta: apply DT_IP_PATCH over a DT_IP_MEM buffer holding DT_IP_BASE +static const uint8_t DT_IP_BASE[3016] = {120,46,186,148,77,51,227,185,104,193,183,194,67,136,62,162,208,188,127,90,106,134,186,157,246,55,79,139,180,84,132,19,187,198,255,221,52,176,192,186,119,236,181,212,223,167,37,136,54,222,105,250,14,197,89,160,106,119,31,185,190,35,195,83,99,84,88,203,51,83,109,106,81,145,54,231,222,104,58,52,10,191,57,195,4,248,221,66,216,129,81,197,245,145,205,180,107,157,28,84,217,167,155,199,59,60,254,118,93,34,51,94,126,152,214,160,36,67,99,159,86,85,240,181,255,182,119,220,43,175,178,196,220,33,84,236,52,148,175,16,25,240,215,44,1,230,38,112,180,60,89,58,20,50,205,72,61,177,118,154,67,123,134,225,111,169,248,106,51,215,18,77,77,71,34,144,169,187,64,134,25,126,55,228,50,200,99,45,131,217,57,81,90,192,179,228,204,11,148,230,238,173,139,96,239,189,184,243,162,18,30,58,14,132,32,241,212,53,232,162,157,236,22,242,129,44,60,124,149,204,187,42,41,22,32,158,26,207,241,152,143,207,254,154,161,7,152,27,143,46,139,178,80,0,31,71,7,46,15,26,162,219,159,172,158,187,53,148,53,165,48,118,47,121,80,69,187,116,162,112,213,183,206,210,55,102,150,221,114,221,107,152,179,34,225,53,41,79,101,50,192,45,91,116,175,3,30,85,172,0,197,57,192,129,107,168,249,9,54,155,118,141,127,140,206,12,110,85,2,130,87,141,249,230,240,224,65,170,187,40,57,154,248,27,211,203,216,110,17,245,225,34,44,6,220,86,78,242,234,64,126,41,92,213,119,237,111,241,125,157,83,40,9,197,31,30,93,108,162,244,46,129,180,24,13,168,111,228,6,207,233,227,240,69,59,30,81,139,222,145,35,54,145,149,27,110,26,241,153,180,178,68,111,143,40,195,59,240,0,131,31,50,96,137,146,104,114,146,201,44,212,165,236,63,141,235,195,74,230,208,70,151,91,115,163,28,103,101,194,72,81,128,133,58,60,210,199,206,91,58,107,201,119,146,79,73,245,172,175,236,49,119,165,138,13,64,97,211,166,53,67,105,132,34,167,80,72,176,137,206,241,34,195,23,129,56,118,155,71,75,63,165,132,99,189,72,244,47,246,228,233,247,122,206,93,247,7,152,165,96,177,16,193,185,231,34,25,108,156,82,48,255,196,244,244,19,194,148,65,8,206,163,198,66,171,217,133,48,241,218,204,111,42,49,182,120,211,68,17,118,31,25,151,68,33,191,98,200,250,150,212,165,25,57,193,149,60,42,75,166,229,35,209,177,238,206,98,175,27,249,33,86,105,84,161,179,85,140,210,192,60,5,154,71,97,35,145,68,43,129,204,230,65,55,225,230,140,33,179,109,189,138,48,32,208,33,172,177,59,64,6,163,157,173,28,251,108,135,107,8,121,39,70,182,92,118,88,74,123,227,173,148,114,232,141,165,8,174,233,72,47,98,166,229,126,163,92,128,124,93,240,40,16,129,188,248,249,141,68,50,46,112,119,83,159,1,36,188,29,108,12,72,193,168,191,20,181,224,21,171,122,118,241,53,134,128,172,191,216,59,171,163,169,80,167,131,162,77,124,75,148,14,173,54,172,186,138,121,182,62,79,166,255,80,64,84,10,35,159,137,202,138,66,88,88,38,13,89,3,158,80,192,80,50,47,239,72,206,116,135,12,251,40,25,201,123,163,67,100,13,105,51,174,34,34,93,3,115,92,192,106,231,63,31,87,218,156,115,83,1,188,172,152,212,206,133,105,183,160,198,113,51,32,213,29,130,254,173,239,245,104,126,255,101,127,115,186,56,236,11,217,178,248,114,215,168,163,134,209,19,244,68,229,206,194,29,94,160,221,137,217,92,17,83,227,202,123,155,151,121,220,191,199,202,241,205,234,70,164,104,185,211,96,128,163,65,230,28,4,63,219,134,230,237,183,181,229,109,41,192,38,48,3,205,3,69,15,27,85,41,249,58,60,229,132,70,199,19,39,247,213,215,10,64,234,230,209,250,91,21,134,94,143,43,27,92,64,200,3,206,207,147,130,194,137,110,114,158,192,62,200,9,173,197,36,195,222,90,68,139,0,117,232,231,62,93,112,14,37,53,86,17,150,199,31,117,20,200,185,21,58,0,193,20,229,210,41,161,119,53,225,179,50,59,120,212,104,243,159,148,20,197,167,106,194,135,192,98,133,68,210,210,227,227,161,183,180,36,62,92,189,50,39,122,5,177,246,255,185,32,70,105,173,233,213,61,174,111,102,212,174,40,98,96,113,237,146,163,30,186,69,139,67,157,37,83,30,161,170,92,20,88,126,31,148,49,32,133,35,206,117,26,155,154,102,119,184,196,144,146,104,188,112,247,229,124,82,237,146,81,215,157,134,60,215,44,249,56,157,202,243,236,105,6,130,117,190,131,93,11,78,190,251,41,176,233,229,147,70,59,56,145,165,174,147,111,242,160,241,201,203,118,88,132,232,178,22,117,168,133,225,76,86,240,4,246,158,51,191,121,245,52,164,72,112,109,131,246,21,37,123,166,143,107,76,69,128,184,87,200,23,39,48,21,138,248,161,140,8,151,174,127,172,55,217,205,164,191,41,40,21,65,102,38,231,245,193,137,82,79,200,219,185,83,168,181,87,125,247,47,112,61,195,99,115,20,131,186,193,11,9,106,72,43,175,52,216,11,212,247,27,151,35,162,53,237,20,208,201,47,218,30,26,86,4,185,26,142,255,250,59,219,212,222,152,155,131,117,49,122,51,2,78,187,225,215,90,99,196,255,71,7,19,218,205,190,132,213,178,193,114,94,81,104,244,138,51,254,116,38,23,73,190,214,169,31,220,41,172,172,205,57,159,93,237,199,135,219,63,101,42,222,90,192,55,33,98,171,182,18,47,63,214,206,6,76,251,28,178,150,174,154,63,127,87,170,172,107,230,55,12,253,165,15,70,55,191,80,114,158,203,222,55,160,212,2,147,59,21,136,61,126,64,193,123,132,221,255,44,162,133,94,13,143,204,9,115,165,105,81,111,100,16,20,44,80,204,209,171,181,144,67,195,225,253,33,209,171,193,155,183,25,142,150,232,170,231,94,127,241,58,4,97,162,46,174,96,95,35,15,98,225,46,228,222,216,179,230,92,153,238,220,253,247,20,57,60,168,242,236,186,70,34,78,184,1,123,95,111,107,91,156,13,3,71,10,125,232,250,215,249,13,223,48,132,48,156,61,129,121,46,148,222,129,228,3,189,217,19,25,6,29,125,180,13,205,152,222,79,198,73,205,97,155,156,229,15,195,235,128,254,9,137,30,63,146,187,223,104,18,14,16,87,252,21,24,8,109,65,199,160,20,169,27,205,213,146,211,166,109,129,203,224,196,235,198,154,251,215,11,228,122,121,254,118,28,97,98,132,36,48,194,97,45,127,162,252,193,144,38,51,235,54,12,75,128,236,76,100,184,5,223,113,92,189,7,229,144,154,126,214,210,14,217,255,3,240,73,227,232,82,220,33,93,55,112,7,32,76,178,247,134,245,50,157,101,68,55,237,191,114,53,220,235,56,124,190,163,8,192,216,1,26,101,153,181,14,174,1,213,255,111,61,46,65,109,153,124,203,111,65,13,206,176,115,10,245,43,152,144,152,119,211,90,150,27,127,253,111,51,198,47,247,9,224,245,233,188,100,81,18,83,249,180,142,84,249,109,215,178,75,102,203,163,250,154,185,204,231,27,239,194,79,230,120,16,249,205,89,189,108,124,23,74,196,171,118,88,103,14,22,9,250,115,195,156,95,122,201,54,2,16,21,23,8,89,182,241,168,220,85,132,206,169,166,61,26,8,208,0,235,19,187,18,27,197,95,7,147,13,48,151,71,243,218,70,3,189,109,122,228,228,24,168,225,138,111,120,81,184,200,93,61,118,227,105,174,242,24,113,22,201,161,12,120,202,28,112,5,43,46,172,168,46,226,229,44,48,20,91,10,59,228,230,236,190,97,0,217,185,27,113,192,7,100,14,89,93,107,66,95,179,55,124,165,105,168,237,38,3,204,188,169,189,35,154,233,228,249,76,132,68,255,95,118,181,182,132,234,105,157,178,176,132,195,183,64,62,170,40,20,135,99,6,238,75,241,135,101,245,19,28,239,79,117,251,11,161,103,148,230,156,97,82,245,248,36,29,77,232,24,61,115,206,10,85,185,234,141,140,183,158,86,215,114,111,36,207,92,252,210,111,241,157,99,97,115,199,170,90,232,213,30,185,191,109,54,216,177,170,225,232,90,48,170,58,215,86,94,202,54,39,191,150,47,189,120,234,161,59,91,219,95,38,160,190,181,2,118,54,254,2,89,50,63,108,80,84,9,115,125,140,243,178,117,100,5,38,237,219,200,62,254,182,2,127,93,146,47,79,13,150,88,221,178,75,232,71,43,46,81,92,163,53,180,207,202,243,40,229,120,166,221,150,6,20,155,26,22,24,92,163,85,117,214,210,191,112,30,227,92,77,38,111,245,111,152,252,10,35,129,158,153,153,20,150,170,209,86,177,142,81,169,6,55,32,170,218,25,23,47,109,30,135,107,196,169,200,79,178,229,12,27,72,239,20,61,227,166,234,174,86,177,184,197,72,161,19,162,9,20,149,29,178,171,11,75,218,23,33,14,131,126,156,122,48,129,25,221,92,199,2,61,248,38,79,17,27,76,70,170,222,166,233,192,96,154,178,148,64,21,136,4,86,222,208,52,174,19,163,74,215,130,27,118,18,129,120,192,188,202,75,171,253,246,36,163,136,10,247,100,10,119,218,67,158,103,253,48,145,245,89,110,244,154,197,9,238,221,133,47,78,160,159,204,225,4,169,209,223,136,127,179,234,176,178,32,244,109,158,123,117,143,142,122,200,37,130,29,87,226,67,209,10,52,142,29,49,199,189,107,161,95,142,101,182,230,0,3,181,31,212,100,211,194,68,88,239,122,38,199,231,62,143,195,76,198,217,49,152,198,98,169,6,111,161,208,229,37,157,26,175,58,235,194,12,130,208,47,58,96,40,177,162,78,24,167,121,80,70,17,255,86,54,223,60,120,243,107,155,52,51,131,168,140,230,94,207,167,67,162,146,199,15,19,23,147,144,161,222,118,19,234,109,214,155,142,65,191,43,26,239,28,102,167,172,222,237,26,148,36,49,105,211,242,73,60,84,80,26,221,208,83,138,48,87,250,120,37,33,218,171,246,97,171,6,247,86,59,64,65,8,160,226,204,127,68,59,77,12,12,224,140,114,52,166,11,25,44,233,41,5,3,182,252,41,105,251,215,25,153,44,189,144,150,73,1,142,202,175,232,240,68,218,90,200,93,130,163,19,144,168,137,22,244,71,172,30,111,151,230,127,28,105,156,154,140,249,220,201,203,136,93,28,117,47,165,141,138,122,248,160,217,119,113,167,209,245,182,1,252,150,25,54,36,62,194,197,136,4,190,12,126,29,230,234,246,252,254,36,99,6,237,175,42,95,94,164,0,220,157,137,123,47,38,152,42,140,245,31,54,38,211,14,178,164,2,211,234,250,121,117,69,98,190,78,20,238,181,194,96,144,209,138,137,232,226,205,225,115,24,57,129,221,89,191,35,96,149,25,151,73,139,213,145,7,162,175,53,142,12,248,115,235,28,27,222,249,253,184,233,157,176,24,138,48,219,6,132,62,41,213,69,73,110,37,72,178,91,57,202,151,2,180,18,216,169,25,181,105,47,144,143,2,187,101,193,163,107,54,42,18,254,14,108,234,166,29,33,219,251,212,119,76,247,204,242,159,49,224,204,189,75,30,135,183,182,147,252,14,187,185,86,6,228,160,111,45,122,79,195,199,30,106,201,237,247,201,72,29,186,226,152,54,181,133,122,118,173,45,197,146,66,247,83,25,176,166,117,207,96,149,25,198,175,41,154,59,109,225,184,107,236,150,25,34,202,13,43,92,96,249,85,73,54,22,172,123,6,174,149,76,59,19,63,195,115,254,12,143,253,116,104,204,9,75,118,200,248,23,178,80,193,61,48,128,232,47,156,18,18,158,148,238,118,11,167,70,41,112,70,48,178,1,174,82,110,22,16,245,110,154,81,216,72,211,37,132,72,185,141,157,102,134,78,205,218,30,109,59,236,102,25,38,221,165,122,246,131,120,66,137,225,50,184,190,74,195,26,32,155,162,132,142,156,141,32,2,209,213,139,198,59,168,211,168,199,46,189,74,203,219,127,108,221,43,124,185,228,187,250,239,195,8,221,31,84,161,24,80,133,244,168,50,199,166,173,233,92,225,38,1,54,145,7,160,122,105,0,32,234,2,89,146,61,78,132,101,85,31,164,226,151,123,53,197,167,56,212,124,226,220,252,7,161,98,26,46,140,40,136,42,158,224,188,172,96,245,228,231,224,19,99,127,123,162,38,111,213,4,169,9,38,253,178,208,83,237,62,224,131,82,90,104,235,13,88,191,215,199,182,210,225,25,64,216,125,102,252,136,181,125,116,52,11,149,169,37,139,183,73,178,160,210,184,143,164,202,70,104,52,141,189,138,2,105,15,214,66,15,180,136,205,72,233,248,103,141,34,179,176,190,125,226,194,107,143,147,164,226,90,12,17,135,137,31,15,146,15,78,151,177,88,61,19,157,195,159,16,1,144,109,100,25,169,117,64,189,36,176,62,18,199,120,137,164,82,192,209,137,235,200,110,117,175,96,198,31,20,251,69,110,100,70,184,11,0,0,206,99,32,159,72,64,138,233}; + +static const uint32_t DT_IP_BASE_LEN = 3016; +static const uint8_t DT_IP_PATCH[262] = {18,128,128,4,128,64,128,192,3,136,47,144,50,0,3,0,183,46,1,137,1,0,0,3,58,70,58,1,208,10,0,0,1,90,1,218,11,0,0,221,1,153,3,161,65,21,222,118,227,83,116,130,47,46,29,77,101,152,18,250,150,181,146,187,69,168,41,231,203,39,100,60,10,171,117,166,25,196,223,139,175,135,160,165,105,80,177,67,238,254,178,160,71,28,52,19,182,119,243,6,138,208,97,27,67,219,113,216,204,202,205,248,201,87,118,153,109,114,73,143,254,2,242,154,38,46,102,225,149,144,52,199,233,250,158,219,149,126,61,148,56,5,192,133,20,207,187,123,188,13,103,205,254,252,168,177,233,227,201,226,244,110,112,157,146,184,209,213,57,222,245,174,112,135,212,136,219,201,165,19,83,42,252,113,219,24,136,72,17,243,212,100,222,153,200,61,81,236,19,226,122,18,18,186,90,31,101,103,96,250,243,52,123,4,252,143,86,100,29,235,253,116,35,103,161,211,45,146,97,128,89,98,232,29,89,4,210,177,153,51,113,37,130,194,69,110,100,70,128,12,0,0,252,28,232,94,154,58,84,206,224,26}; + +static const uint32_t DT_IP_PATCH_LEN = 262; +static const uint8_t DT_IP_TARGET[3216] = {120,46,186,148,77,51,227,185,104,193,183,194,67,136,62,162,208,188,127,90,106,134,186,157,246,55,79,139,180,84,132,19,187,198,255,221,52,176,192,186,119,236,181,212,223,167,37,136,54,222,105,250,14,197,89,160,106,119,31,185,190,35,195,83,99,84,88,203,51,83,109,106,81,145,54,231,222,104,58,52,10,191,57,195,4,248,221,66,216,129,81,197,245,145,205,180,107,157,28,84,217,167,155,199,59,60,254,118,93,34,51,94,126,152,214,160,36,67,99,159,86,85,240,181,255,182,119,220,43,175,178,196,220,33,84,236,52,206,245,74,25,240,215,44,1,230,38,112,180,60,89,58,20,50,205,72,61,177,118,154,67,123,134,225,111,169,248,106,51,215,18,77,77,71,34,144,169,187,64,134,25,126,55,228,50,200,99,45,131,217,57,81,90,192,179,228,204,11,148,230,238,173,139,96,239,189,184,243,162,18,30,58,14,132,32,241,212,53,232,162,157,236,22,242,129,44,60,124,149,204,187,42,41,22,32,158,26,207,241,152,143,207,254,154,161,7,152,27,143,46,139,178,80,0,31,71,7,46,15,26,162,219,159,172,158,187,53,148,53,165,48,118,47,121,80,69,187,116,162,112,213,183,206,210,55,102,150,221,114,221,107,152,179,34,225,53,41,79,101,50,192,45,91,116,175,3,30,85,172,0,197,57,192,129,107,168,249,9,54,155,118,141,127,140,206,12,110,85,2,130,87,141,249,230,240,224,65,170,187,40,57,154,248,27,211,203,216,110,17,245,225,34,44,6,220,86,78,242,234,64,126,41,92,213,119,237,111,241,125,157,83,40,9,197,31,30,93,108,162,244,46,129,180,24,13,168,111,228,6,207,233,227,240,69,59,30,81,139,222,145,35,54,145,149,27,110,26,241,153,180,178,68,111,143,40,195,59,240,0,131,31,50,96,137,146,104,114,146,201,44,212,165,236,63,141,235,195,74,230,208,70,151,91,115,163,28,103,101,194,72,81,128,133,58,60,210,199,206,91,58,107,201,119,146,79,73,245,172,175,236,49,119,165,138,13,64,97,211,166,53,67,105,132,34,167,80,72,176,137,206,241,34,195,23,129,56,118,155,71,75,63,165,132,99,189,72,244,47,246,228,233,247,122,206,93,247,7,152,165,96,177,16,193,185,231,34,25,108,156,82,48,255,196,244,244,19,194,148,65,8,206,163,198,66,171,217,133,48,241,218,204,111,42,49,182,120,211,68,17,118,31,25,151,68,33,191,98,200,250,150,212,165,25,57,193,149,60,42,75,166,229,35,209,177,238,206,98,175,27,249,33,86,105,84,161,179,85,140,210,192,60,5,154,71,97,35,145,68,43,129,204,230,65,55,225,230,140,33,179,109,189,138,48,32,208,33,172,177,59,64,6,163,157,173,28,251,108,135,107,8,121,39,70,182,92,118,88,74,123,227,173,148,114,232,141,165,8,174,233,72,47,98,166,229,126,163,92,128,124,93,240,40,16,129,188,248,249,141,68,50,46,112,119,83,159,1,36,188,29,108,12,72,193,168,191,20,181,224,21,171,122,118,241,53,134,128,172,191,216,59,171,163,169,80,167,131,162,77,124,75,148,14,173,54,172,186,138,121,182,62,79,166,255,80,64,84,10,35,159,137,202,138,66,88,88,38,13,89,3,158,80,192,80,50,47,239,72,206,116,135,12,251,40,25,201,123,163,67,100,13,105,51,174,34,34,93,3,115,92,192,106,231,63,31,87,218,156,115,83,1,188,172,152,212,206,133,105,183,160,198,113,51,32,213,29,130,254,173,239,245,104,126,255,101,127,115,186,56,236,11,217,178,248,114,215,168,163,134,209,19,244,68,229,206,194,29,94,160,221,137,217,92,17,83,227,202,123,155,151,121,220,191,199,202,241,205,234,70,164,104,185,211,96,128,163,65,230,28,4,63,219,134,230,237,183,181,229,109,41,192,38,48,3,205,3,69,15,27,85,41,249,58,60,229,132,70,199,19,39,247,213,215,10,64,234,230,209,250,91,21,134,94,143,43,27,92,64,200,3,206,207,147,130,194,137,110,114,158,192,62,200,9,173,197,36,195,222,90,68,139,0,117,232,231,62,93,112,14,37,53,86,17,150,199,31,117,20,200,185,21,58,0,193,20,229,210,41,161,119,53,225,179,50,59,120,212,104,243,159,148,20,197,167,106,194,135,192,98,133,68,210,210,227,227,161,183,180,36,62,92,189,50,39,122,5,177,246,255,185,32,70,105,173,233,213,61,174,111,102,212,174,40,98,96,113,237,146,163,30,186,69,139,67,157,37,83,30,161,170,92,20,88,126,31,148,49,32,133,35,206,117,26,155,154,102,119,184,196,144,146,104,188,112,247,229,124,82,237,146,81,215,157,134,60,215,44,249,56,157,202,243,236,105,6,130,117,190,131,93,11,78,190,251,41,176,233,229,147,70,59,56,145,165,174,147,111,242,160,241,201,203,118,88,132,232,178,22,117,168,133,225,76,86,240,4,246,158,51,191,121,245,52,164,72,112,109,131,246,21,37,123,166,143,107,76,69,128,184,87,200,23,39,48,21,138,248,161,140,8,151,174,127,172,55,217,205,164,191,41,40,21,65,102,38,231,245,193,137,82,79,200,219,185,83,168,181,87,125,247,47,112,61,195,99,115,20,131,186,193,11,9,106,72,43,175,52,216,11,212,247,27,151,35,162,53,237,20,208,201,47,218,30,26,86,4,185,26,142,255,250,59,219,212,222,152,155,131,117,49,122,51,2,78,187,225,215,90,99,196,255,71,7,19,218,205,190,132,213,178,193,114,94,81,104,244,138,51,254,116,38,23,73,190,214,169,31,220,41,172,172,205,57,159,93,237,199,135,219,63,101,42,222,90,192,55,33,98,171,182,18,47,63,214,206,6,76,251,28,178,150,174,154,63,127,87,170,172,107,230,55,12,253,165,15,70,55,191,80,114,158,203,222,55,160,212,2,147,59,21,136,61,126,64,193,123,132,221,255,44,162,133,94,13,143,204,9,115,165,105,81,111,100,16,20,44,80,204,209,171,181,144,67,195,225,253,33,209,171,193,155,183,25,142,150,232,170,231,94,127,241,58,4,97,162,46,174,96,95,35,15,98,225,46,228,222,216,179,230,92,153,238,220,253,247,20,57,60,168,242,236,186,70,34,78,184,1,123,95,111,107,91,156,13,3,71,10,125,232,250,215,249,13,223,48,132,48,156,61,129,121,46,148,222,129,228,3,189,217,19,25,6,29,125,180,13,205,152,222,79,198,73,205,97,155,156,229,15,195,235,128,254,9,137,30,63,146,187,223,104,18,14,16,87,252,21,24,8,109,65,199,250,20,169,27,205,213,146,211,166,109,129,203,224,196,235,198,154,251,215,11,228,122,121,254,118,28,97,98,132,36,48,194,97,45,127,162,252,193,144,38,51,235,54,12,75,128,236,76,100,184,5,223,113,92,189,7,229,144,154,126,214,210,14,217,255,3,240,73,227,232,82,220,33,93,55,112,7,32,76,178,247,134,245,50,157,101,68,55,237,191,114,53,220,235,56,124,190,163,8,192,216,1,26,101,153,181,14,174,1,213,255,111,61,46,65,109,153,124,203,111,65,13,206,176,115,10,245,43,152,144,152,119,211,90,150,27,127,253,111,51,198,47,247,9,224,245,233,188,100,81,18,83,249,180,142,84,249,109,215,178,75,102,203,163,250,154,185,204,231,27,239,194,79,230,120,16,249,205,89,189,108,124,23,74,196,171,118,88,103,14,22,9,250,115,195,156,95,122,201,54,2,16,21,23,8,89,182,241,168,220,85,132,206,169,166,61,26,8,208,0,235,19,187,18,27,197,95,7,147,13,48,151,71,243,218,70,3,189,109,122,228,228,24,168,225,138,111,120,81,184,200,93,61,118,227,105,174,242,24,113,22,201,161,12,120,202,28,112,5,43,46,172,168,46,226,229,44,48,20,91,10,59,228,230,236,190,97,0,217,185,27,113,192,7,100,14,89,93,107,66,95,179,55,124,165,105,168,237,38,3,204,188,169,189,35,154,233,228,249,76,132,68,255,95,118,181,182,132,234,105,157,178,176,132,195,183,64,62,170,40,20,135,99,6,238,75,241,135,101,245,19,28,239,79,117,251,11,161,103,148,230,156,97,82,245,248,36,29,77,232,24,61,115,206,10,85,185,234,141,140,183,158,86,215,114,111,36,207,92,252,210,111,241,157,99,97,115,199,170,90,232,213,30,185,191,109,54,216,177,170,225,232,90,48,170,58,215,86,94,202,54,39,191,150,47,189,120,234,161,59,91,219,95,38,160,190,181,2,118,54,254,2,89,50,63,108,80,84,9,115,125,140,243,178,117,100,5,38,237,219,200,62,254,182,2,127,93,146,47,79,13,150,88,221,178,75,232,71,43,46,81,92,163,53,180,207,202,243,40,229,120,166,221,150,6,20,155,26,22,24,92,163,85,117,214,210,191,112,30,227,92,77,38,111,245,111,152,252,10,35,129,158,153,153,20,150,170,209,86,177,142,81,169,6,55,32,170,218,25,23,47,109,30,135,107,196,169,200,79,178,229,12,27,72,239,20,61,227,166,234,174,86,177,184,197,72,161,19,162,9,20,149,29,178,171,11,75,218,23,33,14,131,126,156,122,48,129,25,221,92,199,2,61,248,38,79,17,27,76,70,170,222,166,233,192,96,154,178,148,64,21,136,4,86,222,208,52,174,19,163,74,215,130,27,118,18,129,120,192,188,202,75,171,253,246,36,163,136,10,247,100,10,119,218,67,158,103,253,48,145,245,89,110,244,154,197,9,238,221,133,47,78,160,159,204,225,4,169,209,223,136,127,179,234,176,178,32,244,109,158,123,117,143,142,122,200,37,130,29,87,226,67,209,10,52,142,29,49,199,189,107,161,95,142,101,182,230,0,3,181,31,212,100,211,194,68,88,239,122,38,199,231,62,143,195,76,198,217,49,152,198,98,169,6,111,161,208,229,37,157,26,175,58,235,194,12,130,208,47,58,96,40,177,162,78,24,167,121,80,70,17,255,86,54,223,60,120,243,107,155,52,51,131,168,140,230,94,207,167,67,162,146,199,15,19,23,147,144,161,222,118,19,234,109,214,155,142,65,191,43,26,239,28,102,167,172,222,237,26,148,36,49,105,211,242,73,60,84,80,26,221,208,83,138,48,87,250,120,37,33,218,171,246,97,171,6,247,86,59,64,65,8,160,226,204,127,68,59,77,12,12,224,140,114,52,166,11,25,44,233,41,5,3,182,252,41,105,251,215,25,153,44,189,144,150,73,1,142,202,175,232,240,68,218,90,200,93,130,163,19,144,168,137,22,244,71,172,30,111,151,230,127,28,105,156,154,140,249,220,201,203,136,93,28,117,47,165,141,138,122,248,160,217,119,113,167,209,245,182,1,252,150,25,54,36,62,194,197,136,4,190,12,126,29,230,234,246,252,254,36,99,6,237,175,42,95,94,164,0,220,157,137,123,47,38,152,42,140,245,31,54,38,211,14,178,164,2,211,234,250,121,117,69,98,190,78,20,238,181,194,96,144,209,138,137,232,226,205,225,115,24,57,129,221,89,191,35,96,149,25,151,73,139,213,145,7,162,175,53,142,12,248,115,235,28,27,222,249,253,184,233,157,176,24,138,48,219,6,132,62,41,213,69,73,110,37,72,178,91,57,202,151,2,180,18,216,169,25,181,105,47,144,143,2,187,101,193,163,107,54,42,18,254,14,108,234,166,29,33,219,251,212,119,76,247,204,242,159,49,224,204,189,75,30,135,183,182,147,252,14,187,185,86,6,228,160,111,45,122,79,195,199,30,106,201,237,247,201,72,29,186,226,152,54,181,133,122,118,173,45,197,146,66,247,83,25,176,166,117,207,96,149,25,198,175,41,154,59,109,225,184,107,236,150,25,34,202,13,43,92,96,249,85,73,54,22,172,123,6,174,149,76,59,19,63,195,115,254,12,143,253,116,104,204,9,75,118,200,248,23,178,80,193,61,48,128,232,47,156,18,18,158,148,238,118,11,167,70,41,112,70,48,178,1,174,82,110,22,16,245,110,154,81,216,72,211,37,132,72,185,141,157,102,134,78,205,218,30,109,59,236,102,25,38,221,165,122,246,131,120,66,137,225,50,184,190,74,195,26,32,155,162,132,142,156,141,32,2,209,213,139,198,59,168,211,168,199,46,189,74,203,219,127,108,221,43,124,185,228,187,250,239,195,8,221,31,84,161,24,80,133,244,168,50,199,166,173,233,92,225,38,1,54,145,7,160,122,105,0,32,234,2,89,146,61,78,132,101,85,31,164,226,151,123,53,197,167,56,212,124,226,220,252,7,161,98,26,46,140,40,136,42,158,224,188,172,96,245,228,231,224,19,99,127,123,162,38,111,213,4,169,9,38,253,178,208,83,237,62,224,131,82,90,104,235,13,88,191,215,199,182,210,225,25,64,216,125,102,252,136,181,125,116,52,11,149,169,37,139,183,73,178,160,210,184,143,164,202,70,104,52,141,189,138,2,105,15,214,66,15,180,136,205,72,233,248,103,141,34,179,176,190,125,226,194,107,143,147,164,226,90,12,17,135,137,31,15,146,15,78,151,177,88,61,19,157,195,159,16,1,144,109,100,25,169,117,64,189,36,176,62,18,199,120,137,164,82,192,209,137,235,200,110,117,175,96,198,31,20,161,65,21,222,118,227,83,116,130,47,46,29,77,101,152,18,250,150,181,146,187,69,168,41,231,203,39,100,60,10,171,117,166,25,196,223,139,175,135,160,165,105,80,177,67,238,254,178,160,71,28,52,19,182,119,243,6,138,208,97,27,67,219,113,216,204,202,205,248,201,87,118,153,109,114,73,143,254,2,242,154,38,46,102,225,149,144,52,199,233,250,158,219,149,126,61,148,56,5,192,133,20,207,187,123,188,13,103,205,254,252,168,177,233,227,201,226,244,110,112,157,146,184,209,213,57,222,245,174,112,135,212,136,219,201,165,19,83,42,252,113,219,24,136,72,17,243,212,100,222,153,200,61,81,236,19,226,122,18,18,186,90,31,101,103,96,250,243,52,123,4,252,143,86,100,29,235,253,116,35,103,161,211,45,146,97,128,89,98,232,29,89,4,210,177,153,51,113,37,130,194,69,110,100,70,128,12,0,0,252,28,232,94,154,58,84,206}; + +static const uint32_t DT_IP_TARGET_LEN = 3216; +static const uint32_t DT_IP_MEM = 32768u; diff --git a/test/test_ota/test_ota_core.cpp b/test/test_ota/test_ota_core.cpp new file mode 100644 index 00000000..d747c300 --- /dev/null +++ b/test/test_ota/test_ota_core.cpp @@ -0,0 +1,567 @@ +#include +#include +#include +#include + +#include "helpers/ota/MotaContainer.h" +#include "helpers/ota/MerkleTree.h" +#include "helpers/ota/BlockBitmap.h" +#include "helpers/ota/Multihash.h" +#include "helpers/ota/FirmwareInfo.h" +#include "helpers/ota/SignerAllowlist.h" +#include "helpers/ota/OtaStore.h" +#include "helpers/ota/OtaProtocol.h" +#include "helpers/ota/OtaManager.h" +#include "mota_vectors.h" // auto-generated by tools/mota/gen_vectors.py + +extern "C" { + #include "helpers/ota/detools/detools.h" // vendored detools 0.53.0 embeddable decoder +} + +using namespace mesh::ota; + +// Build a flashed-image layout (body || EndF) the way the host packager / build hook do. +static std::vector make_image(const std::vector& body) { + std::vector img = body; + img.insert(img.end(), ENDF_MAGIC, ENDF_MAGIC + 4); + uint32_t n = (uint32_t)body.size(); + for (int i = 0; i < 4; i++) img.push_back((uint8_t)(n >> (8 * i))); + uint8_t h[8]; mh8(h, body.data(), body.size()); + img.insert(img.end(), h, h + 8); + return img; +} + +// --- cross-check the C++ parser/merkle against the Python reference vectors ---------------- + +TEST(OtaParse, ParsesReferenceContainer) { + MotaManifest m; + ASSERT_TRUE(mota_parse(MOTA_VEC, MOTA_VEC_LEN, m)); + EXPECT_EQ(m.format_ver, MOTA_FORMAT_VER); + EXPECT_TRUE(m.is_full()); + EXPECT_FALSE(m.is_signed()); + EXPECT_EQ(m.target_id, EXP_TARGET_ID); + EXPECT_EQ(m.fw_version, EXP_FW_VERSION); + EXPECT_EQ(m.image_size, EXP_IMAGE_SIZE); + EXPECT_EQ(m.payload_size, EXP_PAYLOAD_SIZE); + EXPECT_EQ(m.block_count, EXP_BLOCK_COUNT); + EXPECT_EQ(m.block_size_log2, EXP_BLOCK_SIZE_LOG2); + EXPECT_EQ(m.codec_id, EXP_CODEC_ID); + EXPECT_EQ(0, memcmp(m.merkle_root, EXP_MERKLE_ROOT, 4)); + EXPECT_EQ(0, memcmp(m.image_hash, EXP_IMAGE_HASH, 32)); + EXPECT_EQ(0, memcmp(m.approval, APPROVAL_NOT, 4)); // distributed = not approved + EXPECT_FALSE(m.is_approved()); +} + +TEST(OtaParse, RejectsTampering) { + MotaManifest m; + // bad magic + std::vector b(MOTA_VEC, MOTA_VEC + MOTA_VEC_LEN); + b[0] ^= 0xFF; + EXPECT_FALSE(mota_parse(b.data(), b.size(), m)); + // bad trailer + b.assign(MOTA_VEC, MOTA_VEC + MOTA_VEC_LEN); + b[b.size() - 1] ^= 0xFF; + EXPECT_FALSE(mota_parse(b.data(), b.size(), m)); + // wrong total-size field + b.assign(MOTA_VEC, MOTA_VEC + MOTA_VEC_LEN); + b[4] ^= 0x01; + EXPECT_FALSE(mota_parse(b.data(), b.size(), m)); +} + +TEST(OtaMerkle, RootMatchesVectorAndLeaves) { + MotaManifest m; + ASSERT_TRUE(mota_parse(MOTA_VEC, MOTA_VEC_LEN, m)); + // root recomputed from stored leaves[] == merkle_root field == Python's EXP_MERKLE_ROOT + uint8_t root[4]; + merkle_root(root, m.leaves, m.block_count); + EXPECT_EQ(0, memcmp(root, EXP_MERKLE_ROOT, 4)); + EXPECT_TRUE(mota_check_root(m)); + // recompute each leaf from the payload block and compare to the stored leaf + uint32_t bs = m.block_size(); + for (uint32_t i = 0; i < m.block_count; i++) { + uint32_t off = i * bs; + uint32_t len = (off + bs <= m.payload_size) ? bs : (m.payload_size - off); + uint8_t leaf[4]; + merkle_leaf(leaf, m.payload + off, len); + EXPECT_EQ(0, memcmp(leaf, m.leaves + i * 4, 4)) << "leaf " << i; + } +} + +TEST(OtaMerkle, FullImageHashMatches) { + MotaManifest m; + ASSERT_TRUE(mota_parse(MOTA_VEC, MOTA_VEC_LEN, m)); + EXPECT_TRUE(mota_check_image_hash_full(m)); +} + +TEST(OtaMerkle, ProofFromReferenceVerifies) { + MotaManifest m; + ASSERT_TRUE(mota_parse(MOTA_VEC, MOTA_VEC_LEN, m)); + uint32_t bs = m.block_size(); + uint32_t off = PROOF_INDEX * bs; + uint32_t len = (off + bs <= m.payload_size) ? bs : (m.payload_size - off); + + EXPECT_TRUE(merkle_verify(m.payload + off, len, PROOF_INDEX, + PROOF_SIBLINGS, PROOF_NSIB, EXP_MERKLE_ROOT, m.block_count)); + + // tampered block -> fails + std::vector blk(m.payload + off, m.payload + off + len); + blk[0] ^= 0xFF; + EXPECT_FALSE(merkle_verify(blk.data(), len, PROOF_INDEX, + PROOF_SIBLINGS, PROOF_NSIB, EXP_MERKLE_ROOT, m.block_count)); + + // wrong index with the same proof -> fails + EXPECT_FALSE(merkle_verify(m.payload + off, len, PROOF_INDEX + 1, + PROOF_SIBLINGS, PROOF_NSIB, EXP_MERKLE_ROOT, m.block_count)); +} + +// --- validate the O(log n) binary-counter root vs a plain level-by-level reference ---------- + +static void ref_root(uint8_t out[4], std::vector> level) { + while (level.size() > 1) { + std::vector> nxt; + for (size_t i = 0; i < level.size(); i += 2) { + if (i + 1 < level.size()) { + std::array p; + merkle_combine(p.data(), level[i].data(), level[i + 1].data()); + nxt.push_back(p); + } else { + nxt.push_back(level[i]); // promote lone last node + } + } + level.swap(nxt); + } + std::memcpy(out, level[0].data(), 4); +} + +TEST(OtaMerkle, BinaryCounterMatchesLevelByLevel) { + uint32_t state = 0x12345678; + auto rnd = [&]() { state = state * 1103515245u + 12345u; return (uint8_t)(state >> 16); }; + // O(log n) root must equal the plain level-by-level root for every count + for (uint32_t count = 1; count <= 600; count++) { + std::vector leaves(count * 4); + std::vector> ref(count); + for (uint32_t i = 0; i < count; i++) + for (int j = 0; j < 4; j++) { uint8_t v = rnd(); leaves[i * 4 + j] = v; ref[i][j] = v; } + uint8_t a[4], b[4]; + merkle_root(a, leaves.data(), count); + ref_root(b, ref); + ASSERT_EQ(0, std::memcmp(a, b, 4)) << "root mismatch count=" << count; + } +} + +// Verify every block's proof for several tricky counts, using proofs generated by the Python +// reference (the oracle) — covers deep promotion chains (100, 255, 256, ...). +TEST(OtaMerkle, ReferenceProofsAllIndices) { + for (int c = 0; c < N_PROOF_CASES; c++) { + const ProofCase& pc = PROOF_CASES[c]; + uint8_t root[4]; + merkle_root(root, pc.leaves, pc.count); + EXPECT_EQ(0, std::memcmp(root, pc.root, 4)) << "root mismatch count=" << pc.count; + for (uint32_t i = 0; i < pc.count; i++) { + EXPECT_TRUE(merkle_verify_from_leaf(pc.leaves + i * 4, i, + pc.pblob + pc.poff[i], pc.pnsib[i], pc.root, pc.count)) + << "count=" << pc.count << " idx=" << i; + } + // a wrong sibling for index 0 must fail + if (pc.pnsib[0] > 0) { + std::vector bad(pc.pblob + pc.poff[0], pc.pblob + pc.poff[0] + pc.pnsib[0] * 4); + bad[0] ^= 0xFF; + EXPECT_FALSE(merkle_verify_from_leaf(pc.leaves, 0, bad.data(), pc.pnsib[0], pc.root, pc.count)); + } + } +} + +// --- availability bitmap (derived from leaves[]) ------------------------------------------- + +TEST(OtaBitmap, AllPresentForCompleteContainer) { + MotaManifest m; + ASSERT_TRUE(mota_parse(MOTA_VEC, MOTA_VEC_LEN, m)); + EXPECT_TRUE(all_present(m.leaves, m.block_count)); + EXPECT_EQ(count_present(m.leaves, m.block_count), m.block_count); + + // a leaf slot of all-FF (erased) means "missing"; bitmap round-trips + std::vector leaves(m.leaves, m.leaves + m.block_count * 4); + std::memset(&leaves[4], 0xFF, 4); // mark block 1 missing + EXPECT_FALSE(leaf_present(leaves.data(), 1)); + EXPECT_FALSE(all_present(leaves.data(), m.block_count)); + EXPECT_EQ(count_present(leaves.data(), m.block_count), m.block_count - 1); + + std::vector bm(bitmap_bytes(m.block_count)); + leaves_to_bitmap(leaves.data(), m.block_count, bm.data()); + EXPECT_FALSE(bitmap_get(bm.data(), 1)); + EXPECT_TRUE(bitmap_get(bm.data(), 0)); +} + +// --- EndF self-firmware scan (P2) ----------------------------------------------------------- + +TEST(OtaFirmwareInfo, FindsEndFInImage) { + std::vector body(4321); + for (size_t i = 0; i < body.size(); i++) body[i] = (uint8_t)(i * 37 + 11); + std::vector img = make_image(body); + + // simulate a flash region: image, then erased 0xFF up to the partition end + std::vector region = img; + region.resize(img.size() + 4096, 0xFF); + + SelfFwInfo fi; + ASSERT_TRUE(find_self_firmware(region.data(), (uint32_t)region.size(), fi, /*verify_body=*/true)); + EXPECT_EQ(fi.body_len, body.size()); + EXPECT_EQ(fi.image_len, img.size()); + EXPECT_EQ(fi.endf_offset, body.size()); + uint8_t h[8]; mh8(h, body.data(), body.size()); + EXPECT_EQ(0, std::memcmp(fi.body_hash, h, 8)); +} + +TEST(OtaFirmwareInfo, IgnoresStagedMotaHigherInRegion) { + // The firmware's own EndF must win even when a staged .mota (which embeds its own EndF) sits + // above it in the same region — the body_len == offset check disambiguates. + std::vector body(2000); + for (size_t i = 0; i < body.size(); i++) body[i] = (uint8_t)(i ^ 0x5A); + std::vector img = make_image(body); + + std::vector region = img; + region.resize(8192, 0xFF); // gap + // drop the reference .mota (which contains an embedded EndF in its payload) higher up + region.insert(region.end(), MOTA_VEC, MOTA_VEC + MOTA_VEC_LEN); + + SelfFwInfo fi; + ASSERT_TRUE(find_self_firmware(region.data(), (uint32_t)region.size(), fi, true)); + EXPECT_EQ(fi.endf_offset, body.size()); // found OUR firmware, not the .mota's + EXPECT_EQ(fi.body_len, body.size()); +} + +TEST(OtaFirmwareInfo, NoMarkerReturnsFalse) { + std::vector region(1000, 0xAB); + SelfFwInfo fi; + EXPECT_FALSE(find_self_firmware(region.data(), (uint32_t)region.size(), fi)); +} + +// --- signer allowlist (P3) ------------------------------------------------------------------ + +TEST(OtaAllowlist, AddContainsRemoveSerialize) { + SignerAllowlist a; + uint8_t k1[32], k2[32], k3[32]; + memset(k1, 0x11, 32); memset(k2, 0x22, 32); memset(k3, 0x33, 32); + EXPECT_FALSE(a.contains(k1)); + EXPECT_TRUE(a.add(k1)); + EXPECT_TRUE(a.add(k2)); + EXPECT_TRUE(a.add(k1)); // idempotent + EXPECT_EQ(a.count(), 2); + EXPECT_TRUE(a.contains(k1)); + EXPECT_FALSE(a.contains(k3)); + + uint8_t buf[1 + MAX_OTA_SIGNERS * 32]; + uint32_t n = a.serialize(buf, sizeof(buf)); + EXPECT_EQ(n, 1u + 2 * 32); + SignerAllowlist b; + EXPECT_TRUE(b.deserialize(buf, n)); + EXPECT_EQ(b.count(), 2); + EXPECT_TRUE(b.contains(k1) && b.contains(k2)); + + EXPECT_TRUE(a.remove(k1)); + EXPECT_EQ(a.count(), 1); + EXPECT_FALSE(a.contains(k1)); + EXPECT_TRUE(a.contains(k2)); +} + +// --- RAM store: out-of-order writes + availability via leaves[] -------------------------------- + +TEST(OtaStoreRamTest, RandomAccessAndErasedSentinel) { + OtaStoreRam<4096> s; + ASSERT_TRUE(s.begin(1000)); + EXPECT_EQ(s.staged_size(), 1000u); + uint8_t blk[8] = {1,2,3,4,5,6,7,8}; + EXPECT_TRUE(s.write(500, blk, 8)); // out-of-order offset + EXPECT_TRUE(s.write(0, blk, 8)); + EXPECT_FALSE(s.write(998, blk, 8)); // out of range + uint8_t rd[8]; + EXPECT_TRUE(s.read(500, rd, 8)); + EXPECT_EQ(0, memcmp(rd, blk, 8)); + // untouched region reads as erased 0xFF (so an unfilled leaf slot is "missing") + EXPECT_TRUE(s.read(100, rd, 8)); + for (int i = 0; i < 8; i++) EXPECT_EQ(rd[i], 0xFF); +} + +// --- merkle proof GENERATION (server side) matches the Python oracle --------------------------- + +TEST(OtaMerkle, GenProofMatchesPythonAndVerifies) { + for (int c = 0; c < N_PROOF_CASES; c++) { + const ProofCase& pc = PROOF_CASES[c]; + std::vector scratch(pc.count * 4); + uint8_t out[32 * 4]; + for (uint32_t i = 0; i < pc.count; i++) { + uint8_t n = merkle_gen_proof(pc.leaves, pc.count, i, scratch.data(), out); + ASSERT_EQ(n, pc.pnsib[i]) << "count=" << pc.count << " idx=" << i; + EXPECT_EQ(0, std::memcmp(out, pc.pblob + pc.poff[i], (size_t)n * 4)) + << "gen_proof != python count=" << pc.count << " idx=" << i; + EXPECT_TRUE(merkle_verify_from_leaf(pc.leaves + i * 4, i, out, n, pc.root, pc.count)); + } + } +} + +// --- protocol codec round-trips --------------------------------------------------------------- + +TEST(OtaProtocol, CodecRoundTrips) { + uint8_t buf[200]; + + AdvMsg adv{0x11223344, 0x02000000, {0x29,0x17,0xe4,0xf7}, MFLAG_FULL | MFLAG_SIGNED, 1, CODEC_DETOOLS_INPLACE}; + uint16_t n = encode_adv(buf, sizeof(buf), adv); + ASSERT_GT(n, 0); EXPECT_EQ(ota_msg_type(buf, n), OTA_ADV); + AdvMsg a2; ASSERT_TRUE(decode_adv(buf, n, a2)); + EXPECT_EQ(a2.target_id, adv.target_id); EXPECT_EQ(a2.fw_version, adv.fw_version); + EXPECT_EQ(0, memcmp(a2.manifest_id, adv.manifest_id, 4)); + EXPECT_EQ(a2.flags, adv.flags); EXPECT_EQ(a2.have_all, 1); + EXPECT_EQ(a2.codec_id, CODEC_DETOOLS_INPLACE); + + GetManifestMsg gm{{1,2,3,4}}; + n = encode_get_manifest(buf, sizeof(buf), gm); + GetManifestMsg g2; ASSERT_TRUE(decode_get_manifest(buf, n, g2)); + EXPECT_EQ(0, memcmp(g2.manifest_id, gm.manifest_id, 4)); + + uint8_t mbytes[40]; for (int i = 0; i < 40; i++) mbytes[i] = (uint8_t)(i + 1); + ManifestMsg mm{{9,8,7,6}, 0, 1, mbytes, 40}; + n = encode_manifest(buf, sizeof(buf), mm); + ManifestMsg m2; ASSERT_TRUE(decode_manifest(buf, n, m2)); + EXPECT_EQ(m2.frag_idx, 0); EXPECT_EQ(m2.frag_total, 1); EXPECT_EQ(m2.len, 40); + EXPECT_EQ(0, memcmp(m2.bytes, mbytes, 40)); + + ReqMsg rq{{4,3,2,1}, 7, 5}; + n = encode_req(buf, sizeof(buf), rq); + ReqMsg r2; ASSERT_TRUE(decode_req(buf, n, r2)); + EXPECT_EQ(r2.start_block, 7); EXPECT_EQ(r2.count, 5); + + uint8_t proof[12]; for (int i = 0; i < 12; i++) proof[i] = (uint8_t)(0xA0 + i); + uint8_t data[100]; for (int i = 0; i < 100; i++) data[i] = (uint8_t)(i * 3); + DataMsg dm{{0,1,2,3}, 42, 0, 1, 3, proof, data, 100}; + n = encode_data(buf, sizeof(buf), dm); + DataMsg d2; ASSERT_TRUE(decode_data(buf, n, d2)); + EXPECT_EQ(d2.block_idx, 42); EXPECT_EQ(d2.frag_idx, 0); EXPECT_EQ(d2.n_proof, 3); + EXPECT_EQ(0, memcmp(d2.proof, proof, 12)); + EXPECT_EQ(d2.data_len, 100); EXPECT_EQ(0, memcmp(d2.data, data, 100)); + + // a non-frag0 DATA carries no proof + DataMsg dm2{{0,1,2,3}, 42, 2, 6, 0, nullptr, data, 50}; + n = encode_data(buf, sizeof(buf), dm2); + DataMsg d3; ASSERT_TRUE(decode_data(buf, n, d3)); + EXPECT_EQ(d3.frag_idx, 2); EXPECT_EQ(d3.n_proof, 0); EXPECT_EQ(d3.data_len, 50); +} + +// --- full transfer simulation between two OtaManagers (P4b) ------------------------------------ + +namespace { +struct SimMsg { OtaManager* dest; std::vector bytes; }; +static std::vector g_q; +struct SendTo { OtaManager* dest; }; +static void sim_send(void* ctx, const uint8_t* msg, uint16_t len, bool /*flood*/) { + g_q.push_back({((SendTo*)ctx)->dest, std::vector(msg, msg + len)}); +} +} + +TEST(OtaTransfer, TwoManagersFullTransfer) { + g_q.clear(); + OtaManager server, client; + OtaStoreRam<4096> store; + SendTo to_client{&client}, to_server{&server}; + + server.begin(/*server's own target irrelevant for serving*/ 0, sim_send, &to_client); + client.begin(SIM_TARGET_ID, sim_send, &to_server); + client.set_fetch_store(&store); + + ASSERT_TRUE(server.serve(SIM_MOTA, SIM_MOTA_LEN)); + server.announce(); // -> client hears the ADV and starts fetching + + // drain the message bus until the client completes (event cascade does the whole transfer) + int guard = 0; + while (!g_q.empty() && guard++ < 100000) { + SimMsg m = std::move(g_q.front()); + g_q.erase(g_q.begin()); + m.dest->on_message(m.bytes.data(), (uint16_t)m.bytes.size()); + if (g_q.empty() && client.fetchState() == OtaManager::FETCHING) client.loop(); + } + + EXPECT_EQ(client.fetchState(), OtaManager::COMPLETE); + EXPECT_EQ(client.blocksHave(), client.blocksTotal()); + EXPECT_GT(client.blocksTotal(), 1u); + + // the client's reassembled container must be byte-identical to the original .mota... + ASSERT_EQ(store.staged_size(), SIM_MOTA_LEN); + EXPECT_EQ(0, std::memcmp(store.data(), SIM_MOTA, SIM_MOTA_LEN)); + + // ...and independently re-verify it parses with a matching root + image_hash + MotaManifest m; + ASSERT_TRUE(mota_parse(store.data(), store.staged_size(), m)); + EXPECT_TRUE(mota_check_root(m)); + EXPECT_TRUE(mota_check_image_hash_full(m)); +} + +TEST(OtaTransfer, ClientRejectsWrongTarget) { + g_q.clear(); + OtaManager server, client; + OtaStoreRam<4096> store; + SendTo to_client{&client}, to_server{&server}; + server.begin(0, sim_send, &to_client); + client.begin(SIM_TARGET_ID ^ 0x1u, sim_send, &to_server); // different target -> not interested + client.set_fetch_store(&store); + ASSERT_TRUE(server.serve(SIM_MOTA, SIM_MOTA_LEN)); + server.announce(); + int guard = 0; + while (!g_q.empty() && guard++ < 1000) { + SimMsg m = std::move(g_q.front()); g_q.erase(g_q.begin()); + m.dest->on_message(m.bytes.data(), (uint16_t)m.bytes.size()); + } + EXPECT_EQ(client.fetchState(), OtaManager::IDLE); // never started +} + +TEST(OtaTransfer, ManualCrossTargetFetch) { + // A node whose own target differs from the served firmware normally won't fetch (role-switch case: + // e.g. companion wanting repeater firmware). An explicit want() override lets it fetch deliberately. + g_q.clear(); + OtaManager server, client; + OtaStoreRam<4096> store; + SendTo to_client{&client}, to_server{&server}; + server.begin(0, sim_send, &to_client); + client.begin(SIM_TARGET_ID ^ 0xABCDu, sim_send, &to_server); // DIFFERENT own target + client.set_fetch_store(&store); + ASSERT_TRUE(server.serve(SIM_MOTA, SIM_MOTA_LEN)); + + // without the override: ignores the ADV (wrong target) + server.announce(); + for (int g = 0; !g_q.empty() && g < 1000; g++) { SimMsg m = std::move(g_q.front()); g_q.erase(g_q.begin()); m.dest->on_message(m.bytes.data(), (uint16_t)m.bytes.size()); } + EXPECT_EQ(client.fetchState(), OtaManager::IDLE); + + // with want(): deliberately fetch the different-target firmware to completion + client.want(SIM_TARGET_ID); + server.announce(); + int guard = 0; + while (!g_q.empty() && guard++ < 100000) { + SimMsg m = std::move(g_q.front()); g_q.erase(g_q.begin()); + m.dest->on_message(m.bytes.data(), (uint16_t)m.bytes.size()); + if (g_q.empty() && client.fetchState() == OtaManager::FETCHING) client.loop(); + } + EXPECT_EQ(client.fetchState(), OtaManager::COMPLETE); + ASSERT_EQ(store.staged_size(), SIM_MOTA_LEN); + EXPECT_EQ(0, std::memcmp(store.data(), SIM_MOTA, SIM_MOTA_LEN)); +} + +// A node must not fetch firmware it can't apply: an ADV whose codec the platform can't decode is +// rejected at ADV time (never requests the manifest). FULL + the platform's delta codec are accepted. +TEST(OtaTransfer, RejectsIncompatibleCodec) { + OtaManager client; OtaStoreRam<4096> store; + SendTo to_server{&client}; // dest unused (we only check client state) + client.begin(SIM_TARGET_ID, sim_send, &to_server); + client.set_fetch_store(&store); + client.set_apply_codec(CODEC_DETOOLS_INPLACE); // nRF52-style: accepts only full + in-place + uint8_t b[32]; + + // our target, but a SEQUENTIAL delta -> incompatible -> ignored (stays IDLE, no GET_MANIFEST) + AdvMsg seq{SIM_TARGET_ID, 0x01000000, {1,2,3,4}, 0, 1, CODEC_DETOOLS_SEQUENTIAL}; + client.on_message(b, encode_adv(b, sizeof(b), seq)); + EXPECT_EQ(client.fetchState(), OtaManager::IDLE); + EXPECT_TRUE(g_q.empty()); + + // our target, IN-PLACE delta -> compatible -> proceeds to request the manifest + AdvMsg ip{SIM_TARGET_ID, 0x01000000, {5,6,7,8}, 0, 1, CODEC_DETOOLS_INPLACE}; + client.on_message(b, encode_adv(b, sizeof(b), ip)); + EXPECT_EQ(client.fetchState(), OtaManager::WANT_MANIFEST); + g_q.clear(); +} + +// --- detools delta decode (vendored detools C decoder, CRLE-only build) ---------------------- +// Mirrors the device apply path (src/helpers/ota/OtaApply.cpp): base read via from_read/from_seek, +// patch streamed via patch_read, output written via to_write. Proves the on-device delta apply uses +// detools 0.53.0's own decoder and reproduces the exact target the host packager targeted. +namespace { +struct DTMem { + const uint8_t* base; long base_len; long base_pos; + const uint8_t* patch; long patch_len; long patch_pos; + std::vector out; +}; +int dt_from_read(void* a, uint8_t* b, size_t n) { + DTMem* c = (DTMem*)a; + if (c->base_pos < 0 || c->base_pos + (long)n > c->base_len) return -DETOOLS_IO_FAILED; + std::memcpy(b, c->base + c->base_pos, n); c->base_pos += (long)n; return DETOOLS_OK; +} +int dt_from_seek(void* a, int off) { + DTMem* c = (DTMem*)a; c->base_pos += off; + if (c->base_pos < 0 || c->base_pos > c->base_len) return -DETOOLS_IO_FAILED; + return DETOOLS_OK; +} +int dt_patch_read(void* a, uint8_t* b, size_t n) { + DTMem* c = (DTMem*)a; + if (c->patch_pos + (long)n > c->patch_len) return -DETOOLS_IO_FAILED; + std::memcpy(b, c->patch + c->patch_pos, n); c->patch_pos += (long)n; return DETOOLS_OK; +} +int dt_to_write(void* a, const uint8_t* b, size_t n) { + DTMem* c = (DTMem*)a; c->out.insert(c->out.end(), b, b + n); return DETOOLS_OK; +} + +// In-place apply over a flat memory region (models the nRF52 app workspace / the bootloader's flash). +struct DTInPlace { + std::vector mem; // [0,memory_size): base in, target out + const uint8_t* patch; long plen, ppos; int step; +}; +int ip_mem_read(void* a, void* dst, uintptr_t src, size_t n) { + DTInPlace* c = (DTInPlace*)a; if (src + n > c->mem.size()) return -DETOOLS_IO_FAILED; + std::memcpy(dst, c->mem.data() + src, n); return DETOOLS_OK; +} +int ip_mem_write(void* a, uintptr_t dst, void* src, size_t n) { + DTInPlace* c = (DTInPlace*)a; if (dst + n > c->mem.size()) return -DETOOLS_IO_FAILED; + std::memcpy(c->mem.data() + dst, src, n); return DETOOLS_OK; +} +int ip_mem_erase(void* a, uintptr_t addr, size_t n) { + DTInPlace* c = (DTInPlace*)a; if (addr + n > c->mem.size()) return -DETOOLS_IO_FAILED; + std::memset(c->mem.data() + addr, 0xFF, n); return DETOOLS_OK; +} +int ip_step_set(void* a, int s) { ((DTInPlace*)a)->step = s; return DETOOLS_OK; } +int ip_step_get(void* a, int* s) { *s = ((DTInPlace*)a)->step; return DETOOLS_OK; } +int ip_patch_read(void* a, uint8_t* b, size_t n) { + DTInPlace* c = (DTInPlace*)a; if (c->ppos + (long)n > c->plen) return -DETOOLS_IO_FAILED; + std::memcpy(b, c->patch + c->ppos, n); c->ppos += (long)n; return DETOOLS_OK; +} +} // namespace + +TEST(Detools, SequentialCrlePatchReproducesTarget) { + DTMem c{DT_BASE, (long)DT_BASE_LEN, 0, DT_PATCH, (long)DT_PATCH_LEN, 0, {}}; + int r = detools_apply_patch_callbacks(dt_from_read, dt_from_seek, dt_patch_read, + (size_t)DT_PATCH_LEN, dt_to_write, &c); + ASSERT_EQ(r, (int)DT_TARGET_LEN); // returns to-size on success + ASSERT_EQ(c.out.size(), (size_t)DT_TARGET_LEN); + EXPECT_EQ(0, std::memcmp(c.out.data(), DT_TARGET, DT_TARGET_LEN)); +} + +TEST(Detools, WrongBaseDoesNotReproduceTarget) { + // a base that differs from the one the patch was built against must NOT yield the target + std::vector bad(DT_BASE, DT_BASE + DT_BASE_LEN); + for (size_t i = 0; i < bad.size(); i += 7) bad[i] ^= 0xFF; + DTMem c{bad.data(), (long)bad.size(), 0, DT_PATCH, (long)DT_PATCH_LEN, 0, {}}; + int r = detools_apply_patch_callbacks(dt_from_read, dt_from_seek, dt_patch_read, + (size_t)DT_PATCH_LEN, dt_to_write, &c); + bool reproduced = (r == (int)DT_TARGET_LEN && c.out.size() == (size_t)DT_TARGET_LEN && + std::memcmp(c.out.data(), DT_TARGET, DT_TARGET_LEN) == 0); + EXPECT_FALSE(reproduced); // wrong base -> wrong/short output (the device then fails image_hash) +} + +TEST(Detools, TruncatedPatchFails) { + DTMem c{DT_BASE, (long)DT_BASE_LEN, 0, DT_PATCH, (long)(DT_PATCH_LEN / 2), 0, {}}; + int r = detools_apply_patch_callbacks(dt_from_read, dt_from_seek, dt_patch_read, + (size_t)(DT_PATCH_LEN / 2), dt_to_write, &c); + EXPECT_TRUE(r < 0 || c.out.size() != (size_t)DT_TARGET_LEN); +} + +// nRF52 path: the bootloader applies an in-place patch over the single app slot. Model the app +// region as a DT_IP_MEM buffer holding the base; after apply, region[0:to_size] must equal the target. +TEST(Detools, InPlaceCrlePatchReproducesTarget) { + DTInPlace c; c.mem.assign(DT_IP_MEM, 0xFF); + std::memcpy(c.mem.data(), DT_IP_BASE, DT_IP_BASE_LEN); // base loaded at offset 0 + c.patch = DT_IP_PATCH; c.plen = DT_IP_PATCH_LEN; c.ppos = 0; c.step = 0; + int r = detools_apply_patch_in_place_callbacks(ip_mem_read, ip_mem_write, ip_mem_erase, + ip_step_set, ip_step_get, ip_patch_read, + (size_t)DT_IP_PATCH_LEN, &c); + ASSERT_EQ(r, (int)DT_IP_TARGET_LEN); // returns to-size on success + EXPECT_EQ(0, std::memcmp(c.mem.data(), DT_IP_TARGET, DT_IP_TARGET_LEN)); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tools/mota/README.md b/tools/mota/README.md new file mode 100644 index 00000000..2720f59e --- /dev/null +++ b/tools/mota/README.md @@ -0,0 +1,80 @@ +# `mota` — MeshCore OTA packaging tool + +Host-side tooling for building and validating `.mota` firmware-update containers. +Implements the wire spec in [`docs/ota_protocol.md`](../../docs/ota_protocol.md) (v1). + +Part of the OTA-over-LoRa work — see `OTA_PLAN.md` (milestone **P0**). + +## Setup + +Uses the repo's Python venv (`meshcore/`). Dependencies: `detools` (delta), `cryptography` (Ed25519). + +```bash +./meshcore/bin/pip install detools cryptography +``` + +## Files + +| File | What | +|---|---| +| `motalib.py` | Core logic: multihash, EndF, merkle tree+proofs, manifest/container build+parse+verify. No CLI; unit-tested; the reference implementation of the spec. | +| `mota.py` | CLI: `keygen` / `build` / `inspect` / `verify`. | +| `endf.py` | Standalone `EndF` trailer injector (idempotent). | +| `pio_endf.py` | PlatformIO post-build hook to inject `EndF` (gated on `-D ENABLE_OTA`). | +| `test_mota.py` | Tests (run directly or via pytest). | + +## Usage + +```bash +PY=./meshcore/bin/python + +# 1. one-time: generate a signing keypair (raw 32-byte hex) +$PY tools/mota/mota.py keygen --out-priv signer.priv + +# 2a. full image (e.g. ESP32 A/B) — payload IS the flashable image +$PY tools/mota/mota.py build \ + --fw firmware.bin --target-id 0x11223344 --fw-version 1.16.0 \ + --codec full --sign signer.priv --out fw_v1.16.0_full.mota + +# 2b. delta against a previous release (e.g. RAK4631) — small patch payload +$PY tools/mota/mota.py build \ + --fw firmware_new.bin --base firmware_old.bin \ + --target-id 0x11223344 --fw-version 1.16.0 \ + --codec sequential --sign signer.priv --out fw_v1.16.0_delta.mota +# --codec inplace for single-slot in-place apply (nRF52); params must match the bootloader contract + +# 3. inspect / validate +$PY tools/mota/mota.py inspect fw_v1.16.0_delta.mota +$PY tools/mota/mota.py verify fw_v1.16.0_delta.mota --pub signer.priv.pub --base firmware_old.bin +``` + +`build` notes: +- `--fw` may be a plain `.bin`; the tool appends `EndF` if absent (idempotent). +- For deltas, `base_hash` is taken from the base image's `EndF` and embedded so a device can confirm + the delta applies to its current firmware. +- `image_hash` (full SHA-256, signed) is the security anchor checked on the reconstructed image before + flashing; the 4-byte merkle tree is for per-block transfer verification. +- `--compression` (delta only) defaults to `crle` (decode-cheap). `lzma` gives smaller deltas but a + heavier on-device decoder — the final choice is pinned by the bootloader/applier contract. + +## Tests + +```bash +./meshcore/bin/python tools/mota/test_mota.py # 11 tests: EndF, merkle+proofs, full/delta, + # signing, tamper detection, approval enforcement +``` + +## `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: + +- **ESP32 / RP2040** (emit `firmware.bin`): add `post:tools/mota/pio_endf.py` to the env's + `extra_scripts` and define `-D ENABLE_OTA=1`. The hook appends `EndF` to the app `.bin` before merge. +- **nRF52 / STM32** (emit `.hex` → `.uf2`): the `.hex` must be rewritten with the trailer at the image + end before `create-uf2.py` runs. This path + the on-device round-trip is completed and validated in + milestone **P2** (which builds/flashes the RAK4631). The byte logic is the same `motalib.ensure_endf` + used everywhere. + +Until build integration lands, `mota build` still produces correct containers (it appends `EndF` to the +image it packages); only the *running* firmware's self-`EndF` depends on the build hook. diff --git a/tools/mota/endf.py b/tools/mota/endf.py new file mode 100644 index 00000000..f5277fa8 --- /dev/null +++ b/tools/mota/endf.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +""" +endf — append the MeshCore ``EndF`` trailer to a firmware image. + +The trailer lets a running node discover its own firmware size/identity on any MCU: + + EndF (16 bytes): "EndF"(4) | body_len(4 LE) | sha2-256:8(body)(8) + +Standalone usage (idempotent — a no-op if a valid EndF is already present): + + ./meshcore/bin/python tools/mota/endf.py firmware.bin # in place + ./meshcore/bin/python tools/mota/endf.py firmware.bin out.bin # to a new file + +As a PlatformIO post-build step (see tools/mota/README.md), wire it so that for OTA-enabled builds +the flashed artifact carries the trailer. EndF must be in the FLASHED image (not just the .mota), +because a node serves its own firmware and matches a delta's base against its own EndF. +""" + +from __future__ import annotations + +import sys +from pathlib import Path + +import motalib as ml + + +def inject(in_path: str, out_path: str | None = None) -> int: + image = Path(in_path).read_bytes() + if ml.has_endf(image): + body, h8 = ml.parse_endf(image) + print(f"EndF already present: body_len={len(body)} body_hash={h8.hex()} (no change)") + out = image + else: + out, h8 = ml.ensure_endf(image) + print(f"EndF appended: body_len={len(image)} body_hash={h8.hex()} " + f"({len(image)} -> {len(out)} bytes)") + Path(out_path or in_path).write_bytes(out) + return 0 + + +if __name__ == "__main__": + if len(sys.argv) not in (2, 3): + sys.exit(__doc__) + raise SystemExit(inject(sys.argv[1], sys.argv[2] if len(sys.argv) == 3 else None)) diff --git a/tools/mota/extract_apply.py b/tools/mota/extract_apply.py new file mode 100644 index 00000000..35e1e4b7 --- /dev/null +++ b/tools/mota/extract_apply.py @@ -0,0 +1,18 @@ +"""Extract the payload (bootable image) + manifest-fixed bytes from a firmware .bin for the apply test. +Usage: extract_apply.py """ +import sys +import motalib as ml +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey + +fw = open(sys.argv[1], "rb").read() +priv = Ed25519PrivateKey.from_private_bytes(bytes.fromhex(open(sys.argv[2]).read().strip())) +image, _ = ml.ensure_endf(fw) # payload = bootable image (+ EndF), what goes to the slot +m = ml.build_manifest(target_id=0, fw_version=ml.pack_version("1.16.0"), + image_size=len(image), payload=image, block_size=1024, + image_hash=ml.mh32(image), codec_id=ml.CODEC_FULL, is_full=True, sign_priv=priv) +manifest_fixed = m.signed_region() + m.signature + m.approval # manifest WITHOUT leaves[] +open(sys.argv[3], "wb").write(image) +open(sys.argv[4], "wb").write(manifest_fixed) +print(f"payload(image)={len(image)} manifest_fixed={len(manifest_fixed)}") +print(f"image_hash={m.image_hash.hex()}") +print(f"signer={priv.public_key().public_bytes_raw().hex()}") diff --git a/tools/mota/gen_vectors.py b/tools/mota/gen_vectors.py new file mode 100644 index 00000000..25158dfd --- /dev/null +++ b/tools/mota/gen_vectors.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python3 +""" +Generate a C++ cross-check header from the reference implementation (motalib). + +Emits test/test_ota/mota_vectors.h containing a real .mota built by the Python tool plus the +expected parse results and a merkle proof, so the device-side C++ core (src/helpers/ota/) is +verified to agree byte-for-byte with the host packager. + +Run: ./meshcore/bin/python tools/mota/gen_vectors.py +""" +from __future__ import annotations + +import random +from pathlib import Path + +import motalib as ml + +OUT = Path(__file__).resolve().parents[2] / "test" / "test_ota" / "mota_vectors.h" + + +def _carr(name, data: bytes) -> str: + body = ",".join(str(b) for b in data) + return f"static const uint8_t {name}[{len(data)}] = {{{body}}};\n" + + +def build_full(): + random.seed(42) + fw = bytes(random.getrandbits(8) for _ in range(5 * 1024 + 137)) # 6 blocks, last short + image, _ = ml.ensure_endf(fw) + m = ml.build_manifest( + target_id=0x11223344, fw_version=ml.pack_version("1.16.0"), + image_size=len(image), payload=image, block_size=1024, + image_hash=ml.mh32(image), codec_id=ml.CODEC_FULL, is_full=True) + return ml.build_container(m, image), m, image + + +def emit_proof_case(idx, count) -> str: + """A tree of `count` arbitrary 4-byte leaves + every block's proof, computed by the reference.""" + random.seed(1000 + count) + leaves = [bytes(random.getrandbits(8) for _ in range(4)) for _ in range(count)] + root = ml.merkle_root(leaves) + blob = b"" + offs, nsibs = [], [] + for i in range(count): + sib = ml.proof_siblings(leaves, i) + assert ml.verify_proof(leaves[i], i, ml.merkle_proof(leaves, i), root, count), (count, i) + offs.append(len(blob)) + nsibs.append(len(sib) // 4) + blob += sib + p = f"T{idx}" + out = [ + f"static const uint32_t {p}_COUNT = {count}u;", + _carr(f"{p}_LEAVES", b"".join(leaves)), + _carr(f"{p}_ROOT", root), + f"static const uint16_t {p}_POFF[{count}] = {{{','.join(str(o) for o in offs)}}};", + f"static const uint8_t {p}_PNSIB[{count}] = {{{','.join(str(n) for n in nsibs)}}};", + _carr(f"{p}_PBLOB", blob), + ] + return "\n".join(out) + + +def main(): + blob, m, image = build_full() + leaves = ml.leaf_hashes(image, 1024) + assert ml.merkle_root(leaves) == m.merkle_root + + proof_idx = 2 if m.block_count > 2 else 0 + siblings = ml.proof_siblings(leaves, proof_idx) + assert ml.verify_proof(leaves[proof_idx], proof_idx, ml.merkle_proof(leaves, proof_idx), + m.merkle_root, len(leaves)) + + lines = [ + "// AUTO-GENERATED by tools/mota/gen_vectors.py — do not edit by hand.", + "// Cross-check vectors: a real .mota from the reference packager (motalib.py).", + "#pragma once", + "#include ", + "", + _carr("MOTA_VEC", blob), + f"static const uint32_t MOTA_VEC_LEN = {len(blob)};", + f"static const uint32_t EXP_TARGET_ID = 0x{m.target_id:08x}u;", + f"static const uint32_t EXP_FW_VERSION = 0x{m.fw_version:08x}u;", + f"static const uint32_t EXP_IMAGE_SIZE = {m.image_size}u;", + f"static const uint32_t EXP_PAYLOAD_SIZE = {m.payload_size}u;", + f"static const uint32_t EXP_BLOCK_COUNT = {m.block_count}u;", + f"static const uint8_t EXP_BLOCK_SIZE_LOG2 = {m.block_size_log2};", + f"static const uint8_t EXP_CODEC_ID = {m.codec_id};", + _carr("EXP_MERKLE_ROOT", m.merkle_root), + _carr("EXP_IMAGE_HASH", m.image_hash), + f"static const uint32_t PROOF_INDEX = {proof_idx}u;", + f"static const uint8_t PROOF_NSIB = {len(siblings)//4};", + _carr("PROOF_SIBLINGS", siblings), + ] + + # exhaustive per-index proof cases for several tricky counts (deep promotion chains) + counts = [5, 7, 8, 65, 100, 255, 256] + for i, c in enumerate(counts): + lines.append(emit_proof_case(i, c)) + lines.append("struct ProofCase { uint32_t count; const uint8_t* leaves; const uint8_t* root;" + " const uint16_t* poff; const uint8_t* pnsib; const uint8_t* pblob; };") + rows = ",".join(f"{{T{i}_COUNT,T{i}_LEAVES,T{i}_ROOT,T{i}_POFF,T{i}_PNSIB,T{i}_PBLOB}}" + for i in range(len(counts))) + lines.append(f"static const ProofCase PROOF_CASES[] = {{{rows}}};") + lines.append(f"static const int N_PROOF_CASES = {len(counts)};") + + # small-block signed .mota for the host transfer simulation (each block fits one packet => no + # fragmentation needed to validate the manager/protocol end-to-end). Deterministic signing key. + from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey + random.seed(99) + sim_fw = bytes(random.getrandbits(8) for _ in range(2000)) + sim_image, _ = ml.ensure_endf(sim_fw) + sim_priv = Ed25519PrivateKey.from_private_bytes(bytes(range(32))) + sm = ml.build_manifest(target_id=0xCAFEBABE, fw_version=ml.pack_version("3.0.0"), + image_size=len(sim_image), payload=sim_image, block_size=128, + image_hash=ml.mh32(sim_image), codec_id=ml.CODEC_FULL, + is_full=True, sign_priv=sim_priv) + sim_blob = ml.build_container(sm, sim_image) + lines.append("// small-block signed .mota for the OtaManager host transfer simulation") + lines.append(_carr("SIM_MOTA", sim_blob)) + lines.append(f"static const uint32_t SIM_MOTA_LEN = {len(sim_blob)};") + lines.append(f"static const uint32_t SIM_TARGET_ID = 0x{sm.target_id:08x}u;") + + # detools sequential+crle delta vector: base image, the real detools 0.53.0 patch, and the + # expected target image. The native test applies DT_PATCH to DT_BASE with the *vendored detools + # C decoder* (src/helpers/ota/detools) and must reproduce DT_TARGET byte-for-byte -- proving the + # on-device delta apply path uses detools, not a reimplementation. + import io + import detools + random.seed(2024) + dt_base_body = bytes(random.getrandbits(8) for _ in range(3000)) + dt_tgt = bytearray(dt_base_body) + for off in (137, 138, 139, 1500, 2999): # localized edits, version-bump style + dt_tgt[off] ^= 0x5A + dt_tgt += bytes(random.getrandbits(8) for _ in range(200)) # small appended tail + dt_base_img, _ = ml.ensure_endf(dt_base_body) + dt_tgt_img, _ = ml.ensure_endf(bytes(dt_tgt)) + fp = io.BytesIO() + detools.create_patch(io.BytesIO(dt_base_img), io.BytesIO(dt_tgt_img), fp, + patch_type="sequential", compression="crle") + dt_patch = fp.getvalue() + lines.append("// detools sequential+crle delta: apply DT_PATCH to DT_BASE -> DT_TARGET") + lines.append(_carr("DT_BASE", dt_base_img)) + lines.append(f"static const uint32_t DT_BASE_LEN = {len(dt_base_img)};") + lines.append(_carr("DT_PATCH", dt_patch)) + lines.append(f"static const uint32_t DT_PATCH_LEN = {len(dt_patch)};") + lines.append(_carr("DT_TARGET", dt_tgt_img)) + lines.append(f"static const uint32_t DT_TARGET_LEN = {len(dt_tgt_img)};") + + # detools IN-PLACE+crle delta (nRF52 single-slot apply): apply DT_IP_PATCH to a memory region + # holding DT_IP_BASE -> region[0:to_size] == DT_IP_TARGET. The native test runs the vendored + # in-place decoder over a DT_IP_MEM-byte RAM buffer, exactly as the bootloader will over flash. + DT_IP_MEM, DT_IP_SEG = 0x8000, 0x1000 + ip_patch_io = io.BytesIO() + detools.create_patch(io.BytesIO(dt_base_img), io.BytesIO(dt_tgt_img), ip_patch_io, + patch_type="in-place", memory_size=DT_IP_MEM, segment_size=DT_IP_SEG, + compression="crle") + dt_ip_patch = ip_patch_io.getvalue() + lines.append("// detools in-place+crle delta: apply DT_IP_PATCH over a DT_IP_MEM buffer holding DT_IP_BASE") + lines.append(_carr("DT_IP_BASE", dt_base_img)) + lines.append(f"static const uint32_t DT_IP_BASE_LEN = {len(dt_base_img)};") + lines.append(_carr("DT_IP_PATCH", dt_ip_patch)) + lines.append(f"static const uint32_t DT_IP_PATCH_LEN = {len(dt_ip_patch)};") + lines.append(_carr("DT_IP_TARGET", dt_tgt_img)) + lines.append(f"static const uint32_t DT_IP_TARGET_LEN = {len(dt_tgt_img)};") + lines.append(f"static const uint32_t DT_IP_MEM = {DT_IP_MEM}u;") + + OUT.parent.mkdir(parents=True, exist_ok=True) + OUT.write_text("\n".join(lines) + "\n") + print(f"wrote {OUT}") + print(f" blob={len(blob)}B blocks={m.block_count} proof_idx={proof_idx} nsib={len(siblings)//4}") + print(f" proof cases: counts={counts}") + print(f" detools delta: base={len(dt_base_img)}B target={len(dt_tgt_img)}B seq_patch={len(dt_patch)}B inplace_patch={len(dt_ip_patch)}B") + + +if __name__ == "__main__": + main() diff --git a/tools/mota/mota.py b/tools/mota/mota.py new file mode 100644 index 00000000..0bcea1bb --- /dev/null +++ b/tools/mota/mota.py @@ -0,0 +1,224 @@ +#!/usr/bin/env python3 +""" +mota — build / inspect / verify MeshCore ``.mota`` firmware-update containers. + +Implements docs/ota_protocol.md (v1). Run with the meshcore venv: + + ./meshcore/bin/python tools/mota/mota.py ... + +Commands: + keygen generate an Ed25519 signing keypair (raw 32-byte hex) + build build a .mota from a firmware image (full, or delta against a base) + inspect print a .mota's manifest fields + verify validate a .mota (magic/trailer/merkle/signature[/delta vs base]) +""" + +from __future__ import annotations + +import argparse +import io +import sys +from pathlib import Path + +import motalib as ml + + +# --------------------------------------------------------------------------- +# key helpers +# --------------------------------------------------------------------------- + +def _load_priv(path): + from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey + raw = bytes.fromhex(Path(path).read_text().strip()) + if len(raw) != 32: + sys.exit(f"private key must be 32 raw bytes (64 hex chars), got {len(raw)}") + return Ed25519PrivateKey.from_private_bytes(raw) + + +def _parse_target_id(s) -> int: + return int(s, 0) & 0xFFFFFFFF # accepts 0x.. or decimal + + +# --------------------------------------------------------------------------- +# commands +# --------------------------------------------------------------------------- + +def cmd_keygen(args): + from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey + priv = Ed25519PrivateKey.generate() + priv_hex = priv.private_bytes_raw().hex() + pub_hex = priv.public_key().public_bytes_raw().hex() + if args.out_priv: + Path(args.out_priv).write_text(priv_hex + "\n") + Path(args.out_pub or (args.out_priv + ".pub")).write_text(pub_hex + "\n") + print(f"private -> {args.out_priv}") + print(f"public -> {args.out_pub or (args.out_priv + '.pub')}") + print(f"pubkey: {pub_hex}") + + +def _resolve_target_id(args) -> int: + if args.target_env: + return ml.target_id_for_env(args.target_env) + if args.target_id: + return _parse_target_id(args.target_id) + sys.exit("provide --target-id or --target-env") + + +def cmd_build(args): + fw = Path(args.fw).read_bytes() + new_image, _ = ml.ensure_endf(fw) # reconstructed image == BODY || EndF + image_hash = ml.mh32(new_image) + image_size = len(new_image) + + codec_map = {"full": ml.CODEC_FULL, + "sequential": ml.CODEC_DETOOLS_SEQUENTIAL, + "inplace": ml.CODEC_DETOOLS_INPLACE} + codec_id = codec_map[args.codec] + is_full = codec_id == ml.CODEC_FULL + + base_hash = None + if is_full: + if args.base: + sys.exit("--base is only for delta codecs") + payload = new_image + else: + if not args.base: + sys.exit("delta codec requires --base ") + old_image, base_hash = ml.ensure_endf(Path(args.base).read_bytes()) + payload = _make_delta(old_image, new_image, args.codec, args.compression, args) + + sign_priv = _load_priv(args.sign) if args.sign else None + + manifest = ml.build_manifest( + target_id=_resolve_target_id(args), + fw_version=ml.pack_version(args.fw_version), + image_size=image_size, + payload=payload, + block_size=args.block_size, + image_hash=image_hash, + codec_id=codec_id, + is_full=is_full, + base_hash=base_hash, + sign_priv=sign_priv, + ) + blob = ml.build_container(manifest, payload) + Path(args.out).write_bytes(blob) + + print(f"wrote {args.out} ({len(blob)} bytes)") + print(f" codec : {ml.CODEC_NAMES[codec_id]}") + print(f" payload : {len(payload)} bytes ({manifest.block_count} blocks of {args.block_size})") + print(f" image_size : {image_size} bytes (BODY+EndF)") + print(f" merkle_root : {manifest.merkle_root.hex()}") + print(f" image_hash : {manifest.image_hash.hex()}") + if base_hash: + print(f" base_hash : {base_hash.hex()}") + print(f" signed : {manifest.is_signed}" + + (f" by {manifest.signer_pubkey.hex()}" if manifest.is_signed else "")) + + +def _make_delta(old_image: bytes, new_image: bytes, codec: str, compression: str, args) -> bytes: + import detools + patch_type = "in-place" if codec == "inplace" else "sequential" + fp = io.BytesIO() + kwargs = dict(patch_type=patch_type, compression=compression) + if patch_type == "in-place": + # bounded-scratch params — MUST match the bootloader's applier contract (TBD with the fork). + kwargs.update(memory_size=args.inplace_memory, + segment_size=args.inplace_segment) + detools.create_patch(io.BytesIO(old_image), io.BytesIO(new_image), fp, **kwargs) + delta = fp.getvalue() + full = len(new_image) + print(f" delta : {len(delta)} bytes ({100*len(delta)/full:.1f}% of full {full})") + return delta + + +def cmd_inspect(args): + parsed = ml.parse_container(Path(args.mota).read_bytes()) + m = parsed.manifest + print(f"total_size : {parsed.total_size}") + print(f"format_ver : {m.format_ver}") + print(f"flags : 0x{m.flags:02x} FULL={m.is_full} SIGNED={m.is_signed}") + print(f"hash_algo : 0x{m.hash_algo:02x} (sha2-256)") + print(f"target_id : 0x{m.target_id:08x}") + print(f"fw_version : {ml.unpack_version(m.fw_version)} (0x{m.fw_version:08x})") + print(f"image_size : {m.image_size}") + print(f"payload_size : {m.payload_size}") + print(f"block_size : {m.block_size} (log2={m.block_size_log2}) block_count={m.block_count}") + print(f"codec_id : {m.codec_id} ({ml.CODEC_NAMES.get(m.codec_id, '?')})") + print(f"merkle_root : {m.merkle_root.hex()}") + print(f"image_hash : {m.image_hash.hex()}") + if m.base_hash: + print(f"base_hash : {m.base_hash.hex()}") + if m.is_signed: + print(f"signer_pubkey : {m.signer_pubkey.hex()}") + print(f"signature : {m.signature.hex()}") + approved = m.approval == ml.APPROVAL_YES + print(f"approval : {m.approval.hex()} ({'APPROVED' if approved else 'not approved'})") + print(f"leaves[] : {len(m.leaves)} x 4 bytes") + + +def cmd_verify(args): + parsed = ml.parse_container(Path(args.mota).read_bytes()) + expect_pub = bytes.fromhex(Path(args.pub).read_text().strip()) if args.pub else None + base_image = Path(args.base).read_bytes() if args.base else None + problems = ml.verify(parsed, expect_pub=expect_pub, base_image=base_image) + if problems: + print("INVALID:") + for p in problems: + print(f" - {p}") + sys.exit(1) + print("OK — container, merkle tree" + + (", signature" if parsed.manifest.is_signed else "") + + (", delta->image_hash" if (base_image and not parsed.manifest.is_full) else + (", image_hash" if parsed.manifest.is_full else "")) + + " all valid.") + + +# --------------------------------------------------------------------------- +# argparse +# --------------------------------------------------------------------------- + +def main(argv=None): + p = argparse.ArgumentParser(prog="mota", description="MeshCore .mota packaging tool") + sub = p.add_subparsers(dest="cmd", required=True) + + g = sub.add_parser("keygen", help="generate an Ed25519 signing keypair") + g.add_argument("--out-priv", help="write 32-byte private key (hex) here") + g.add_argument("--out-pub", help="write 32-byte public key (hex) here") + g.set_defaults(func=cmd_keygen) + + b = sub.add_parser("build", help="build a .mota") + b.add_argument("--fw", required=True, help="new firmware image (.bin / EndF appended if absent)") + b.add_argument("--out", required=True, help="output .mota path") + b.add_argument("--target-id", help="target_id (0x.. or decimal)") + b.add_argument("--target-env", help="PlatformIO env name; target_id = sha2-256:4(env) " + "(matches build.sh / device getOtaTargetId)") + b.add_argument("--fw-version", required=True, help="e.g. 1.16.0 (or .pre as 1.16.0.2)") + b.add_argument("--codec", choices=["full", "sequential", "inplace"], default="full") + b.add_argument("--base", help="base firmware (.bin) for delta codecs") + b.add_argument("--compression", default="crle", + choices=["none", "crle", "lz4", "zstd", "lzma", "bz2"], + help="delta patch compression (decode-cheap 'crle' default; must be supported by " + "the applier. Ignored for --codec full, whose payload is the raw flashable image)") + b.add_argument("--block-size", type=int, default=ml.DEFAULT_BLOCK_SIZE) + b.add_argument("--sign", help="Ed25519 private key file (hex) to sign the manifest") + b.add_argument("--inplace-memory", type=int, default=4096, help="detools in-place memory_size") + b.add_argument("--inplace-segment", type=int, default=4096, help="detools in-place segment_size") + b.set_defaults(func=cmd_build) + + i = sub.add_parser("inspect", help="dump a .mota manifest") + i.add_argument("mota") + i.set_defaults(func=cmd_inspect) + + v = sub.add_parser("verify", help="validate a .mota") + v.add_argument("mota") + v.add_argument("--pub", help="expected signer public key file (hex)") + v.add_argument("--base", help="base firmware to fully validate a delta -> image_hash") + v.set_defaults(func=cmd_verify) + + args = p.parse_args(argv) + args.func(args) + + +if __name__ == "__main__": + main() diff --git a/tools/mota/motalib.py b/tools/mota/motalib.py new file mode 100644 index 00000000..111ed2f7 --- /dev/null +++ b/tools/mota/motalib.py @@ -0,0 +1,457 @@ +""" +motalib — build/parse/verify MeshCore ``.mota`` firmware-update containers. + +Pure logic, no CLI. Implements docs/ota_protocol.md (v1, format_ver=1). + +The wire format (all integers little-endian): + + container = MAGIC(4) | MOTA_TOTAL_SIZE(4) | MANIFEST | PAYLOAD | TRAILER(5) + + manifest = format_ver(1) flags(1) hash_algo(1) target_id(4) fw_version(4) + image_size(4) payload_size(4) block_size_log2(1) merkle_root(4) + image_hash(32) codec_id(1) + [base_hash(8) if delta] [signer_pubkey(32) signature(64) if signed] + approval(4) leaves[](4*BC) + +Hashes are SHA-256, truncated per multihash convention (sha2-256:N = first N bytes). +""" + +from __future__ import annotations + +import hashlib +import io +import struct +from dataclasses import dataclass, field +from typing import List, Optional, Tuple + +# --------------------------------------------------------------------------- +# Reference constants (must match docs/ota_protocol.md and the device code) +# --------------------------------------------------------------------------- + +MAGIC = b"mOTA" # 6D 4F 54 41 +TRAILER = b"vk496" # 76 6B 34 39 36 +ENDF_MAGIC = b"EndF" # 45 6E 64 46 +ENDF_LEN = 16 # marker(4) + body_len(4) + body_hash8(8) + +FORMAT_VER = 1 +HASH_ALGO_SHA256 = 0x12 # multihash code for sha2-256 + +FLAG_FULL = 0x01 +FLAG_SIGNED = 0x02 + +CODEC_FULL = 0 +CODEC_DETOOLS_SEQUENTIAL = 1 # detools `sequential` patch (decoded on-device by vendored detools C) +CODEC_DETOOLS_INPLACE = 2 # detools `in-place` patch (nRF52 bootloader-handoff path; TBD) +CODEC_NAMES = {CODEC_FULL: "full", CODEC_DETOOLS_SEQUENTIAL: "detools-sequential", + CODEC_DETOOLS_INPLACE: "detools-in-place"} + +APPROVAL_NOT = b"\xff\xff\xff\xff" # erased = not approved +APPROVAL_YES = b"APRV" # 41 50 52 56 = approved + +DEFAULT_BLOCK_SIZE = 1024 + + +# --------------------------------------------------------------------------- +# Multihash helpers (sha2-256 truncations) +# --------------------------------------------------------------------------- + +def sha256(data: bytes) -> bytes: + return hashlib.sha256(data).digest() + + +def mh4(data: bytes) -> bytes: + return hashlib.sha256(data).digest()[:4] + + +def mh8(data: bytes) -> bytes: + return hashlib.sha256(data).digest()[:8] + + +def mh32(data: bytes) -> bytes: + return hashlib.sha256(data).digest() + + +# --------------------------------------------------------------------------- +# fw_version packing +# --------------------------------------------------------------------------- + +def pack_version(s) -> int: + """'1.16.0' or '1.16.0.2' -> uint32 (MAJOR<<24|MINOR<<16|PATCH<<8|pre). Ints pass through.""" + if isinstance(s, int): + return s & 0xFFFFFFFF + s = s.strip().lstrip("vV") + parts = [int(p) for p in s.split(".")] + parts += [0] * (4 - len(parts)) + maj, mnr, pat, pre = parts[:4] + return ((maj & 0xFF) << 24) | ((mnr & 0xFF) << 16) | ((pat & 0xFF) << 8) | (pre & 0xFF) + + +def unpack_version(v: int) -> str: + return f"{(v >> 24) & 0xFF}.{(v >> 16) & 0xFF}.{(v >> 8) & 0xFF}.{v & 0xFF}" + + +# --------------------------------------------------------------------------- +# target_id +# --------------------------------------------------------------------------- + +def target_id_for_env(env_name: str) -> int: + """4-byte build-target id = sha2-256:4(env_name), little-endian uint32. + + The PlatformIO env name (e.g. 'RAK_4631_companion_radio_usb') uniquely captures hardware AND + role/partition layout. build.sh injects the same value as -D MOTA_TARGET_ID so the device's + getOtaTargetId() matches what the packager stamps into the manifest. (Must match build.sh.) + """ + d = hashlib.sha256(env_name.encode()).digest()[:4] + return int.from_bytes(d, "little") + + +# --------------------------------------------------------------------------- +# EndF trailer +# --------------------------------------------------------------------------- + +def build_endf(body: bytes) -> bytes: + """The 16-byte EndF trailer for a firmware BODY.""" + return ENDF_MAGIC + struct.pack(" bool: + """True iff `image` ends with a self-consistent EndF trailer (image == BODY || EndF).""" + if len(image) < ENDF_LEN: + return False + trailer = image[-ENDF_LEN:] + if trailer[:4] != ENDF_MAGIC: + return False + body_len = struct.unpack(" Tuple[bytes, bytes]: + """Return (body, body_hash8) for an image that ends with a valid EndF. Raises otherwise.""" + if not has_endf(image): + raise ValueError("image has no valid EndF trailer") + return image[:-ENDF_LEN], image[-8:] + + +def ensure_endf(image: bytes) -> Tuple[bytes, bytes]: + """Return (image_with_endf, body_hash8). Appends EndF if not already present.""" + if has_endf(image): + return image, image[-8:] + body_hash8 = mh8(image) + return image + build_endf(image), body_hash8 + + +# --------------------------------------------------------------------------- +# Merkle tree (sha2-256:4 leaves/nodes, promote-odd, no padding) +# --------------------------------------------------------------------------- + +def block_count(payload_size: int, block_size: int) -> int: + return (payload_size + block_size - 1) // block_size + + +def leaf_hashes(payload: bytes, block_size: int) -> List[bytes]: + return [mh4(payload[i:i + block_size]) for i in range(0, len(payload), block_size)] + + +def merkle_root(leaves: List[bytes]) -> bytes: + if not leaves: + raise ValueError("empty payload / no leaves") + level = list(leaves) + while len(level) > 1: + nxt = [] + n = len(level) + for i in range(0, n, 2): + if i + 1 < n: + nxt.append(mh4(level[i] + level[i + 1])) + else: + nxt.append(level[i]) # promote lone last node unchanged + level = nxt + return level[0] + + +def merkle_proof(leaves: List[bytes], index: int) -> List[Tuple[bytes, bool]]: + """Proof for block `index`: list of (sibling_digest, sibling_is_left).""" + proof: List[Tuple[bytes, bool]] = [] + level = list(leaves) + idx = index + while len(level) > 1: + n = len(level) + is_last_odd = (n % 2 == 1) and (idx == n - 1) + if not is_last_odd: + if idx % 2 == 0: + proof.append((level[idx + 1], False)) # sibling on the right + else: + proof.append((level[idx - 1], True)) # sibling on the left + nxt = [mh4(level[i] + level[i + 1]) if i + 1 < n else level[i] + for i in range(0, n, 2)] + idx //= 2 + level = nxt + return proof + + +def proof_siblings(leaves: List[bytes], index: int) -> bytes: + """Wire form of a proof: just the ordered sibling digests, concatenated. + + The left/right direction is derived by the verifier from the block index + count + (sibling is on the left iff the current index is odd), so no direction bits are sent. + """ + return b"".join(sib for sib, _ in merkle_proof(leaves, index)) + + +def verify_proof(leaf: bytes, index: int, proof: List[Tuple[bytes, bool]], + root: bytes, count: int) -> bool: + h = leaf + idx = index + n = count + p = 0 + while n > 1: + is_last_odd = (n % 2 == 1) and (idx == n - 1) + if is_last_odd: + pass # promoted, no proof element + else: + if p >= len(proof): + return False + sib, is_left = proof[p] + p += 1 + h = mh4(sib + h) if is_left else mh4(h + sib) + idx //= 2 + n = (n + 1) // 2 + return h == root and p == len(proof) + + +# --------------------------------------------------------------------------- +# Manifest + container +# --------------------------------------------------------------------------- + +@dataclass +class Manifest: + format_ver: int = FORMAT_VER + flags: int = 0 + hash_algo: int = HASH_ALGO_SHA256 + target_id: int = 0 + fw_version: int = 0 + image_size: int = 0 + payload_size: int = 0 + block_size_log2: int = 10 + merkle_root: bytes = b"\0\0\0\0" + image_hash: bytes = b"\0" * 32 + codec_id: int = CODEC_FULL + base_hash: Optional[bytes] = None # 8 bytes, delta only + signer_pubkey: Optional[bytes] = None # 32 bytes, signed only + signature: Optional[bytes] = None # 64 bytes, signed only + approval: bytes = APPROVAL_NOT + leaves: List[bytes] = field(default_factory=list) + + @property + def is_full(self) -> bool: + return bool(self.flags & FLAG_FULL) + + @property + def is_signed(self) -> bool: + return bool(self.flags & FLAG_SIGNED) + + @property + def block_size(self) -> int: + return 1 << self.block_size_log2 + + @property + def block_count(self) -> int: + return block_count(self.payload_size, self.block_size) + + def signed_region(self) -> bytes: + """Bytes the Ed25519 signature covers: everything from format_ver up to (not incl.) signature.""" + out = bytearray() + out += bytes([self.format_ver, self.flags, self.hash_algo]) + out += struct.pack(" bytes: + out = bytearray(self.signed_region()) + if self.is_signed: + out += self.signature + out += self.approval + for lf in self.leaves: + out += lf + return bytes(out) + + +def _validate_lengths(m: Manifest): + assert len(m.merkle_root) == 4 + assert len(m.image_hash) == 32 + assert len(m.approval) == 4 + if not m.is_full: + assert m.base_hash is not None and len(m.base_hash) == 8, "delta requires 8-byte base_hash" + if m.is_signed: + assert m.signer_pubkey is not None and len(m.signer_pubkey) == 32 + assert m.signature is not None and len(m.signature) == 64 + + +def build_manifest(*, target_id: int, fw_version: int, image_size: int, payload: bytes, + block_size: int, image_hash: bytes, codec_id: int, is_full: bool, + base_hash: Optional[bytes] = None, sign_priv=None) -> Manifest: + assert (block_size & (block_size - 1)) == 0, "block_size must be a power of two" + leaves = leaf_hashes(payload, block_size) + m = Manifest( + flags=(FLAG_FULL if is_full else 0) | (FLAG_SIGNED if sign_priv is not None else 0), + target_id=target_id, + fw_version=fw_version, + image_size=image_size, + payload_size=len(payload), + block_size_log2=block_size.bit_length() - 1, + merkle_root=merkle_root(leaves), + image_hash=image_hash, + codec_id=codec_id, + base_hash=None if is_full else base_hash, + leaves=leaves, + ) + if sign_priv is not None: + m.signer_pubkey = sign_priv.public_key().public_bytes_raw() + m.signature = sign_priv.sign(m.signed_region()) + _validate_lengths(m) + return m + + +def build_container(manifest: Manifest, payload: bytes) -> bytes: + assert len(payload) == manifest.payload_size + mser = manifest.serialize() + total = 4 + 4 + len(mser) + len(payload) + len(TRAILER) + return MAGIC + struct.pack(" Parsed: + if blob[:4] != MAGIC: + raise ValueError("bad MAGIC") + if blob[-5:] != TRAILER: + raise ValueError("bad TRAILER") + total = struct.unpack(" List[str]: + """Return a list of problem strings (empty == fully valid for what could be checked).""" + problems: List[str] = [] + m, payload = parsed.manifest, parsed.payload + + # block_count / leaves + if len(m.leaves) != m.block_count: + problems.append(f"leaves count {len(m.leaves)} != block_count {m.block_count}") + + # merkle root must match recomputation from the actual payload blocks + recomputed_leaves = leaf_hashes(payload, m.block_size) + if recomputed_leaves != m.leaves: + problems.append("stored leaves[] do not match payload blocks") + try: + if merkle_root(recomputed_leaves) != m.merkle_root: + problems.append("merkle_root does not match payload") + except ValueError as e: + problems.append(f"merkle: {e}") + + # spot-check a proof round-trips (block 0 and last) + if recomputed_leaves: + for idx in {0, len(recomputed_leaves) - 1}: + pr = merkle_proof(recomputed_leaves, idx) + if not verify_proof(recomputed_leaves[idx], idx, pr, m.merkle_root, len(recomputed_leaves)): + problems.append(f"merkle proof failed for block {idx}") + + # approval must be 'not approved' in a distributed container + if m.approval != APPROVAL_NOT: + problems.append(f"approval is not the erased sentinel (got {m.approval.hex()})") + + # signature + if m.is_signed: + from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey + from cryptography.exceptions import InvalidSignature + pub = Ed25519PublicKey.from_public_bytes(m.signer_pubkey) + try: + pub.verify(m.signature, m.signed_region()) + except InvalidSignature: + problems.append("Ed25519 signature INVALID") + if expect_pub is not None and m.signer_pubkey != expect_pub: + problems.append("signer_pubkey != expected key") + + # image_hash: directly checkable only for full images (payload IS the image) + if m.is_full: + if mh32(payload) != m.image_hash: + problems.append("image_hash does not match full payload") + elif base_image is not None: + # delta: optionally apply against a provided base to confirm image_hash + try: + import detools + out = io.BytesIO() + detools.apply_patch(io.BytesIO(_ensure_base(base_image)), io.BytesIO(payload), out) + rebuilt = out.getvalue() + if mh32(rebuilt) != m.image_hash: + problems.append("delta applied to base does not match image_hash") + if len(rebuilt) != m.image_size: + problems.append("delta result size != image_size") + except Exception as e: # noqa: BLE001 + problems.append(f"delta apply check failed: {e}") + return problems + + +def _ensure_base(base_image: bytes) -> bytes: + img, _ = ensure_endf(base_image) + return img diff --git a/tools/mota/pio_endf.py b/tools/mota/pio_endf.py new file mode 100644 index 00000000..235e338c --- /dev/null +++ b/tools/mota/pio_endf.py @@ -0,0 +1,86 @@ +""" +PlatformIO post-build extra-script: append the MeshCore ``EndF`` trailer to the +firmware image so a running node can self-locate its size/identity (docs/ota_protocol.md §2). + +Wire it (ONLY for OTA-enabled builds) from a variant/env, e.g.: + + extra_scripts = + ${nrf52_base.extra_scripts} + post:tools/mota/pio_endf.py + +and define ``-D ENABLE_OTA=1``. With ENABLE_OTA unset this script is a no-op, so it is safe to +leave wired everywhere. + +The byte logic is the same `motalib.ensure_endf` exercised by `endf.py` and the unit tests. + +ESP32 / RP2040 emit ${PROGNAME}.bin (the raw app image) -> EndF appended to the .bin. +nRF52 emits ${PROGNAME}.hex (the app, for DFU/UF2) -> EndF appended into the .hex right after the +app's last byte (so the downstream .uf2 / DFU .zip carry it). Both feed the same on-device EndF scan. +""" + +Import("env") # noqa: F821 (injected by PlatformIO/SCons) + +import os +import sys + +sys.path.insert(0, os.path.join(env["PROJECT_DIR"], "tools", "mota")) # noqa: F821 +import motalib as ml + + +def _ota_enabled() -> bool: + for d in env.get("CPPDEFINES", []): # noqa: F821 + name = d[0] if isinstance(d, (list, tuple)) else d + if name == "ENABLE_OTA": + return True + return False + + +def _is_nrf52() -> bool: + for d in env.get("CPPDEFINES", []): # noqa: F821 + name = d[0] if isinstance(d, (list, tuple)) else d + if name == "NRF52_PLATFORM": + return True + return False + + +def _append_endf(source, target, env): # raw .bin path (ESP32 / RP2040) + path = str(target[0]) + with open(path, "rb") as f: + data = f.read() + out, h8 = ml.ensure_endf(data) + if len(out) != len(data): + with open(path, "wb") as f: + f.write(out) + print(f"EndF: appended to {os.path.basename(path)} " + f"(body_len={len(data)} body_hash={h8.hex()})") + else: + print(f"EndF: already present in {os.path.basename(path)} (no change)") + + +def _append_endf_hex(source, target, env): # Intel-HEX path (nRF52: app for DFU/UF2) + from intelhex import IntelHex + path = str(target[0]) + ih = IntelHex(path) + segs = ih.segments() + if not segs: + print("EndF: empty .hex, skipping"); return + app_start, app_end = segs[0] # first (lowest) segment = the application image + body = bytes(ih.tobinarray(start=app_start, size=app_end - app_start)) + out, h8 = ml.ensure_endf(body) + if len(out) == len(body): + print(f"EndF: already present in {os.path.basename(path)} (no change)"); return + trailer = out[len(body):] # the 16-byte EndF trailer + for i, b in enumerate(trailer): + ih[app_end + i] = b # write it right after the app's last byte + ih.write_hex_file(path) + print(f"EndF: appended to {os.path.basename(path)} at 0x{app_end:X} " + f"(app=0x{app_start:X}.. body_len={len(body)} body_hash={h8.hex()})") + + +if _ota_enabled(): + if _is_nrf52(): + env.AddPostAction("$BUILD_DIR/${PROGNAME}.hex", _append_endf_hex) # noqa: F821 + else: + env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", _append_endf) # noqa: F821 +else: + print("EndF: ENABLE_OTA not defined; skipping trailer injection") diff --git a/tools/mota/test_mota.py b/tools/mota/test_mota.py new file mode 100644 index 00000000..fb4fa6f9 --- /dev/null +++ b/tools/mota/test_mota.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python3 +""" +Tests for motalib — run with the meshcore venv: + + ./meshcore/bin/python tools/mota/test_mota.py + +(Also pytest-compatible: functions are named test_*.) +""" + +from __future__ import annotations + +import io +import os +import random +import struct + +import motalib as ml +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey + + +def _fw(seed, size): + random.seed(seed) + return bytes(random.getrandbits(8) for _ in range(size)) + + +# --- multihash / version --------------------------------------------------- + +def test_version_pack_roundtrip(): + assert ml.pack_version("1.16.0") == (1 << 24) | (16 << 16) + assert ml.unpack_version(ml.pack_version("1.16.0.2")) == "1.16.0.2" + assert ml.pack_version(0x01100000) == 0x01100000 + + +def test_target_id_for_env(): + import hashlib + env = "RAK_4631_companion_radio_usb" + expect = int.from_bytes(hashlib.sha256(env.encode()).digest()[:4], "little") + assert ml.target_id_for_env(env) == expect + # distinct envs (same board, different role) get distinct ids + assert ml.target_id_for_env("RAK_4631_repeater") != ml.target_id_for_env("RAK_4631_companion_radio_usb") + + +# --- EndF ------------------------------------------------------------------ + +def test_endf_roundtrip_and_idempotent(): + body = _fw(1, 5000) + img, h8 = ml.ensure_endf(body) + assert len(img) == 5000 + ml.ENDF_LEN + assert ml.has_endf(img) + pbody, ph8 = ml.parse_endf(img) + assert pbody == body and ph8 == h8 == ml.mh8(body) + # idempotent: feeding an already-EndF'd image returns it unchanged + img2, h82 = ml.ensure_endf(img) + assert img2 == img and h82 == h8 + + +def test_endf_rejects_garbage_tail(): + assert not ml.has_endf(b"too short") + body = _fw(2, 1000) + img = body + ml.ENDF_MAGIC + struct.pack(" flagged + other = Ed25519PrivateKey.generate().public_key().public_bytes_raw() + assert any("signer_pubkey" in p for p in ml.verify(parsed, expect_pub=other)) + + +def test_tampered_signature_detected(): + priv = Ed25519PrivateKey.generate() + fw = _fw(13, 8 * 1024) + image, _ = ml.ensure_endf(fw) + m = ml.build_manifest(target_id=7, fw_version=1, image_size=len(image), payload=image, + block_size=1024, image_hash=ml.mh32(image), + codec_id=ml.CODEC_FULL, is_full=True, sign_priv=priv) + blob = bytearray(ml.build_container(m, image)) + # flip a byte of target_id (inside signed region) without re-signing + blob[10] ^= 0xFF + problems = ml.verify(ml.parse_container(bytes(blob))) + assert any("signature INVALID" in p for p in problems), problems + + +# --- approval enforcement -------------------------------------------------- + +def test_approval_default_and_flagged_if_preapproved(): + fw = _fw(14, 4 * 1024) + image, _ = ml.ensure_endf(fw) + m = ml.build_manifest(target_id=1, fw_version=1, image_size=len(image), payload=image, + block_size=1024, image_hash=ml.mh32(image), + codec_id=ml.CODEC_FULL, is_full=True) + assert m.approval == ml.APPROVAL_NOT + # simulate a malicious pre-approved container -> verify must flag it + m.approval = ml.APPROVAL_YES + parsed = ml.parse_container(ml.build_container(m, image)) + assert any("approval" in p for p in ml.verify(parsed)) + + +# --- delta ----------------------------------------------------------------- + +def test_delta_build_apply_verify(): + old_body = _fw(20, 40 * 1024) + # new = old with a chunk changed + appended -> a real, small-ish delta + new_body = bytearray(old_body) + for i in range(1000, 1500): + new_body[i] = (new_body[i] + 1) & 0xFF + new_body += _fw(21, 2048) + old_image, base_hash = ml.ensure_endf(bytes(old_body)) + new_image, _ = ml.ensure_endf(bytes(new_body)) + + import detools + fp = io.BytesIO() + detools.create_patch(io.BytesIO(old_image), io.BytesIO(new_image), fp, + patch_type="sequential", compression="crle") + delta = fp.getvalue() + # with compression a near-identical-base delta is a fraction of the full image + assert len(delta) < len(new_image) // 2, (len(delta), len(new_image)) + + m = ml.build_manifest(target_id=0xABCD, fw_version=ml.pack_version("1.2.0"), + image_size=len(new_image), payload=delta, block_size=1024, + image_hash=ml.mh32(new_image), codec_id=ml.CODEC_DETOOLS_SEQUENTIAL, + is_full=False, base_hash=base_hash) + parsed = ml.parse_container(ml.build_container(m, delta)) + assert parsed.manifest.base_hash == base_hash == ml.mh8(bytes(old_body)) + # full verify incl. applying the delta to the base and checking image_hash + assert ml.verify(parsed, base_image=old_image) == [] + # wrong base must fail the delta->image_hash check + wrong = ml.verify(parsed, base_image=_fw(99, 40 * 1024)) + assert wrong, "delta verify against a wrong base should fail" + + +# --- runner ---------------------------------------------------------------- + +def _run(): + tests = {k: v for k, v in sorted(globals().items()) + if k.startswith("test_") and callable(v)} + failed = 0 + for name, fn in tests.items(): + try: + fn() + print(f"ok {name}") + except Exception as e: # noqa: BLE001 + failed += 1 + import traceback + print(f"FAIL {name}: {e}") + traceback.print_exc() + print(f"\n{len(tests) - failed}/{len(tests)} passed") + return 1 if failed else 0 + + +if __name__ == "__main__": + raise SystemExit(_run()) diff --git a/variants/gat562_30s_mesh_kit/platformio.ini b/variants/gat562_30s_mesh_kit/platformio.ini index 2baac256..e940085e 100644 --- a/variants/gat562_30s_mesh_kit/platformio.ini +++ b/variants/gat562_30s_mesh_kit/platformio.ini @@ -1,8 +1,8 @@ [GAT562_30S_Mesh_Kit] -extends = nrf52_base +extends = rak4631_hw board = rak4631 board_check = true -build_flags = ${nrf52_base.build_flags} +build_flags = ${rak4631_hw.build_flags} ${sensor_base.build_flags} -I variants/gat562_30s_mesh_kit -D RAK_4631 @@ -19,13 +19,13 @@ build_flags = ${nrf52_base.build_flags} -D PIN_BUZZER=33 -D SX126X_RX_BOOSTED_GAIN=1 -D SX126X_DIO2_AS_RF_SWITCH=true -build_src_filter = ${nrf52_base.build_src_filter} +build_src_filter = ${rak4631_hw.build_src_filter} +<../variants/gat562_30s_mesh_kit> + + + lib_deps = - ${nrf52_base.lib_deps} + ${rak4631_hw.lib_deps} ${sensor_base.lib_deps} adafruit/Adafruit SSD1306 @ ^2.5.13 sparkfun/SparkFun u-blox GNSS Arduino Library@^2.2.27 diff --git a/variants/gat562_mesh_evb_pro/platformio.ini b/variants/gat562_mesh_evb_pro/platformio.ini index b3e89417..20dedd84 100644 --- a/variants/gat562_mesh_evb_pro/platformio.ini +++ b/variants/gat562_mesh_evb_pro/platformio.ini @@ -1,8 +1,8 @@ [GAT562_Mesh_EVB_Pro] -extends = nrf52_base +extends = rak4631_hw board = rak4631 board_check = true -build_flags = ${nrf52_base.build_flags} +build_flags = ${rak4631_hw.build_flags} ${sensor_base.build_flags} -I variants/gat562_mesh_evb_pro -D NRF52_POWER_MANAGEMENT @@ -13,12 +13,12 @@ build_flags = ${nrf52_base.build_flags} -D LORA_TX_POWER=22 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 -build_src_filter = ${nrf52_base.build_src_filter} +build_src_filter = ${rak4631_hw.build_src_filter} +<../variants/gat562_mesh_evb_pro> + + lib_deps = - ${nrf52_base.lib_deps} + ${rak4631_hw.lib_deps} ${sensor_base.lib_deps} sparkfun/SparkFun u-blox GNSS Arduino Library@^2.2.27 diff --git a/variants/gat562_mesh_tracker_pro/platformio.ini b/variants/gat562_mesh_tracker_pro/platformio.ini index af153b8f..26bf49cb 100644 --- a/variants/gat562_mesh_tracker_pro/platformio.ini +++ b/variants/gat562_mesh_tracker_pro/platformio.ini @@ -1,8 +1,8 @@ [GAT562_Mesh_Tracker_Pro] -extends = nrf52_base +extends = rak4631_hw board = rak4631 board_check = true -build_flags = ${nrf52_base.build_flags} +build_flags = ${rak4631_hw.build_flags} ${sensor_base.build_flags} -I variants/gat562_mesh_tracker_pro -D NRF52_POWER_MANAGEMENT @@ -15,13 +15,13 @@ build_flags = ${nrf52_base.build_flags} -D LORA_TX_POWER=22 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 -build_src_filter = ${nrf52_base.build_src_filter} +build_src_filter = ${rak4631_hw.build_src_filter} +<../variants/gat562_mesh_tracker_pro> + + + lib_deps = - ${nrf52_base.lib_deps} + ${rak4631_hw.lib_deps} ${sensor_base.lib_deps} adafruit/Adafruit SSD1306 @ ^2.5.13 sparkfun/SparkFun u-blox GNSS Arduino Library@^2.2.27 diff --git a/variants/gat562_mesh_watch13/platformio.ini b/variants/gat562_mesh_watch13/platformio.ini index f3510b74..59ec79d3 100644 --- a/variants/gat562_mesh_watch13/platformio.ini +++ b/variants/gat562_mesh_watch13/platformio.ini @@ -1,8 +1,8 @@ [GAT562_Mesh_Watch13] -extends = nrf52_base +extends = rak4631_hw board = rak4631 board_check = true -build_flags = ${nrf52_base.build_flags} +build_flags = ${rak4631_hw.build_flags} ${sensor_base.build_flags} -UENV_INCLUDE_GPS -I variants/gat562_mesh_watch13 @@ -18,13 +18,13 @@ build_flags = ${nrf52_base.build_flags} -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 -D QSPIFLASH=1 -build_src_filter = ${nrf52_base.build_src_filter} +build_src_filter = ${rak4631_hw.build_src_filter} +<../variants/gat562_mesh_watch13> + + + lib_deps = - ${nrf52_base.lib_deps} + ${rak4631_hw.lib_deps} ${sensor_base.lib_deps} adafruit/Adafruit SSD1306 @ ^2.5.13 diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index a70a93a5..60259948 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -47,10 +47,16 @@ build_flags = -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=50 + -D ENABLE_OTA=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 +extra_scripts = + merge-bin.py + post:tools/mota/pio_endf.py build_src_filter = ${Heltec_lora32_v3.build_src_filter} + + + + + +<../examples/simple_repeater> lib_deps = ${Heltec_lora32_v3.lib_deps} diff --git a/variants/muziworks_r1_neo/platformio.ini b/variants/muziworks_r1_neo/platformio.ini index 3dbecf1e..d12c5281 100644 --- a/variants/muziworks_r1_neo/platformio.ini +++ b/variants/muziworks_r1_neo/platformio.ini @@ -1,8 +1,8 @@ [R1Neo] -extends = nrf52_base +extends = rak4631_hw board = rak4631 board_check = true -build_flags = ${nrf52_base.build_flags} +build_flags = ${rak4631_hw.build_flags} ${sensor_base.build_flags} -I variants/muziworks_r1_neo -I src/helpers/ui @@ -19,13 +19,13 @@ build_flags = ${nrf52_base.build_flags} -D PIN_GPS_TX=25 -D PIN_GPS_RX=24 -D PIN_GPS_EN=33 -build_src_filter = ${nrf52_base.build_src_filter} +build_src_filter = ${rak4631_hw.build_src_filter} +<../variants/muziworks_r1_neo> + + + lib_deps = - ${nrf52_base.lib_deps} + ${rak4631_hw.lib_deps} ${sensor_base.lib_deps} sparkfun/SparkFun u-blox GNSS Arduino Library@^2.2.27 diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 2bbba314..2a6349f3 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -4,6 +4,7 @@ board = rak4631 board_check = true extra_scripts = ${nrf52_base.extra_scripts} post:variants/rak4631/fix_bsec_lib.py + post:tools/mota/pio_endf.py ; EndF trailer for OTA self-identity (all RAK4631 roles) build_flags = ${nrf52_base.build_flags} ${sensor_base.build_flags} -I variants/rak4631 @@ -25,11 +26,14 @@ build_flags = ${nrf52_base.build_flags} -D ENV_INCLUDE_RAK12035=1 -UENV_INCLUDE_BME680 -D ENV_INCLUDE_BME680_BSEC=1 + -D ENABLE_OTA=1 ; OTA delta updates on every RAK4631 role (single-slot, bootloader-applied) + -D OTA_FLASH_STORE=1 ; stage the received .mota in flash (survives reboot into the bootloader) build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631> + + + + + lib_deps = ${nrf52_base.lib_deps} ${sensor_base.lib_deps} @@ -47,10 +51,12 @@ build_flags = -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=50 +; -D OTA_DEBUG=1 ; bring-up: trace OTA fetch (REQ / block / page-flush) over Serial ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${rak4631.build_src_filter} + + ; OTA (ENABLE_OTA + flash store + EndF + helpers/ota) is inherited from the [rak4631] base now +<../examples/simple_repeater> [env:RAK_4631_repeater_bridge_rs232_serial1] diff --git a/variants/rak_wismesh_tag/platformio.ini b/variants/rak_wismesh_tag/platformio.ini index e9cddb74..45669414 100644 --- a/variants/rak_wismesh_tag/platformio.ini +++ b/variants/rak_wismesh_tag/platformio.ini @@ -1,8 +1,8 @@ [rak_wismesh_tag] -extends = nrf52_base +extends = rak4631_hw board = rak4631 board_check = true -build_flags = ${nrf52_base.build_flags} +build_flags = ${rak4631_hw.build_flags} ${sensor_base.build_flags} -I variants/rak_wismesh_tag -I src/helpers/ui @@ -25,13 +25,13 @@ build_flags = ${nrf52_base.build_flags} -D PIN_BUZZER=21 -D PIN_BOARD_SDA=PIN_WIRE_SDA -D PIN_BOARD_SCL=PIN_WIRE_SCL -build_src_filter = ${nrf52_base.build_src_filter} +build_src_filter = ${rak4631_hw.build_src_filter} +<../variants/rak_wismesh_tag> + + + lib_deps = - ${nrf52_base.lib_deps} + ${rak4631_hw.lib_deps} ${sensor_base.lib_deps} [env:RAK_WisMesh_Tag_repeater]