From e7f05eec5200c24154e3ccd5e8b95ce23b9758c7 Mon Sep 17 00:00:00 2001 From: Valentin Kivachuk Burda Date: Sun, 28 Jun 2026 18:53:23 +0200 Subject: [PATCH] Fix layout and metatools --- .github/workflows/dev-firmware-rolling.yml | 8 +- docs/ota_protocol.md | 108 ++--- docs/ota_user_guide.md | 8 +- src/helpers/ota/FirmwareInfo.cpp | 16 +- src/helpers/ota/FirmwareInfo.h | 6 +- src/helpers/ota/MotaContainer.cpp | 28 +- src/helpers/ota/MotaContainer.h | 20 +- src/helpers/ota/OtaApply.cpp | 4 +- src/helpers/ota/OtaContext.h | 4 +- src/helpers/ota/OtaFormat.h | 20 +- src/helpers/ota/OtaManager.cpp | 2 +- src/helpers/ota/OtaManager.h | 2 +- src/helpers/ota/OtaSelf.cpp | 35 +- test/test_ota/mota_vectors.h | 48 +- test/test_ota/test_ota_core.cpp | 49 +- tools/mota/README.md | 132 ++---- tools/mota/dev_motas.py | 37 +- tools/mota/mota.py | 259 ----------- tools/mota/mota_seeder.py | 212 --------- tools/mota/motalib.py | 120 ++--- tools/mota/test_mota.py | 19 +- tools/motatool/.gitignore | 1 + tools/motatool/CMakeLists.txt | 37 ++ tools/motatool/README.md | 113 +++++ tools/motatool/src/crypto.cpp | 61 +++ tools/motatool/src/crypto.h | 28 ++ tools/motatool/src/input.cpp | 34 ++ tools/motatool/src/input.h | 15 + tools/motatool/src/main.cpp | 493 +++++++++++++++++++++ tools/motatool/src/mota.cpp | 314 +++++++++++++ tools/motatool/src/mota.h | 79 ++++ tools/motatool/src/mota_format.h | 90 ++++ tools/motatool/src/serve.cpp | 209 +++++++++ tools/motatool/src/serve.h | 68 +++ tools/motatool/src/util.h | 77 ++++ tools/motatool/tests/test_motatool.cpp | 274 ++++++++++++ 36 files changed, 2189 insertions(+), 841 deletions(-) delete mode 100644 tools/mota/mota.py delete mode 100755 tools/mota/mota_seeder.py create mode 100644 tools/motatool/.gitignore create mode 100644 tools/motatool/CMakeLists.txt create mode 100644 tools/motatool/README.md create mode 100644 tools/motatool/src/crypto.cpp create mode 100644 tools/motatool/src/crypto.h create mode 100644 tools/motatool/src/input.cpp create mode 100644 tools/motatool/src/input.h create mode 100644 tools/motatool/src/main.cpp create mode 100644 tools/motatool/src/mota.cpp create mode 100644 tools/motatool/src/mota.h create mode 100644 tools/motatool/src/mota_format.h create mode 100644 tools/motatool/src/serve.cpp create mode 100644 tools/motatool/src/serve.h create mode 100644 tools/motatool/src/util.h create mode 100644 tools/motatool/tests/test_motatool.cpp diff --git a/.github/workflows/dev-firmware-rolling.yml b/.github/workflows/dev-firmware-rolling.yml index d33db75b..d1ccf6ec 100644 --- a/.github/workflows/dev-firmware-rolling.yml +++ b/.github/workflows/dev-firmware-rolling.yml @@ -84,12 +84,18 @@ jobs: - name: Package .mota (full + same-image delta) run: | set -euo pipefail - pip install --quiet detools intelhex + pip install --quiet detools intelhex # detools: delta encoder (motatool shells to it); intelhex: nRF52 .hex extraction + # build the motatool packager (C++; OpenSSL for sha256+ed25519) + sudo apt-get update -qq + sudo apt-get install -y --no-install-recommends libssl-dev cmake g++ >/dev/null + cmake -S tools/motatool -B tools/motatool/build -DCMAKE_BUILD_TYPE=Release >/dev/null + cmake --build tools/motatool/build -j"$(nproc)" >/dev/null SHA=$(git rev-parse --short HEAD) python3 tools/mota/dev_motas.py \ --env "${{ matrix.env }}" --platform "${{ matrix.platform }}" \ --build-dir ".pio/build/${{ matrix.env }}" --target-env "${{ matrix.env }}" \ --out-prefix "out/${{ matrix.env }}-${FIRMWARE_VERSION}-${SHA}" --work /tmp \ + --motatool "$PWD/tools/motatool/build/motatool" \ || echo "::warning::.mota packaging failed for ${{ matrix.env }}" - name: Upload this board's artifact diff --git a/docs/ota_protocol.md b/docs/ota_protocol.md index d5a5fb4f..830fc745 100644 --- a/docs/ota_protocol.md +++ b/docs/ota_protocol.md @@ -1,4 +1,4 @@ -# MeshCore OTA — `.mota` container & LoRa protocol (v2) +# MeshCore OTA — `.mota` container & LoRa protocol This is the **single source of truth** for MeshCore's over-the-air firmware update system ("mOTA"). It is written for developers who want to implement an interoperable peer (server, fetcher, relay, or host tool) @@ -33,14 +33,14 @@ where a section names a source file, that file is the authoritative reference fo | Staging stores | `OtaStore.h`, `OtaStoreFlashNrf52.*`, `OtaStoreFlashEsp32.*` | | Apply | `OtaApply.*`, bootloader `Adafruit_nRF52_Bootloader_OTAFIX` | | Device glue (CLI/context) | `OtaCli.cpp`, `OtaContext.h` | -| Host tooling | `tools/mota/` (`mota.py`, `motalib.py`, `mota_seeder.py`) | +| Host tooling | `tools/motatool/` (C++ CLI: build/verify/inspect/serve); `tools/mota/` (Python reference lib `motalib.py` + build/test glue) | --- ## 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`. v2 uses +- **Hashes (multihash):** the hash family is declared once per manifest via `hash_algo` = `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, @@ -73,36 +73,32 @@ where a section names a source file, that file is the authoritative reference fo ## 2. Firmware image & the `EndF` trailer -Every OTA-capable build appends an `EndF` trailer to its flashed image so a running node can discover its -own size **and self-describing identity** on any MCU (no linker symbols needed). Implemented by -`FirmwareInfo.cpp`; appended at build time by `tools/mota/pio_endf.py` (post-build hook). +Every OTA-capable build appends a fixed **56-byte** `EndF` trailer to its flashed image so a running node +can discover its own size **and self-describing identity** on any MCU (no linker symbols needed). Every +field is always present at a constant offset. Implemented by `FirmwareInfo.cpp`; appended at build time by +`tools/mota/pio_endf.py` (post-build hook). ``` flashed image = BODY (image bytes) || EndF trailer -EndF trailer: +EndF trailer (fixed 56 bytes): off 0 4 "EndF" 45 6E 64 46 off 4 4 body_len uint32 LE — length of BODY (excludes the whole trailer) off 8 8 body_hash sha2-256:8 of BODY - --- the 16 bytes above are the whole (legacy) trailer; the identity block below is optional: --- - off 16 4 "EnFx" 45 6E 46 78 — present iff this is an extended (identity) trailer - off 20 4 fw_version uint32 LE, packed MAJOR<<24|MINOR<<16|PATCH<<8|pre - off 24 4 target_id uint32 LE — sha2-256:4(pio_env): hardware + role + partition (fetch routing) - off 28 32 hw_id NUL-padded ASCII hardware tag (brick-safety), e.g. "RAK4631" - --- extended trailer = 60 bytes --- + off 16 4 fw_version uint32 LE, packed MAJOR<<24|MINOR<<16|PATCH<<8|pre (0 = unknown) + off 20 4 target_id uint32 LE — sha2-256:4(pio_env): hardware + role + partition (fetch routing) + off 24 32 hw_id NUL-padded ASCII hardware tag (brick-safety), e.g. "RAK4631" ("" = unknown) ``` -- **Self-describing identity (extended trailer).** `pio_endf.py` computes `target_id` from the PlatformIO - env name itself (so it's correct even without `build.sh`'s `-D MOTA_TARGET_ID`), `hw_id` from `MOTA_HW_ID`, - and `fw_version` from `FIRMWARE_VERSION`. The device reads them back (`ota_self_firmware()`), so a node's - advertised identity is correct regardless of how it was built — and the packaging tool reads them straight - from a raw `.bin` (no `--target-env`/`--fw-version` flags, no reliance on filenames; §9, §13). -- **Backward-compatible:** the first 16 bytes are unchanged, so the bootloader and any legacy reader (which - read only `[marker, marker+16)`) are unaffected by the extension. A reader detects the extension by the - `EnFx` magic at `+16`; absence ⇒ a 16-byte legacy trailer (identity unknown). +- **Self-describing identity.** `pio_endf.py` computes `target_id` from the PlatformIO env name itself (so + it's correct even without `build.sh`'s `-D MOTA_TARGET_ID`), `hw_id` from `MOTA_HW_ID`, and `fw_version` + from `FIRMWARE_VERSION`. The device reads them back (`ota_self_firmware()`), so a node's advertised + identity is correct regardless of how it was built — and the packaging tool reads them straight from a raw + `.bin` (no `--target-env`/`--fw-version` flags, no reliance on filenames; §9, §13). A dev build with no + dotted version simply carries `fw_version = 0` / empty `hw_id` (= unknown) — still a full 56-byte trailer. - **Size discovery:** scan flash from the partition top downward for the `EndF` marker; the byte before it - is the last BODY byte. The trailer is 60 bytes when `EnFx` follows, else 16. (See `ota_self_firmware()`.) + is the last BODY byte (the trailer is always 56 bytes). See `ota_self_firmware()`. - **Delta base matching:** a node's `body_hash` is read directly from its own `EndF`; a delta's `base_hash` - (§5) must equal it. `body_hash` is over BODY only, so it is identical whether the trailer is 16 or 60 bytes. + (§5) must equal it. `body_hash` is over BODY only. - **No circularity:** `EndF` hashes only the BODY, never itself. The "reconstructed image" referenced by the manifest is the full `BODY || EndF` (what gets flashed). @@ -122,7 +118,7 @@ off size field 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 MANIFEST (§4; M = 197 fixed + leaves[], 4*BC; no length field — BC from payload_size) 8 + M P PAYLOAD (payload_size bytes; delta or full image) 8 + M + P 5 TRAILER = 76 6B 34 39 36 ``` @@ -139,8 +135,10 @@ is immutable. ## 4. The manifest -Fields serialized in this exact order; conditional fields present per `flags`. Fixed head is **89 bytes** -(through `hw_id`). Parsed by `mota_parse_manifest()`. +**Fixed layout.** Every field sits at a constant offset and is always present — `base_hash`, +`signer_pubkey` and `signature` are zero-filled when not applicable (a full image / an unsigned container). +Only `leaves[]` is variable (one 4-byte hash per block). So the manifest-minus-leaves (`mfl`) is **always +197 bytes** and the parser is plain offset reads — no conditionals. Parsed by `mota_parse_manifest()`. ``` off size field notes @@ -157,25 +155,25 @@ off size field notes 56 1 codec_id 0=full/raw, 1=detools-sequential, 2=detools-in-place 57 32 hw_id NUL-padded ASCII hardware tag (e.g. "RAK4631"); same tag => bootable-compatible. SIGNED. Applier refuses a mismatch (brick-safety); empty on either side = skip. ---- end of fixed 89-byte head --- -89 8 base_hash [iff !FULL] sha2-256:8 of the BASE image's BODY (== that build's EndF.body_hash) -. 32 signer_pubkey [iff SIGNED] Ed25519 public key -. 64 signature [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 ---- end of manifest-minus-leaves (mfl); leaves_off = 8 + mfl in the container --- -. 4*BC leaves[] ALWAYS present. BC = ceil(payload_size / 2^block_size_log2). sha2-256:4 each +89 8 base_hash sha2-256:8 of the BASE image's BODY (== that build's EndF.body_hash). 0 if FULL. +97 32 signer_pubkey Ed25519 public key. 0 if not SIGNED. +129 64 signature Ed25519 over manifest[0, 129). 0 if not SIGNED. +193 4 approval FF FF FF FF = not approved; 41 50 52 56 ("APRV") = approved +--- end of manifest-minus-leaves: mfl = 197 (constant); leaves_off = 8 + 197 = 205 in the container --- +197 4*BC leaves[] BC = ceil(payload_size / 2^block_size_log2). sha2-256:4 each (the only variable field) ``` -Self-delimiting: every offset is known from `flags` + `payload_size` (→ `BC`); no length field is stored. +The signature always covers `manifest[0, 129)` (the head + `base_hash` + `signer_pubkey`). `approval` is +outside the signed region so it can be flipped in place on consent without breaking the signature. -Manifest-minus-leaves size (`mfl`): unsigned-full `89+4 = 93`, signed-full `189`, unsigned-delta `101`, -**signed-delta `197`**. A signed manifest exceeds one packet, so `OTA_MANIFEST` is sent multi-fragment -(§8.4) and reassembled by the fetcher. +Manifest-minus-leaves size (`mfl`) is a constant **197 bytes** for every container (full or delta, signed +or unsigned). At 197 bytes the manifest exceeds one packet, so `OTA_MANIFEST` is always sent multi-fragment +(§8.4, 2 fragments) and reassembled by the fetcher. ### 4.1 Signed region -`signature` covers manifest bytes `[0, signature_offset)` — everything before it, including `signer_pubkey` -and (for deltas) `base_hash`. It does **not** cover `approval` or `leaves[]`: +`signature` covers manifest bytes `[0, 129)` — the head + `base_hash` + `signer_pubkey`. 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 (§4.2), deliberately outside the signature. @@ -437,10 +435,10 @@ All serving stays reactive and lowest-priority, so seeding never competes with r - **`target_id`** (4 B): `sha2-256:4(pio_env_name)` (little-endian uint32). The env name uniquely captures hardware **and** role/partition, so a node auto-fetches only matching firmware (a companion image is not fetched onto a repeater even though it shares `hw_id`). It is **self-described in the firmware's EndF** - (§2, written by `pio_endf.py`) and read via `ota_self_firmware()`, so it is correct on any build; the - legacy `-D MOTA_TARGET_ID` / `MainBoard::getOtaTargetId()` path is the fallback. `tools/mota` reads it from - the firmware's EndF (or `--target-env`). A manual `ota pull`/`want` can override target (deliberate role - switch); the `hw_id` brick-safety gate (§4) still applies at apply time. + (§2, written by `pio_endf.py`) and read via `ota_self_firmware()`, so it is correct on any build; + `-D MOTA_TARGET_ID` / `MainBoard::getOtaTargetId()` is the fallback when no EndF identity is present. + `tools/mota` reads it from the firmware's EndF (or `--target-env`). A manual `ota pull`/`want` can override + target (deliberate role switch); the `hw_id` brick-safety gate (§4) still applies at apply time. - **`target_id` vs `hw_id`** — complementary, not redundant: `target_id` is the fetch-routing key (hw + role + partition); `hw_id` is the human-readable brick-safety key (hardware only). Same board, two roles ⇒ same `hw_id`, different `target_id`. @@ -493,10 +491,11 @@ blocks) and streams payload blocks from the source on demand; proofs are generat ### 10.2 The `mota-seeder` transport (`MotaSeederProto.h`) -The first concrete `MotaSource` is a host daemon (`tools/mota/mota_seeder.py`) serving a folder over the -device's **USB serial — the same console the CLI uses** (no extra hardware). The device only emits request -frames *while actively serving a fetch*, and reads the reply synchronously, so binary frames coexist with -the text CLI/logs (resync on magic + checksum). Little-endian, XOR-checksummed: +A `MotaSource` is fed by a host that serves a folder over the device's **USB serial — the same console the +CLI uses** (no extra hardware). The host is the self-contained C++ tool `tools/motatool/` (`motatool serve`, +which also builds + validates `.mota` and runs on small hardware). The device only emits request frames +*while actively serving a fetch*, and reads the reply synchronously, so binary frames coexist with the text +CLI/logs (resync on magic + checksum). Little-endian, XOR-checksummed: ``` request (device → host): 'M' 'S' op(1) args... xsum(1 = XOR of op+args) @@ -515,6 +514,14 @@ Device CLI: `ota folder on` (attach + announce), `ota folder` (list), `ota folde `OTA_FOLDER_SERIAL_BEGIN` for a dedicated UART). Verified on hardware: a RAK4631 relays a host folder to a Heltec V3 over one USB cable, every block merkle-checked. +**Transport-agnostic by design.** The request/response *semantics* (`COUNT` / `DESCRIBE(idx)` / +`READ(idx, off, len)` over a folder catalog) are independent of the link. The 2-byte magic + XOR checksum + +resync framing above exists only because a shared USB-UART is an unreliable, unframed byte stream. Over a +framed/reliable link such as **BLE GATT** (e.g. an Android phone relaying a folder to a node), the same ops +carry over directly — a request characteristic write delivers `op + args` and the reply is a notification +of `status + payload`, with no magic/checksum needed. `motatool` reflects this split: a transport-free +`SeederCore` (the catalog logic) under a serial framing layer, so a BLE transport reuses the core verbatim. + --- ## 11. CLI surface (`OtaCli.cpp`) @@ -566,6 +573,7 @@ The signature proves author authenticity; `approval` proves local owner consent ## 13. Versioning of this spec -`format_ver = 2` (v2 added `hw_id` to the signed head and split discovery/transfer as in §8/§10). 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). +`format_ver = 2`. A parser accepts exactly this value and rejects anything else — there is one container +format, fixed-layout, and no compatibility shims to carry. If the format ever needs to change, bump +`format_ver`; the multihash `hash_algo` separately allows swapping the digest family without a format +bump. Unknown `codec_id` / `ota_msg_type` values are ignored (a node simply won't fetch what it can't apply). diff --git a/docs/ota_user_guide.md b/docs/ota_user_guide.md index 3255b98b..e078c6a9 100644 --- a/docs/ota_user_guide.md +++ b/docs/ota_user_guide.md @@ -162,13 +162,13 @@ folder of firmware files to the mesh — without storing them itself. Useful for remote area. 1. Put the firmware files (`.mota` files — see below) in a folder on the computer. -2. Run the helper daemon, pointing it at your node's USB port and the folder: +2. Build the helper tool once (`tools/motatool/`), then point it at your node's USB port and the folder: ``` - pip install pyserial - ./tools/mota/mota_seeder.py --port /dev/ttyACM0 --dir ./my_firmware/ -v + cmake -S tools/motatool -B tools/motatool/build && cmake --build tools/motatool/build + ./tools/motatool/build/motatool serve --dir ./my_firmware/ --serial /dev/ttyACM0 -v ``` It turns the relay on for you and then answers the node's requests. Your node now advertises those - updates to neighbours, who can `ota get` them like any other. (Details: [tools/mota/README.md](../tools/mota/README.md).) + updates to neighbours, who can `ota get` them like any other. (Details: [tools/motatool/README.md](../tools/motatool/README.md).) To turn it off, stop the daemon (or run `ota folder off` on the node). `ota folder` on its own lists what your node is currently offering. diff --git a/src/helpers/ota/FirmwareInfo.cpp b/src/helpers/ota/FirmwareInfo.cpp index b2b35c9d..38a2a869 100644 --- a/src/helpers/ota/FirmwareInfo.cpp +++ b/src/helpers/ota/FirmwareInfo.cpp @@ -30,16 +30,12 @@ bool find_self_firmware(const uint8_t* region, uint32_t region_len, out.body_len = body_len; out.image_len = off + ENDF_LEN; memcpy(out.body_hash, region + off + 8, 8); - // Extended EndF? An "EnFx" block right after body_hash carries self-describing identity. The trailer is - // then 60 bytes, so the reconstructed image extends to off + ENDF_EXT_LEN. - if (off + ENDF_EXT_LEN <= region_len && memcmp(region + off + 16, ENDF_EXT_MAGIC, 4) == 0) { - out.has_ident = true; - out.fw_version = rd_u32(region + off + 20); - out.target_id = rd_u32(region + off + 24); - memcpy(out.hw_id, region + off + 28, 32); - out.hw_id[32] = 0; - out.image_len = off + ENDF_EXT_LEN; - } + // Fixed 56-byte trailer: the self-describing identity follows body_hash at constant offsets + // (fw_version@16, target_id@20, hw_id@24..56). Zero/"" means "unknown". + out.fw_version = rd_u32(region + off + 16); + out.target_id = rd_u32(region + off + 20); + memcpy(out.hw_id, region + off + 24, 32); + out.hw_id[32] = 0; return true; } return false; diff --git a/src/helpers/ota/FirmwareInfo.h b/src/helpers/ota/FirmwareInfo.h index a6231fa9..a08bdc75 100644 --- a/src/helpers/ota/FirmwareInfo.h +++ b/src/helpers/ota/FirmwareInfo.h @@ -14,11 +14,11 @@ namespace ota { struct SelfFwInfo { bool valid = false; uint32_t body_len = 0; // firmware body length (excludes the EndF trailer) - uint32_t image_len = 0; // body_len + trailer length (what a delta base / full image hashes over) + 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) - // self-describing identity (extended EndF only; has_ident=false on a legacy 16-byte trailer) - bool has_ident = false; + // Self-describing identity — always present in the fixed 56-byte trailer (zero/"" means "unknown", + // e.g. a dev build with no dotted version). uint32_t fw_version = 0; // packed MAJOR<<24|MINOR<<16|PATCH<<8|pre uint32_t target_id = 0; // sha2-256:4(env) as uint32 — hw+role+partition (fetch routing) char hw_id[33] = {0}; // readable hardware tag (NUL-terminated), e.g. "RAK4631" diff --git a/src/helpers/ota/MotaContainer.cpp b/src/helpers/ota/MotaContainer.cpp index d2133b0b..3a344424 100644 --- a/src/helpers/ota/MotaContainer.cpp +++ b/src/helpers/ota/MotaContainer.cpp @@ -15,11 +15,11 @@ bool MotaManifest::is_approved() const { return approval && memcmp(approval, APPROVAL_YES, 4) == 0; } -// Read the manifest's fixed head + conditional/variable fields from a cursor (shared by the full-container -// and standalone-manifest parsers). Reads each field by name in declaration order (docs/ota_protocol.md §4); -// `signed_off` is the cursor base the signature is measured from (manifest_start). Leaves/payload (only in -// a full container) are read by the caller. Returns false on any over-read or bad format_ver. -static bool parse_manifest_fields(ByteReader& r, uint32_t signed_off, MotaManifest& out) { +// Fixed-layout parse (docs/ota_protocol.md §4): every field sits at a constant offset — base_hash(8), +// signer_pubkey(32) and signature(64) are ALWAYS present (zero-filled when not applicable), so there are +// no conditionals. Only leaves[]/payload (after `approval`) is variable, read by the caller. The signature +// always covers manifest[0, MOTA_SIGNED_LEN). Returns false on any over-read or bad format_ver. +static bool parse_manifest_fields(ByteReader& r, MotaManifest& out) { out.format_ver = r.u8(); if (out.format_ver != MOTA_FORMAT_VER) return false; out.flags = r.u8(); @@ -33,15 +33,11 @@ static bool parse_manifest_fields(ByteReader& r, uint32_t signed_off, MotaManife out.image_hash = r.take(32); out.codec_id = r.u8(); out.hw_id = r.take(32); // 32-byte NUL-padded hardware tag (signed) - if (!out.is_full()) out.base_hash = r.take(8); - if (out.is_signed()) { - out.signer_pubkey = r.take(32); - out.signed_len = r.pos() - signed_off; // signature covers manifest_start .. here (exclusive) - out.signature = r.take(64); - } else { - out.signed_len = r.pos() - signed_off; - } - out.approval = r.take(4); + out.base_hash = r.take(8); // always present (zero for a full image) + out.signer_pubkey = r.take(32); // always present (zero when unsigned) + out.signed_len = MOTA_SIGNED_LEN; // signature always covers manifest[0, 129) + out.signature = r.take(64); // always present (zero when unsigned) + out.approval = r.take(4); if (!r.ok) return false; 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(); @@ -59,7 +55,7 @@ bool mota_parse(const uint8_t* buf, uint32_t len, MotaManifest& out) { ByteReader r(buf, len - 5); // everything up to (not incl.) the trailer r.skip(4 + 4); // MAGIC + MOTA_TOTAL_SIZE (already validated) out.manifest_start = buf + 8; - if (!parse_manifest_fields(r, 8, out)) return false; + if (!parse_manifest_fields(r, out)) return false; out.leaves = r.take(out.block_count * 4); out.payload = r.take(out.payload_size); if (!r.ok) return false; @@ -70,7 +66,7 @@ bool mota_parse_manifest(const uint8_t* mf, uint32_t len, MotaManifest& out) { out = MotaManifest(); out.manifest_start = mf; ByteReader r(mf, len); // a standalone manifest = container bytes [8, leaves) - return parse_manifest_fields(r, 0, out); + return parse_manifest_fields(r, out); } bool mota_check_root(const MotaManifest& m) { diff --git a/src/helpers/ota/MotaContainer.h b/src/helpers/ota/MotaContainer.h index 64dc0b7c..4425e62b 100644 --- a/src/helpers/ota/MotaContainer.h +++ b/src/helpers/ota/MotaContainer.h @@ -24,17 +24,19 @@ struct MotaManifest { 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* hw_id = nullptr; // 32 (NUL-padded ASCII hardware tag; signed; v2+) - 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 + // Fixed layout (docs/ota_protocol.md §4): every field below sits at a constant offset and is ALWAYS + // present; base_hash/signer_pubkey/signature are zero-filled when not applicable (full / unsigned). + const uint8_t* merkle_root = nullptr; // 4 @20 + const uint8_t* image_hash = nullptr; // 32 @24 + const uint8_t* hw_id = nullptr; // 32 @57 (NUL-padded ASCII hardware tag; signed) + const uint8_t* base_hash = nullptr; // 8 @89 (zero for a full image) + const uint8_t* signer_pubkey = nullptr; // 32 @97 (zero when unsigned) + const uint8_t* signature = nullptr; // 64 @129 (zero when unsigned) + const uint8_t* approval = nullptr; // 4 @193 + const uint8_t* leaves = nullptr; // 4 * block_count (the only variable-length field) 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) + uint32_t signed_len = 0; // = MOTA_SIGNED_LEN (129): bytes the signature covers bool is_full() const { return flags & MFLAG_FULL; } bool is_signed() const { return flags & MFLAG_SIGNED; } diff --git a/src/helpers/ota/OtaApply.cpp b/src/helpers/ota/OtaApply.cpp index 8b10a0e2..4e7fa075 100644 --- a/src/helpers/ota/OtaApply.cpp +++ b/src/helpers/ota/OtaApply.cpp @@ -95,7 +95,7 @@ struct DetoolsCtx { OtaStoreFlashEsp32* store; // staged container; patch = payload region [patch_base, +patch_len) uint32_t patch_base; // container offset where the payload (patch) begins #else - const uint8_t* patch; // .mota payload held wholly in RAM (legacy RAM store) + const uint8_t* patch; // .mota payload held wholly in RAM (RAM store; bring-up/host) #endif uint32_t patch_len; uint32_t patch_pos; @@ -352,7 +352,7 @@ bool ota_apply_detools_mota(OtaStoreFlashEsp32& store, const SignerAllowlist& al return true; } -#else // !OTA_FLASH_STORE: legacy RAM-staged apply (whole .mota in a contiguous RAM buffer; bring-up) +#else // !OTA_FLASH_STORE: RAM-staged apply (whole .mota in a contiguous RAM buffer; bring-up/host) bool ota_apply_detools_mota(const uint8_t* buf, uint32_t len, const SignerAllowlist& allow, ApplyState& st, char* msg) { diff --git a/src/helpers/ota/OtaContext.h b/src/helpers/ota/OtaContext.h index 117ce9e4..7daf1082 100644 --- a/src/helpers/ota/OtaContext.h +++ b/src/helpers/ota/OtaContext.h @@ -62,7 +62,7 @@ struct OtaContext { // count (the manager's fixed 4 KB scratch only covers <=1024 blocks; a >1 MB image needs more). uint8_t* serve_self_leaves = nullptr; uint8_t* serve_self_proof = nullptr; - uint8_t serve_self_manifest[96]; // v2 full+unsigned manifest = 89 (fixed incl. hw_id) + 4 approval + uint8_t serve_self_manifest[MOTA_MFL]; // fixed-layout full+unsigned manifest-minus-leaves (197 B) ApplyState apply_st; // pending apply (P6) // OTA policy (persisted via NodePrefs; autofetch lives in the manager). Conservative defaults: a fresh @@ -158,7 +158,7 @@ struct OtaContext { // passed — it's correct on any build (build.sh injection, bare IDE build, ...), so `ota ls`/`status` // and fetch-routing show the right hardware/role instead of 0 / "". SelfFwInfo _fi; - if (ota_self_firmware(_fi) && _fi.valid && _fi.has_ident) { + if (ota_self_firmware(_fi) && _fi.valid) { if (_fi.target_id) target_id = _fi.target_id; if (_fi.hw_id[0]) hw = _fi.hw_id; } diff --git a/src/helpers/ota/OtaFormat.h b/src/helpers/ota/OtaFormat.h index 1b7e080d..e9ed72f9 100644 --- a/src/helpers/ota/OtaFormat.h +++ b/src/helpers/ota/OtaFormat.h @@ -15,16 +15,24 @@ namespace ota { 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) -// Extended EndF (docs/ota_protocol.md §2): the 16-byte trailer above, then a self-describing firmware -// identity block. The 16-byte prefix is unchanged, so the bootloader + legacy readers ignore the rest. -static const uint8_t ENDF_EXT_MAGIC[4] = { 'E', 'n', 'F', 'x' }; // 45 6E 46 78 -static const uint32_t ENDF_EXT_LEN = 60; // +EnFx(4)+fw_version(4)+target_id(4)+hw_id(32) +// Fixed 56-byte trailer (docs/ota_protocol.md §2): marker(4) body_len(4) body_hash8(8) + a self-describing +// identity block fw_version(4) target_id(4) hw_id(32). No optional/variable parts. +static const uint32_t ENDF_LEN = 56; // ---- manifest ------------------------------------------------------------- -static const uint8_t MOTA_FORMAT_VER = 2; // v2 adds hw_id[32] (a human-readable hardware tag) +static const uint8_t MOTA_FORMAT_VER = 2; // fixed-layout manifest (see offsets below) static const uint8_t HASH_ALGO_SHA256 = 0x12; // multihash code +// Fixed manifest layout (manifest-minus-leaves) — every field is present at a constant offset, so the +// parser is plain offset reads (docs/ota_protocol.md §4). base_hash/signer_pubkey/signature are always +// present (zero-filled when not applicable); only leaves[] (after `approval`) is variable. +static const uint32_t MOTA_OFF_BASE_HASH = 89; // 8 (zero for a full image) +static const uint32_t MOTA_OFF_SIGNER = 97; // 32 (zero when unsigned) +static const uint32_t MOTA_OFF_SIGNATURE = 129; // 64 (zero when unsigned) — covers manifest[0,129) +static const uint32_t MOTA_OFF_APPROVAL = 193; // 4 +static const uint32_t MOTA_MFL = 197; // manifest-minus-leaves length (constant) +static const uint32_t MOTA_SIGNED_LEN = 129; // bytes the signature covers (manifest[0, signer_end)) + // hw_id: a fixed 32-byte, NUL-padded ASCII string naming the hardware a firmware can boot on (e.g. // "RAK4631", "Heltec_v3"). Same hw_id == bootable-compatible (a role switch on the same board keeps it; // different MCU/board differs). It sits in the SIGNED region of the manifest, so it can't be tampered. diff --git a/src/helpers/ota/OtaManager.cpp b/src/helpers/ota/OtaManager.cpp index a85cb98c..79305cb6 100644 --- a/src/helpers/ota/OtaManager.cpp +++ b/src/helpers/ota/OtaManager.cpp @@ -494,7 +494,7 @@ void OtaManager::handleManifest(const uint8_t* m, uint16_t n) { const uint8_t* mf = _mf_buf; // fully reassembled manifest-minus-leaves uint32_t mfl = _mf_len; - if (mfl < 89) { _fstate = FAILED; return; } // fixed head incl. hw_id[32] + if (mfl != MOTA_MFL) { _fstate = FAILED; return; } // manifest-minus-leaves is a fixed 197 bytes 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]; diff --git a/src/helpers/ota/OtaManager.h b/src/helpers/ota/OtaManager.h index f55e72d9..c60cf2c5 100644 --- a/src/helpers/ota/OtaManager.h +++ b/src/helpers/ota/OtaManager.h @@ -40,7 +40,7 @@ typedef bool (*ServeReadFn)(void* ctx, uint32_t off, uint8_t* buf, uint32_t len) #define OTA_MF_FRAG 176 // manifest bytes per OTA_MANIFEST fragment (<= MAX_PACKET_PAYLOAD - header) #endif #ifndef OTA_MF_MAXFRAG -#define OTA_MF_MAXFRAG 4 // max manifest fragments (a signed v2 manifest is ~2) +#define OTA_MF_MAXFRAG 4 // max manifest fragments (the fixed 197 B manifest is always 2) #endif #ifndef OTA_MANIFEST_MAX_RETRY #define OTA_MANIFEST_MAX_RETRY 20 // give up (FAILED) after this many GET_MANIFEST retries — frees the slot diff --git a/src/helpers/ota/OtaSelf.cpp b/src/helpers/ota/OtaSelf.cpp index 863089db..00092bfa 100644 --- a/src/helpers/ota/OtaSelf.cpp +++ b/src/helpers/ota/OtaSelf.cpp @@ -48,17 +48,14 @@ bool ota_self_firmware(SelfFwInfo& out) { out.body_len = body_len; out.image_len = body_len + ENDF_LEN; memcpy(out.body_hash, buf + i + 8, 8); - // Extended identity? Re-read the full trailer at the marker (it may straddle the chunk window, so - // the EnFx fields aren't reliably in `buf`). docs/ota_protocol.md §2. - uint8_t tr[ENDF_EXT_LEN]; - if (body_len + ENDF_EXT_LEN <= p->size && - esp_partition_read(p, body_len, tr, ENDF_EXT_LEN) == ESP_OK && - memcmp(tr + 16, ENDF_EXT_MAGIC, 4) == 0) { - out.has_ident = true; - out.fw_version = (uint32_t)tr[20] | ((uint32_t)tr[21]<<8) | ((uint32_t)tr[22]<<16) | ((uint32_t)tr[23]<<24); - out.target_id = (uint32_t)tr[24] | ((uint32_t)tr[25]<<8) | ((uint32_t)tr[26]<<16) | ((uint32_t)tr[27]<<24); - memcpy(out.hw_id, tr + 28, 32); out.hw_id[32] = 0; - out.image_len = body_len + ENDF_EXT_LEN; + // Fixed 56-byte trailer: re-read it whole at the marker (it may straddle the chunk window, so the + // identity fields aren't reliably in `buf`) and pull identity from constant offsets (docs §2). + uint8_t tr[ENDF_LEN]; + if (body_len + ENDF_LEN <= p->size && + esp_partition_read(p, body_len, tr, ENDF_LEN) == ESP_OK) { + out.fw_version = (uint32_t)tr[16] | ((uint32_t)tr[17]<<8) | ((uint32_t)tr[18]<<16) | ((uint32_t)tr[19]<<24); + out.target_id = (uint32_t)tr[20] | ((uint32_t)tr[21]<<8) | ((uint32_t)tr[22]<<16) | ((uint32_t)tr[23]<<24); + memcpy(out.hw_id, tr + 24, 32); out.hw_id[32] = 0; } return true; } @@ -167,12 +164,14 @@ bool ota_serve_self(OtaContext& c, uint32_t fw_version) { // Prefer the SELF-DESCRIBING identity embedded in our own EndF (docs §2) over build flags / the param — // it's correct regardless of how the firmware was built (build.sh injection, IDE, etc.). - uint32_t out_target = (fi.has_ident && fi.target_id) ? fi.target_id : c.manager.target(); - uint32_t out_ver = (fi.has_ident && fi.fw_version) ? fi.fw_version : fw_version; - const char* out_hw = (fi.has_ident && fi.hw_id[0]) ? fi.hw_id : c.hw_id; + uint32_t out_target = fi.target_id ? fi.target_id : c.manager.target(); + uint32_t out_ver = fi.fw_version ? fi.fw_version : fw_version; + const char* out_hw = fi.hw_id[0] ? fi.hw_id : c.hw_id; - uint8_t* m = c.serve_self_manifest; // assemble v2 manifest-minus-leaves (full, unsigned) = 93 bytes - memset(m, 0, 96); + // Assemble the fixed-layout manifest-minus-leaves (full, unsigned) = MOTA_MFL bytes. base_hash(89), + // signer_pubkey(97) and signature(129) stay zero-filled (full + unsigned); only `approval` is set. + uint8_t* m = c.serve_self_manifest; + memset(m, 0, MOTA_MFL); m[0] = MOTA_FORMAT_VER; m[1] = MFLAG_FULL; m[2] = HASH_ALGO_SHA256; wr_u32le(m + 3, out_target); wr_u32le(m + 7, out_ver); wr_u32le(m + 11, image_size); wr_u32le(m + 15, image_size); // full: payload == image @@ -181,8 +180,8 @@ bool ota_serve_self(OtaContext& c, uint32_t fw_version) { memcpy(m + 24, image_hash, 32); m[56] = CODEC_FULL; memcpy(m + 57, out_hw, strlen(out_hw) < 32 ? strlen(out_hw) : 32); // hw_id[32] (NUL-padded by memset) - memcpy(m + 89, APPROVAL_NOT, 4); // approval marker (fetching device's apply-gate handles it) - return c.manager.serve_self(m, 93, c.serve_self_leaves, bc, + memcpy(m + MOTA_OFF_APPROVAL, APPROVAL_NOT, 4); // approval (fetching device's apply-gate handles it) + return c.manager.serve_self(m, MOTA_MFL, c.serve_self_leaves, bc, c.serve_self_proof, (size_t)bc * 4, self_read_cb, nullptr); } #endif diff --git a/test/test_ota/mota_vectors.h b/test/test_ota/mota_vectors.h index 6c97172b..48a1e205 100644 --- a/test/test_ota/mota_vectors.h +++ b/test/test_ota/mota_vectors.h @@ -3,25 +3,25 @@ #pragma once #include -static const uint8_t MOTA_VEC[5403] = {109,79,84,65,27,21,0,0,2,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,84,69,83,84,72,87,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,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 uint8_t MOTA_VEC[5547] = {109,79,84,65,171,21,0,0,2,1,18,68,51,34,17,0,0,16,1,193,20,0,0,193,20,0,0,10,242,21,180,56,232,121,143,217,6,12,12,64,164,54,242,61,17,64,7,235,26,78,104,183,219,50,235,76,151,230,187,56,8,129,160,136,0,84,69,83,84,72,87,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,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,106,86,0,67,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,118,107,52,57,54}; -static const uint32_t MOTA_VEC_LEN = 5403; +static const uint32_t MOTA_VEC_LEN = 5547; 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_IMAGE_SIZE = 5313u; +static const uint32_t EXP_PAYLOAD_SIZE = 5313u; 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_MERKLE_ROOT[4] = {242,21,180,56}; -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 uint8_t EXP_IMAGE_HASH[32] = {232,121,143,217,6,12,12,64,164,54,242,61,17,64,7,235,26,78,104,183,219,50,235,76,151,230,187,56,8,129,160,136}; static const uint8_t EXP_HW_ID[32] = {84,69,83,84,72,87,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; 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 uint8_t PROOF_SIBLINGS[12] = {47,111,103,108,64,78,181,132,54,247,159,221}; 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}; @@ -90,33 +90,33 @@ struct ProofCase { uint32_t count; const uint8_t* leaves; const uint8_t* root; c 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[2282] = {109,79,84,65,234,8,0,0,2,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,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,10,236,152,1,18,58,147,234,161,120,150,207,56,96,162,64,219,140,87,35,208,226,207,51,131,232,66,53,29,184,184,194,188,102,219,160,122,189,147,137,106,15,117,213,226,47,113,74,225,6,38,149,121,212,104,94,79,103,89,198,78,159,234,5,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 uint8_t SIM_MOTA[2334] = {109,79,84,65,30,9,0,0,2,3,18,190,186,254,202,0,0,0,3,8,8,0,0,8,8,0,0,7,97,190,149,151,182,60,80,58,200,234,98,44,193,140,71,76,243,130,97,209,39,43,50,65,54,148,88,72,127,67,165,233,114,102,17,129,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,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,38,98,213,118,187,206,236,168,51,174,162,73,89,30,161,211,77,42,34,60,159,243,83,241,213,243,188,72,94,109,154,234,68,70,87,185,215,226,224,96,146,252,13,183,118,91,107,134,241,252,255,206,215,103,126,185,188,114,49,17,62,148,194,5,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,30,57,43,99,175,85,112,245,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,118,107,52,57,54}; -static const uint32_t SIM_MOTA_LEN = 2282; +static const uint32_t SIM_MOTA_LEN = 2334; static const uint32_t SIM_TARGET_ID = 0xcafebabeu; // 1 KB-block signed .mota (multi-fragment per block) for the reassembly transfer test -static const uint8_t SIM_MOTA_1K[2226] = {109,79,84,65,178,8,0,0,2,3,18,190,186,254,202,0,0,0,3,224,7,0,0,224,7,0,0,10,141,137,148,65,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,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,86,37,208,73,247,78,10,175,137,157,152,31,13,197,254,154,244,76,218,60,107,47,204,6,35,236,179,222,6,186,219,31,25,4,204,133,252,129,92,9,170,81,104,32,162,53,42,224,0,80,253,196,208,99,214,76,157,180,57,31,197,116,45,1,255,255,255,255,226,102,124,83,175,206,204,60,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 uint8_t SIM_MOTA_1K[2278] = {109,79,84,65,230,8,0,0,2,3,18,190,186,254,202,0,0,0,3,8,8,0,0,8,8,0,0,10,164,51,202,103,182,60,80,58,200,234,98,44,193,140,71,76,243,130,97,209,39,43,50,65,54,148,88,72,127,67,165,233,114,102,17,129,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,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,171,129,180,182,69,0,212,122,174,47,219,250,34,204,198,63,40,244,119,210,21,11,30,188,119,170,189,46,247,90,166,169,6,102,11,226,54,97,112,202,185,10,171,131,170,250,246,20,84,173,222,226,146,215,83,51,164,107,48,67,2,52,40,1,255,255,255,255,226,102,124,83,43,137,26,96,175,85,112,245,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,118,107,52,57,54}; -static const uint32_t SIM_MOTA_1K_LEN = 2226; -static const uint32_t SIM_MOTA_1K_BLOCKS = 2; +static const uint32_t SIM_MOTA_1K_LEN = 2278; +static const uint32_t SIM_MOTA_1K_BLOCKS = 3; // 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 uint8_t DT_BASE[3056] = {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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; -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_BASE_LEN = 3056; +static const uint8_t DT_PATCH[256] = {2,184,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,219,1,129,4,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,1,40,0,0,1,56}; -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_PATCH_LEN = 256; +static const uint8_t DT_TARGET[3256] = {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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; -static const uint32_t DT_TARGET_LEN = 3216; +static const uint32_t DT_TARGET_LEN = 3256; // 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 uint8_t DT_IP_BASE[3056] = {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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; -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_BASE_LEN = 3056; +static const uint8_t DT_IP_PATCH[266] = {18,128,128,4,128,64,128,192,3,176,47,184,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,219,1,129,4,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,1,40,0,0,1,56}; -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_PATCH_LEN = 266; +static const uint8_t DT_IP_TARGET[3256] = {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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; -static const uint32_t DT_IP_TARGET_LEN = 3216; +static const uint32_t DT_IP_TARGET_LEN = 3256; 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 index c0ff2443..bd3b8c83 100644 --- a/test/test_ota/test_ota_core.cpp +++ b/test/test_ota/test_ota_core.cpp @@ -20,16 +20,26 @@ extern "C" { 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) { +// Build a flashed-image layout (body || fixed 56-byte EndF) the way the host packager / build hook do: +// marker(4) body_len(4) body_hash8(8) fw_version(4) target_id(4) hw_id(32). Identity is always present +// (zero/"" = unknown). +static std::vector make_image_id(const std::vector& body, uint32_t fw_version, + uint32_t target_id, const char* hw_id) { 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); + for (int i = 0; i < 4; i++) img.push_back((uint8_t)(fw_version >> (8 * i))); + for (int i = 0; i < 4; i++) img.push_back((uint8_t)(target_id >> (8 * i))); + uint8_t hw[32] = {0}; size_t k = hw_id ? strlen(hw_id) : 0; if (k > 32) k = 32; if (k) memcpy(hw, hw_id, k); + img.insert(img.end(), hw, hw + 32); // -> fixed 56-byte trailer return img; } +static std::vector make_image(const std::vector& body) { + return make_image_id(body, 0, 0, ""); // zero identity (still a full 56-byte trailer) +} // --- cross-check the C++ parser/merkle against the Python reference vectors ---------------- @@ -73,8 +83,8 @@ TEST(OtaParse, RejectsTampering) { // block_idx is a uint16 on the wire, so a manifest needing > 65535 blocks can't be addressed and must be // rejected at parse (this also keeps block_count*4 from overflowing the leaves-length computation). TEST(OtaParse, RejectsTooManyBlocks) { - auto manifest = [](uint32_t payload_size, uint8_t bsl) { // minimal unsigned-full manifest (93 B) - std::vector m(93, 0); + auto manifest = [](uint32_t payload_size, uint8_t bsl) { // fixed-layout unsigned-full manifest + std::vector m(MOTA_MFL, 0); m[0] = MOTA_FORMAT_VER; m[1] = MFLAG_FULL; m[2] = 0x12; m[15] = payload_size; m[16] = payload_size >> 8; m[17] = payload_size >> 16; m[18] = payload_size >> 24; m[19] = bsl; @@ -232,41 +242,30 @@ TEST(OtaFirmwareInfo, FindsEndFInImage) { EXPECT_EQ(0, std::memcmp(fi.body_hash, h, 8)); } -// Build a body || EXTENDED EndF (identity-carrying), the way pio_endf / motalib do. -static std::vector make_image_v2(const std::vector& body, uint32_t fw_version, - uint32_t target_id, const char* hw_id) { - std::vector img = make_image(body); // body + 16-byte base trailer - static const uint8_t EXT[4] = {'E','n','F','x'}; - img.insert(img.end(), EXT, EXT + 4); - for (int i = 0; i < 4; i++) img.push_back((uint8_t)(fw_version >> (8 * i))); - for (int i = 0; i < 4; i++) img.push_back((uint8_t)(target_id >> (8 * i))); - uint8_t hw[32] = {0}; size_t n = strlen(hw_id); if (n > 32) n = 32; memcpy(hw, hw_id, n); - img.insert(img.end(), hw, hw + 32); // -> 60-byte extended trailer - return img; -} - -// The self-describing identity in an extended EndF is parsed; a legacy 16-byte trailer reports no identity. -TEST(OtaFirmwareInfo, ParsesExtendedIdentity) { +// The self-describing identity lives at fixed offsets in the 56-byte EndF and is always parsed; a +// zero-identity trailer reports zero/"" (unknown), still at the fixed 56-byte size. +TEST(OtaFirmwareInfo, ParsesIdentity) { std::vector body(2000); for (size_t i = 0; i < body.size(); i++) body[i] = (uint8_t)(i * 13 + 5); - auto img = make_image_v2(body, 0x01100000u, 0x04d413fdu, "RAK4631"); + auto img = make_image_id(body, 0x01100000u, 0x04d413fdu, "RAK4631"); 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, body.size() + 60); // extended trailer length - EXPECT_TRUE(fi.has_ident); + EXPECT_EQ(fi.image_len, body.size() + 56); // fixed trailer length EXPECT_EQ(fi.fw_version, 0x01100000u); EXPECT_EQ(fi.target_id, 0x04d413fdu); EXPECT_STREQ(fi.hw_id, "RAK4631"); - auto img1 = make_image(body); // legacy 16-byte trailer + auto img1 = make_image(body); // zero-identity trailer (still 56 bytes) std::vector r1 = img1; r1.resize(img1.size() + 64, 0xFF); SelfFwInfo fi1; ASSERT_TRUE(find_self_firmware(r1.data(), (uint32_t)r1.size(), fi1, true)); - EXPECT_FALSE(fi1.has_ident); - EXPECT_EQ(fi1.image_len, body.size() + 16); + EXPECT_EQ(fi1.fw_version, 0u); + EXPECT_EQ(fi1.target_id, 0u); + EXPECT_STREQ(fi1.hw_id, ""); + EXPECT_EQ(fi1.image_len, body.size() + 56); } TEST(OtaFirmwareInfo, IgnoresStagedMotaHigherInRegion) { diff --git a/tools/mota/README.md b/tools/mota/README.md index d65a080c..e5417d0d 100644 --- a/tools/mota/README.md +++ b/tools/mota/README.md @@ -1,122 +1,54 @@ -# `mota` — MeshCore OTA packaging tool +# `tools/mota/` — OTA Python reference library & build/test tooling -Host-side tooling for building and validating `.mota` firmware-update containers. -Implements the wire spec in [`docs/ota_protocol.md`](../../docs/ota_protocol.md) — the single source of -truth for the `.mota` format and the OTA-over-LoRa protocol. +The Python side of MeshCore's `.mota` OTA system. It is the **reference implementation** of the wire +spec ([`docs/ota_protocol.md`](../../docs/ota_protocol.md)) and the **build + test infrastructure** — it is +no longer a user-facing CLI. + +> **Want to build / verify / inspect / serve `.mota` from the command line?** Use the self-contained C++ +> tool [`tools/motatool/`](../motatool/). It supersedes the old `mota.py` / `mota_seeder.py` (now removed), +> produces byte-identical containers, and runs on small hardware. The files here are the spec oracle and +> the firmware build/test glue. ## Setup -Uses the repo's Python venv (`meshcore/`). Dependencies: `detools` (delta), `cryptography` (Ed25519). +Uses the repo's Python venv (`meshcore/`). Dependencies: `detools` (delta), `cryptography` (Ed25519), +`intelhex` (nRF52 `.hex` handling). ```bash -./meshcore/bin/pip install detools cryptography +./meshcore/bin/pip install detools cryptography intelhex ``` ## 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). -- **Self-describing identity (no flags / no filenames).** A firmware built by `pio_endf.py` carries its - `target_id`, `fw_version` and `hw_id` in an extended `EndF` trailer (docs/ota_protocol.md §2). `build` - reads them, so `--target-env`/`--fw-version`/`--hw-id` are **optional** — point `--fw` at any such `.bin` - (e.g. from a folder) and it packages with the right identity. Explicit flags still override. -- **Cross-hardware delta guard.** A delta is built only if the base and target firmware have the **same** - `hw_id` + `target_id` (read from their `EndF`, not the filenames). A mismatch is refused with a clear - reason; use `--force` to override deliberately. -- 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. +| `motalib.py` | Core logic: multihash, EndF, merkle tree+proofs, manifest/container build/parse/verify. The **reference implementation** of the spec and the unit-test oracle. Imported by everything below. | +| `pio_endf.py` | **PlatformIO post-build hook** (wired in `platformio.ini`) that injects the `EndF` self-identity trailer into the flashed firmware (`-D ENABLE_OTA`). | +| `gen_vectors.py` | Generates `test/test_ota/mota_vectors.h` — the cross-check vectors the native C++ tests run against. | +| `test_mota.py` | Unit tests for `motalib` (run directly or via pytest). | +| `endf.py` | Standalone `EndF` trailer injector (idempotent) — handy for one-off `.bin` patching. | +| `dev_motas.py` | CI packaging step for the rolling dev release: extracts the OTA image from a build (incl. nRF52 `.hex`) and drives `motatool build` for a full + same-image-delta `.mota`. | +| `extract_apply.py` | Dev helper: split a firmware into payload + fixed-manifest bytes for the apply test. | ## Tests ```bash -./meshcore/bin/python tools/mota/test_mota.py # 11 tests: EndF, merkle+proofs, full/delta, - # signing, tamper detection, approval enforcement +./meshcore/bin/python tools/mota/test_mota.py # EndF, merkle+proofs, full/delta, signing, + # tamper detection, approval enforcement +./meshcore/bin/python tools/mota/gen_vectors.py # regenerate the native-test cross-check vectors ``` -## Folder serve (`mota_seeder.py`) — relay many `.mota` from a host - -A node can advertise + serve `.mota` it does **not** hold in flash, by relaying them from a folder on a -host computer. Drop several `.mota` (any architecture) into a folder; the node then shows up to peers as -having all of them. The relay is **trustless** — fetchers verify the merkle root + signature, so the -host/daemon never needs the signing keys and a bad file simply fails the fetch. - -```bash -# just connect the MeshCore node to the PC over its normal USB — no extra hardware: -pip install pyserial -./tools/mota/mota_seeder.py --port /dev/ttyACM0 --baud 115200 --dir ./my_firmware/ -v -``` - -The daemon owns the port, auto-sends `ota folder on` to the node, then answers the node's byte requests. -It speaks a tiny binary request/response protocol (`src/helpers/ota/MotaSeederProto.h`) over the **same -USB serial the CLI uses** — the node only emits request frames *while actively serving a fetch* and reads -the reply synchronously, so the binary frames coexist with the text CLI / logs (the daemon resyncs on a -magic + checksum and surfaces device text as `[dev]` lines). Peers discover the folder mOTAs via the -normal beacon → query → HAVE catalog and `ota pull ` fetches them block-by-block straight from the -host folder. Verified on HW: a RAK4631 relayed a host folder to a Heltec V3, which fetched a folder mota -to COMPLETE (every block merkle-checked) over the single USB. - -`OTA_FOLDER_SERIAL` is already enabled on the stock RAK4631 / ESP32 OTA repeater builds (inert until you -run `ota folder on` — which the daemon does for you). To point the relay at a *dedicated* UART instead of -the console, override in `platformio.ini`: - -```ini -build_flags = - -D OTA_FOLDER_SERIAL_STREAM=Serial1 ; default is the USB console `Serial` - -D OTA_FOLDER_SERIAL_BEGIN ; call .begin() on it (console is already initialized) - -D OTA_FOLDER_SERIAL_BAUD=115200 -``` - -CLI on the node: `ota folder on` (attach + announce), `ota folder` (list served mOTAs, `*`=own fw), -`ota folder off` (detach). - ## `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: +`EndF` must live in the **flashed** firmware (not just inside the `.mota`), because a node serves its own +firmware and matches a delta's `base_hash` against its own `EndF`. Wiring (handled by `pio_endf.py`): -- **ESP32 / RP2040** (emit `firmware.bin`): 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. +- **ESP32 / RP2040** (emit `firmware.bin`): `post:tools/mota/pio_endf.py` in the env's `extra_scripts` plus + `-D ENABLE_OTA=1`. The hook appends the 56-byte `EndF` (with `target_id`/`fw_version`/`hw_id`) to the app + `.bin` before merge. +- **nRF52 / STM32** (emit `.hex` → `.uf2`): the same hook rewrites the `.hex` with the trailer at the image + end. The byte logic is `motalib.ensure_endf`, used everywhere. -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. +`target_id` = `sha2-256:4(pio_env_name)`, `hw_id` = `-D MOTA_HW_ID`, `fw_version` = parsed from +`FIRMWARE_VERSION` — so a node (and `motatool`, reading the firmware's `EndF`) auto-discovers identity +without relying on filenames. diff --git a/tools/mota/dev_motas.py b/tools/mota/dev_motas.py index b3e2e098..5b6ad90d 100644 --- a/tools/mota/dev_motas.py +++ b/tools/mota/dev_motas.py @@ -1,10 +1,11 @@ #!/usr/bin/env python3 """Package a full + same-image-delta .mota for ONE built firmware (dev rolling release). -Extracts the OTA image (BODY||EndF) from a PlatformIO build and runs `mota build` twice: +CI orchestration on top of the `motatool` packager: extracts the OTA image (BODY||EndF) from a +PlatformIO build (the part motatool can't do for nRF52 .hex), then runs `motatool build` twice: - full .mota (codec full) — the flashable image, universally applicable - delta .mota (same-image) — base == target, so the patch is tiny (a format/transport demo). - ESP32 -> sequential+crle ; nRF52 -> in-place (RAK4631 flash layout). + ESP32 -> sequential ; nRF52 -> in-place (RAK4631 flash layout). Image source per platform: ESP32 : /firmware.bin (pio_endf has already appended EndF) @@ -12,13 +13,14 @@ Image source per platform: Usage: dev_motas.py --env RAK_4631_repeater --platform NRF52 --build-dir .pio/build/RAK_4631_repeater \ - --target-env RAK_4631_repeater --out-prefix out/RAK_4631_repeater-dev-abc1234 + --target-env RAK_4631_repeater --out-prefix out/RAK_4631_repeater-dev-abc1234 \ + --motatool ./tools/motatool/build/motatool Writes .full.mota and (best-effort) .delta.mota. """ import argparse, os, subprocess, sys -HERE = os.path.dirname(os.path.abspath(__file__)) -MOTA = os.path.join(HERE, "mota.py") +# the motatool binary (build it from tools/motatool/); overridable via --motatool or $MOTATOOL +DEFAULT_MOTATOOL = os.environ.get("MOTATOOL", "motatool") # nRF52 (RAK4631) flash layout — keep in sync with src/helpers/ota/OtaFlashLayout_nrf52.h NRF52_APP_BASE = 0x26000 @@ -48,9 +50,9 @@ def extract_image(platform, build_dir, work): return None -def run_mota(args): - print(" +", "mota.py", " ".join(args)) - subprocess.run([sys.executable, MOTA, "build", *args], check=True) +def run_mota(motatool, args): + print(" +", motatool, "build", " ".join(args)) + subprocess.run([motatool, "build", *args], check=True) def main(): @@ -61,6 +63,7 @@ def main(): ap.add_argument("--target-env", required=True) ap.add_argument("--out-prefix", required=True) ap.add_argument("--work", default=".") + ap.add_argument("--motatool", default=DEFAULT_MOTATOOL, help="path to the motatool binary") a = ap.parse_args() img = extract_image(a.platform, a.build_dir, a.work) @@ -69,20 +72,20 @@ def main(): return 0 print(f"OTA image for {a.env}: {img} ({os.path.getsize(img)} bytes)") - # full .mota (always) - run_mota(["--fw", img, "--target-env", a.target_env, "--fw-version", "0.0.0", - "--codec", "full", "--out", a.out_prefix + ".full.mota"]) + # full .mota (always). motatool hardwires crle for deltas — the codec the on-device applier expects. + run_mota(a.motatool, ["--fw", img, "--target-env", a.target_env, "--fw-version", "0.0.0", + "--codec", "full", "--out", a.out_prefix + ".full.mota"]) # same-image delta .mota (best-effort; codec per platform's applier) try: if a.platform == "ESP32": - run_mota(["--fw", img, "--base", img, "--target-env", a.target_env, "--fw-version", "0.0.1", - "--codec", "sequential", "--compression", "crle", "--out", a.out_prefix + ".delta.mota"]) + run_mota(a.motatool, ["--fw", img, "--base", img, "--target-env", a.target_env, "--fw-version", "0.0.1", + "--codec", "sequential", "--out", a.out_prefix + ".delta.mota"]) else: # NRF52 in-place - run_mota(["--fw", img, "--base", img, "--target-env", a.target_env, "--fw-version", "0.0.1", - "--codec", "inplace", "--compression", "crle", - "--inplace-memory", str(NRF52_INPLACE_MEMORY), "--inplace-segment", str(NRF52_INPLACE_SEGMENT), - "--out", a.out_prefix + ".delta.mota"]) + run_mota(a.motatool, ["--fw", img, "--base", img, "--target-env", a.target_env, "--fw-version", "0.0.1", + "--codec", "inplace", + "--inplace-memory", str(NRF52_INPLACE_MEMORY), "--inplace-segment", str(NRF52_INPLACE_SEGMENT), + "--out", a.out_prefix + ".delta.mota"]) except subprocess.CalledProcessError as e: print(f"::warning::delta .mota for {a.env} failed ({e}); full .mota still produced") return 0 diff --git a/tools/mota/mota.py b/tools/mota/mota.py deleted file mode 100644 index 9b336563..00000000 --- a/tools/mota/mota.py +++ /dev/null @@ -1,259 +0,0 @@ -#!/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) - - # Self-describing identity: if the firmware carries an extended EndF (target_id/fw_version/hw_id), use - # it as the default so a raw .bin from a folder packages correctly WITHOUT --target-env/--fw-version/ - # --hw-id (we can't rely on filenames). Explicit flags still override. - ident = ml.parse_endf_ident(new_image) - if args.target_env: target_id = ml.target_id_for_env(args.target_env) - elif args.target_id: target_id = _parse_target_id(args.target_id) - elif ident and ident.target_id: target_id = ident.target_id - else: sys.exit("no target: pass --target-env/--target-id, or build a firmware whose EndF carries one") - fw_version = ml.pack_version(args.fw_version) if args.fw_version else (ident.fw_version if ident else 0) - hw_id = args.hw_id or (ident.hw_id if ident else "") - - 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()) - # A delta is only applicable to the SAME hardware+role as the target. Verify via the firmwares' - # self-describing EndF identity (not the filenames), so we never ship a cross-HW delta that would - # brick a node. Skippable with --force for deliberate cross-target experiments. - base_ident = ml.parse_endf_ident(old_image) - if ident and base_ident: - mismatch = [] - if ident.hw_id and base_ident.hw_id and ident.hw_id != base_ident.hw_id: - mismatch.append(f"hw_id {base_ident.hw_id!r} (base) != {ident.hw_id!r} (target)") - if ident.target_id and base_ident.target_id and ident.target_id != base_ident.target_id: - mismatch.append(f"target_id {base_ident.target_id:#010x} != {ident.target_id:#010x}") - if mismatch and not args.force: - sys.exit("refusing cross-hardware delta (use --force to override):\n " + "\n ".join(mismatch)) - if mismatch: - print("WARNING: building a cross-hardware delta (--force): " + "; ".join(mismatch)) - elif not args.force: - print("note: base and/or target firmware has no EndF identity — cannot verify same-hardware " - "(build a firmware whose EndF carries identity, or pass --force to silence)") - 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=target_id, - fw_version=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, - hw_id=hw_id, - ) - 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" hw_id : {manifest.hw_id.rstrip(bytes([0])).decode('ascii', 'replace') or '(none)'}") - 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", help="e.g. 1.16.0 (or .pre as 1.16.0.2). Optional: read from the " - "firmware's EndF identity if present.") - b.add_argument("--force", action="store_true", - help="build a delta even if base/target hardware identities differ (normally refused)") - 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("--hw-id", default="", help="hardware tag (<=32 ASCII chars, e.g. RAK4631) the firmware " - "can boot on; the device refuses a .mota whose hw_id differs from its own. Empty = unset.") - 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/mota_seeder.py b/tools/mota/mota_seeder.py deleted file mode 100755 index 6e937d68..00000000 --- a/tools/mota/mota_seeder.py +++ /dev/null @@ -1,212 +0,0 @@ -#!/usr/bin/env python3 -"""mota-seeder: serve a FOLDER of .mota firmware to a MeshCore node over a serial link. - -The node (a SerialMotaSource) pulls the catalog + bytes on demand and RELAYS them into the mesh as if it -held them — peers just see "this node has N mOTAs". Drop several .mota (any architecture) into a folder -and point this daemon at the node's seeder UART; the node advertises + serves them all. The relay is -trustless (fetchers verify merkle+signature), so this daemon never needs the signing keys. - -Protocol: src/helpers/ota/MotaSeederProto.h (little-endian, XOR-checksummed, resync on magic). - request (node -> here): 'M''S' op(1) args... xsum - response (here -> node): 'm''s' op(1) status(1) payload... xsum - OP_COUNT 0x01 -> count(1) ; OP_DESCRIBE 0x02 idx -> MotaDesc(38) ; OP_READ 0x03 idx off(4) len(2) -> bytes - -Usage: mota_seeder.py --port /dev/ttyUSB0 --baud 115200 --dir ./firmware_folder [--watch] -Requires: pyserial, and motalib.py (this same tools/mota dir) for parsing. -""" -import argparse, glob, os, struct, sys, time -import serial # pyserial -import motalib - -REQ_MAGIC = b"MS" -RSP_MAGIC = b"ms" -OP_COUNT, OP_DESCRIBE, OP_READ = 0x01, 0x02, 0x03 -ST_OK, ST_ERR = 0x00, 0x01 -DESC_WIRE = 38 - -HEAD = 89 # fixed manifest head incl hw_id[32] - - -def mota_offsets(blob: bytes): - """Parse a .mota and return its catalog descriptor + region offsets (mirrors MotaContainer.cpp).""" - p = motalib.parse_container(blob) - m = p.manifest - base = 0 if m.is_full else 8 - sig = 96 if m.is_signed else 0 - mfl = HEAD + base + sig + 4 # manifest-minus-leaves: head + base_hash? + sig? + approval - leaves_off = 8 + mfl # container = MAGIC(4) total(4) manifest... - bc = m.block_count - payload_off = leaves_off + bc * 4 - return { - "mid": bytes(m.merkle_root), - "target_id": m.target_id, - "fw_version": m.fw_version, - "codec_id": m.codec_id, - "flags": m.flags, - "total_size": len(blob), - "leaves_off": leaves_off, - "block_count": bc, - "payload_off": payload_off, - "payload_size": m.payload_size, - } - - -def desc_wire(d) -> bytes: - w = bytearray(DESC_WIRE) - w[0:4] = d["mid"] - struct.pack_into(" int: - x = seed - for b in data: - x ^= b - return x & 0xFF - - -def send_rsp(ser, op, status, payload=b""): - body = bytes([op, status]) + payload - frame = RSP_MAGIC + body + bytes([xor(RSP_MAGIC + body)]) - ser.write(frame) - ser.flush() - - -def read_exact(ser, n): - buf = bytearray() - while len(buf) < n: - chunk = ser.read(n - len(buf)) - if not chunk: - return None - buf += chunk - return bytes(buf) - - -def handle_one(ser, items, verbose): - """Read one request body (magic already consumed) and send its response.""" - op = read_exact(ser, 1) - if op is None: - return - op = op[0] - if op == OP_COUNT: - args = b"" - elif op == OP_DESCRIBE: - args = read_exact(ser, 1) - elif op == OP_READ: - args = read_exact(ser, 7) - else: - return - if args is None: - return - xs = read_exact(ser, 1) - if xs is None or xs[0] != xor(args, op): - return # bad checksum -> ignore; node retries - - if op == OP_COUNT: - send_rsp(ser, op, ST_OK, bytes([min(len(items), 255)])) - if verbose: - print(f" COUNT -> {len(items)}") - elif op == OP_DESCRIBE: - idx = args[0] - if idx < len(items): - send_rsp(ser, op, ST_OK, desc_wire(items[idx]["desc"])) - if verbose: - print(f" DESCRIBE {idx} -> {os.path.basename(items[idx]['path'])}") - else: - send_rsp(ser, op, ST_ERR) - elif op == OP_READ: - idx = args[0] - off, lo, hi = struct.unpack(" int: # EndF trailer # --------------------------------------------------------------------------- -ENDF_EXT_MAGIC = b"EnFx" # marks an extended (identity-carrying) EndF trailer -ENDF_EXT_LEN = ENDF_LEN + 4 + 4 + 4 + 32 # 16 + EnFx(4) + fw_version(4) + target_id(4) + hw_id(32) = 60 - - @dataclass class FwIdent: - """Self-describing firmware identity, carried in the extended EndF trailer (docs/ota_protocol.md §2) - so a node / the packaging tool can read it straight from the firmware instead of relying on build - flags or filenames.""" + """Self-describing firmware identity carried in the EndF trailer (docs/ota_protocol.md §2) so a node / + the packaging tool reads it straight from the firmware instead of relying on build flags or filenames.""" fw_version: int = 0 # packed MAJOR<<24 | MINOR<<16 | PATCH<<8 | pre target_id: int = 0 # sha2-256:4(pio_env) as uint32 LE — hw + role + partition (fetch routing) hw_id: str = "" # readable hardware tag (brick-safety), e.g. "RAK4631" def build_endf(body: bytes, ident: Optional["FwIdent"] = None) -> bytes: - """The EndF trailer for a firmware BODY: 16 bytes (legacy) or 60 bytes when `ident` is given. The - 16-byte prefix is identical either way, so the bootloader and legacy readers (which read only the - first 16 bytes) are unaffected by the extension.""" - base = ENDF_MAGIC + struct.pack(" int: - """Length of the trailing EndF (60 if extended, 16 if legacy, 0 if none/invalid).""" - if len(image) >= ENDF_EXT_LEN: - t = image[-ENDF_EXT_LEN:] - if (t[:4] == ENDF_MAGIC and struct.unpack("= ENDF_LEN: - t = image[-ENDF_LEN:] - if (t[:4] == ENDF_MAGIC and struct.unpack(" bool: - """True iff `image` ends with a self-consistent EndF trailer (legacy or extended).""" - return _endf_trailer_len(image) != 0 + """True iff `image` ends with a self-consistent (fixed 56-byte) EndF trailer (image == BODY || EndF).""" + if len(image) < ENDF_LEN: + return False + t = image[-ENDF_LEN:] + return (t[:4] == ENDF_MAGIC and struct.unpack(" Tuple[bytes, bytes]: """Return (body, body_hash8) for an image that ends with a valid EndF. Raises otherwise.""" - n = _endf_trailer_len(image) - if not n: + if not has_endf(image): raise ValueError("image has no valid EndF trailer") - t = image[-n:] - return image[:-n], t[8:16] + return image[:-ENDF_LEN], image[-ENDF_LEN + 8:-ENDF_LEN + 16] def parse_endf_ident(image: bytes) -> Optional["FwIdent"]: - """The self-describing identity from an extended EndF, or None for a legacy/absent trailer.""" - if _endf_trailer_len(image) != ENDF_EXT_LEN: + """The self-describing identity from a valid EndF trailer, or None if there is no valid trailer.""" + if not has_endf(image): return None - t = image[-ENDF_EXT_LEN:] - fw, tgt = struct.unpack(" Tuple[bytes, bytes]: - """Return (image_with_endf, body_hash8). Appends EndF (with `ident` if given) if not already present. - If the image already has a trailer it is kept as-is (we never rewrite an existing identity).""" + """Return (image_with_endf, body_hash8). Appends EndF (with `ident` if given) if not already present.""" if has_endf(image): _, h8 = parse_endf(image) return image, h8 @@ -279,9 +259,11 @@ class Manifest: image_hash: bytes = b"\0" * 32 codec_id: int = CODEC_FULL hw_id: bytes = b"\0" * 32 # 32-byte NUL-padded ASCII hardware tag (signed) - 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 + # Fixed-layout: these are ALWAYS present (zero-filled when not applicable), so the manifest has a + # constant size and a trivial offset-based parser; only leaves[] is variable. + base_hash: bytes = b"\0" * 8 # 8 bytes; zero for a full image (meaningful iff !FULL) + signer_pubkey: bytes = b"\0" * 32 # 32 bytes; zero when unsigned (meaningful iff SIGNED) + signature: bytes = b"\0" * 64 # 64 bytes; zero when unsigned (meaningful iff SIGNED) approval: bytes = APPROVAL_NOT leaves: List[bytes] = field(default_factory=list) @@ -302,7 +284,7 @@ class Manifest: 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.""" + """Bytes the Ed25519 signature covers: format_ver .. signer_pubkey (fixed 129 bytes).""" out = bytearray() out += bytes([self.format_ver, self.flags, self.hash_algo]) out += struct.pack(" bytes: + """Fixed layout: signed_region(129) + signature(64) + approval(4) + leaves[4*BC].""" out = bytearray(self.signed_region()) - if self.is_signed: - out += self.signature + out += self.signature # always present (zero when unsigned) out += self.approval for lf in self.leaves: out += lf @@ -343,11 +323,9 @@ def _validate_lengths(m: Manifest): assert len(m.image_hash) == 32 assert len(m.hw_id) == 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 + assert len(m.base_hash) == 8 # fixed layout: always present (zero for full) + assert len(m.signer_pubkey) == 32 + assert len(m.signature) == 64 def build_manifest(*, target_id: int, fw_version: int, image_size: int, payload: bytes, @@ -366,12 +344,14 @@ def build_manifest(*, target_id: int, fw_version: int, image_size: int, payload: image_hash=image_hash, codec_id=codec_id, hw_id=hw_id_bytes(hw_id), - base_hash=None if is_full else base_hash, + base_hash=(b"\0" * 8 if is_full else base_hash), # always 8 bytes; zero for a full image leaves=leaves, ) + if not is_full and (base_hash is None or len(base_hash) != 8): + raise ValueError("delta requires an 8-byte base_hash") if sign_priv is not None: m.signer_pubkey = sign_priv.public_key().public_bytes_raw() - m.signature = sign_priv.sign(m.signed_region()) + m.signature = sign_priv.sign(m.signed_region()) # else signer_pubkey/signature stay zero _validate_lengths(m) return m @@ -423,11 +403,9 @@ def parse_container(blob: bytes) -> Parsed: m.image_hash = take(32) m.codec_id = take(1)[0] m.hw_id = take(32) - if not m.is_full: - m.base_hash = take(8) - if m.is_signed: - m.signer_pubkey = take(32) - m.signature = take(64) + m.base_hash = take(8) # fixed layout: always present (zero for full) + m.signer_pubkey = take(32) + m.signature = take(64) m.approval = take(4) bc = m.block_count m.leaves = [take(4) for _ in range(bc)] diff --git a/tools/mota/test_mota.py b/tools/mota/test_mota.py index ec1e1fda..cd9a522e 100644 --- a/tools/mota/test_mota.py +++ b/tools/mota/test_mota.py @@ -61,22 +61,21 @@ def test_endf_rejects_garbage_tail(): assert not ml.has_endf(img) -def test_endf_extended_identity(): +def test_endf_identity(): body = _fw(3, 4096) ident = ml.FwIdent(fw_version=ml.pack_version("1.16.0"), target_id=ml.target_id_for_env("RAK_4631_repeater"), hw_id="RAK4631") - ext, h8 = ml.ensure_endf(body, ident) - assert len(ext) == len(body) + ml.ENDF_EXT_LEN # 60-byte extended trailer - assert ml.parse_endf(ext) == (body, h8) # body + body_hash still parse - # body_hash is over BODY only -> identical to the legacy trailer (delta base_hash stays valid) - assert h8 == ml.ensure_endf(body)[1] - gi = ml.parse_endf_ident(ext) + img, h8 = ml.ensure_endf(body, ident) + assert len(img) == len(body) + ml.ENDF_LEN # fixed 56-byte trailer + assert ml.parse_endf(img) == (body, h8) # body + body_hash parse + assert h8 == ml.mh8(body) # body_hash is over BODY only + gi = ml.parse_endf_ident(img) assert gi is not None and gi.hw_id == "RAK4631" assert gi.target_id == ml.target_id_for_env("RAK_4631_repeater") assert gi.fw_version == ml.pack_version("1.16.0") - # the 16-byte prefix equals the legacy trailer (bootloader-compatible); legacy images carry no identity - assert ext[len(body):len(body) + 16] == ml.ensure_endf(body)[0][len(body):] - assert ml.parse_endf_ident(ml.ensure_endf(body)[0]) is None + # no identity supplied -> zero-filled (still fixed size, still self-consistent) + z, _ = ml.ensure_endf(body) + assert len(z) == len(body) + ml.ENDF_LEN and ml.parse_endf_ident(z) == ml.FwIdent(0, 0, "") # --- merkle ---------------------------------------------------------------- diff --git a/tools/motatool/.gitignore b/tools/motatool/.gitignore new file mode 100644 index 00000000..567609b1 --- /dev/null +++ b/tools/motatool/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/tools/motatool/CMakeLists.txt b/tools/motatool/CMakeLists.txt new file mode 100644 index 00000000..a13da0aa --- /dev/null +++ b/tools/motatool/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.16) +project(motatool LANGUAGES CXX) + +# Self-contained MeshCore OTA host tool: build / verify / inspect / serve `.mota` containers. +# Portable C++17 — builds on Ubuntu, Raspberry Pi, and any arch with a C++17 compiler + OpenSSL. +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() +add_compile_options(-Wall -Wextra) + +# OpenSSL libcrypto provides SHA-256 + Ed25519 (sign/verify/keygen). On Debian/Ubuntu/Pi: +# sudo apt install libssl-dev +find_package(OpenSSL REQUIRED) + +# Core logic (everything except the CLI front-end) — shared by the tool and the test runner. +add_library(motatool_core STATIC + src/mota.cpp + src/crypto.cpp + src/input.cpp + src/serve.cpp +) +target_include_directories(motatool_core PUBLIC src) +target_link_libraries(motatool_core PUBLIC OpenSSL::Crypto) + +add_executable(motatool src/main.cpp) +target_link_libraries(motatool PRIVATE motatool_core) +install(TARGETS motatool RUNTIME DESTINATION bin) + +# Unit tests (no external test framework — a tiny built-in harness keeps the project self-contained). +# Run with: cmake --build build && ctest --test-dir build --output-on-failure +# or: ./build/motatool_tests +enable_testing() +add_executable(motatool_tests tests/test_motatool.cpp) +target_link_libraries(motatool_tests PRIVATE motatool_core) +add_test(NAME motatool_tests COMMAND motatool_tests) diff --git a/tools/motatool/README.md b/tools/motatool/README.md new file mode 100644 index 00000000..9ed451b9 --- /dev/null +++ b/tools/motatool/README.md @@ -0,0 +1,113 @@ +# motatool — self-contained MeshCore OTA host tool (C++) + +A small, portable C++17 tool that **builds**, **verifies**, and **serves** MeshCore `.mota` +firmware-update containers. It is an independent project (its own `CMakeLists.txt`) that compiles on a +laptop, a Raspberry Pi, or any architecture with a C++17 compiler — so it can run as a lightweight +folder-relay daemon on small hardware. + +It implements the wire spec in [`../../docs/ota_protocol.md`](../../docs/ota_protocol.md) and is +cross-checked byte-for-byte against the Python reference packager `tools/mota/motalib.py` (a full signed +`.mota` built by `motatool` is identical to the reference's output). + +## What it does + +| Command | Purpose | +|---|---| +| `build` | Create a **full** or **delta** `.mota` from a firmware (local file **or** http(s) URL). | +| `verify` | Validate one or more `.mota` (merkle tree, leaves vs payload, image hash, Ed25519 signature). `--pub` requires a specific signer; `--base` confirms a sequential delta rebuilds its image. | +| `inspect`| Print every field of a `.mota`'s manifest (debugging). | +| `serve` | Serve a **folder** of `.mota` to a node over USB serial. Invalid files are warned about and skipped — one corrupt file never sinks the rest. | +| `keygen` | Generate an Ed25519 signing keypair (hex). | + +Every command has detailed, example-rich help: `motatool --help`. + +## Build + +Dependencies: a C++17 compiler, CMake, and **OpenSSL** (libcrypto: SHA-256 + Ed25519). For URL input +and delta creation, see the notes below. + +```bash +sudo apt install build-essential cmake libssl-dev # Debian / Ubuntu / Raspberry Pi OS +cmake -S tools/motatool -B tools/motatool/build +cmake --build tools/motatool/build -j +# -> tools/motatool/build/motatool +``` + +### Tests + +Unit tests (a tiny built-in harness — no external framework) cover the crypto, EndF, build/verify, +merkle, parse rejection, the folder scanner + seeder protocol, and a real detools delta round-trip +(auto-skipped if `detools` isn't installed): + +```bash +ctest --test-dir tools/motatool/build --output-on-failure +# or directly: tools/motatool/build/motatool_tests +``` + +## Usage + +```bash +MT=tools/motatool/build/motatool + +# 1. signing keypair (64-char hex) +$MT keygen --out signer.key # writes signer.key + signer.key.pub + +# 2a. FULL .mota — payload IS the flashable image (identity is auto-read from the firmware's EndF) +$MT build --fw firmware.bin --sign signer.key --out-dir ./motas/ + +# 2b. FULL .mota straight from a URL +$MT build --fw https://example.org/Heltec_v3_repeater.bin --sign signer.key --out-dir ./motas/ + +# 2c. DELTA .mota against a previous release (codec auto-selected from the hardware tag: +# nRF52 -> in-place, ESP32 -> sequential; override with --codec) +$MT build --fw new.bin --base old.bin --sign signer.key --out-dir ./motas/ +$MT build --fw new.bin --base old.bin --codec sequential --out-dir ./motas/ + +# 3. validate a folder of .mota (optionally require a signer / check a delta against its base) +$MT verify ./motas/*.mota +$MT verify update.mota --pub signer.key.pub +$MT verify delta.mota --base old_firmware.bin + +# 4. dump a single .mota's manifest fields +$MT inspect ./motas/RAK4631_04D413FD_v1.16.0_full_ABCD1234.mota + +# 5. serve a folder to a node over its USB serial (recursive; skips non-.mota; warns on corrupt) +$MT serve --dir ./motas --serial /dev/ttyUSB0 --baud 115200 -v +``` + +`build` notes: +- **Identity is self-described.** A firmware built by the project's `pio_endf.py` carries its + `target_id`/`fw_version`/`hw_id` in its 56-byte `EndF` trailer; `build` reads them, so + `--target-env`/`--target-id`, `--fw-version`, and `--hw-id` are **optional** (explicit flags override). +- **Cross-hardware delta guard.** A delta is refused if the base and target firmware identities differ + (read from their `EndF`, not filenames); pass `--force` to override. +- **Delta codec** needs the `detools` encoder (the project's pinned codec — never reimplemented). Install + it (`pip install detools`) and ensure it's on `PATH`, or pass `--detools `. **Full** builds, + **verify**, and **serve** need no detools. In-place defaults: `--inplace-memory 0xAE000` + (nRF52 workspace), `--inplace-segment 4096`. +- **URL input** uses the system `curl` (falling back to `wget`) — no link-time dependency. +- All output is written into one folder (`--out-dir`, default `.`) with a descriptive, unique name: + `__v__.mota`. + +## Serving is transport-agnostic (USB serial today, BLE later) + +The protocol is split so the serving logic is reusable across links: + +- **`SeederCore`** (`src/serve.{h,cpp}`) is transport-free: it maps a request `(op, args)` to a response + `(status, payload)` — `COUNT` / `DESCRIBE(idx)` / `READ(idx, off, len)` over the validated catalog. +- **`SerialTransport` + `serve_serial()`** wrap it with the byte-stream framing (magic + XOR checksum + + resync) for the unreliable USB-UART link (`MotaSeederProto.h`). + +To serve the same folder over **BLE** (e.g. an Android phone relaying to a MeshCore node), implement the +transport at the GATT layer — a request characteristic write hands `(op, args)` straight to +`SeederCore::handle()` and the reply is sent as a notification. No byte-stream framing is needed there +(BLE is reliable/segmented), and `SeederCore` + `Folder` are reused unchanged. OpenSSL builds for the +Android NDK, so the validation path ports as-is. + +## Relationship to `tools/mota/` + +`motatool` is the user-facing CLI: it replaced the old Python `mota.py` (build/verify/inspect/keygen) and +`mota_seeder.py` (serve), which have been removed. The Python `tools/mota/` directory remains as the +**reference implementation** (`motalib.py`, the spec oracle + unit tests) and the **firmware build/test +glue** (`pio_endf.py` build hook, `gen_vectors.py` test vectors, `dev_motas.py` CI packaging — which now +drives `motatool`). Their `.mota` output is byte-identical (verified by cross-checks). diff --git a/tools/motatool/src/crypto.cpp b/tools/motatool/src/crypto.cpp new file mode 100644 index 00000000..a4076a9c --- /dev/null +++ b/tools/motatool/src/crypto.cpp @@ -0,0 +1,61 @@ +#include "crypto.h" +#include +#include +#include + +namespace mota { + +void sha256_trunc(uint8_t* out, size_t out_len, const uint8_t* data, size_t len) { + uint8_t full[32]; + SHA256(data, len, full); + if (out_len > 32) out_len = 32; + std::memcpy(out, full, out_len); +} + +bool ed25519_verify(const uint8_t sig[64], const uint8_t* msg, size_t msg_len, const uint8_t pub[32]) { + EVP_PKEY* key = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, nullptr, pub, 32); + if (!key) return false; + EVP_MD_CTX* ctx = EVP_MD_CTX_new(); + bool ok = ctx && EVP_DigestVerifyInit(ctx, nullptr, nullptr, nullptr, key) == 1 && + EVP_DigestVerify(ctx, sig, 64, msg, msg_len) == 1; + if (ctx) EVP_MD_CTX_free(ctx); + EVP_PKEY_free(key); + return ok; +} + +bool ed25519_sign(uint8_t sig[64], const uint8_t* msg, size_t msg_len, const uint8_t priv[32]) { + EVP_PKEY* key = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, nullptr, priv, 32); + if (!key) return false; + EVP_MD_CTX* ctx = EVP_MD_CTX_new(); + size_t siglen = 64; + bool ok = ctx && EVP_DigestSignInit(ctx, nullptr, nullptr, nullptr, key) == 1 && + EVP_DigestSign(ctx, sig, &siglen, msg, msg_len) == 1 && siglen == 64; + if (ctx) EVP_MD_CTX_free(ctx); + EVP_PKEY_free(key); + return ok; +} + +bool ed25519_pub_from_priv(uint8_t pub[32], const uint8_t priv[32]) { + EVP_PKEY* key = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, nullptr, priv, 32); + if (!key) return false; + size_t len = 32; + bool ok = EVP_PKEY_get_raw_public_key(key, pub, &len) == 1 && len == 32; + EVP_PKEY_free(key); + return ok; +} + +bool ed25519_keygen(uint8_t priv[32], uint8_t pub[32]) { + EVP_PKEY* key = nullptr; + EVP_PKEY_CTX* pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, nullptr); + bool ok = pctx && EVP_PKEY_keygen_init(pctx) == 1 && EVP_PKEY_keygen(pctx, &key) == 1; + if (ok) { + size_t lp = 32, lk = 32; + ok = EVP_PKEY_get_raw_private_key(key, priv, &lp) == 1 && lp == 32 && + EVP_PKEY_get_raw_public_key(key, pub, &lk) == 1 && lk == 32; + } + if (key) EVP_PKEY_free(key); + if (pctx) EVP_PKEY_CTX_free(pctx); + return ok; +} + +} // namespace mota diff --git a/tools/motatool/src/crypto.h b/tools/motatool/src/crypto.h new file mode 100644 index 00000000..eac5d8d3 --- /dev/null +++ b/tools/motatool/src/crypto.h @@ -0,0 +1,28 @@ +// SHA-256 (multihash truncations) and Ed25519 — backed by OpenSSL libcrypto, so the tool is portable +// across Ubuntu / Raspberry Pi / any arch that has OpenSSL (and Android NDK for a future BLE seeder). +#pragma once +#include +#include +#include +#include +#include + +namespace mota { + +// SHA-256 of `data`, truncated to out_len (sha2-256:N). out_len <= 32. +void sha256_trunc(uint8_t* out, size_t out_len, const uint8_t* data, size_t len); + +inline std::array mh4(const uint8_t* d, size_t n) { std::array o; sha256_trunc(o.data(),4,d,n); return o; } +inline std::array mh8(const uint8_t* d, size_t n) { std::array o; sha256_trunc(o.data(),8,d,n); return o; } +inline std::array mh32(const uint8_t* d, size_t n) { std::array o; sha256_trunc(o.data(),32,d,n); return o; } + +// Ed25519. Keys are raw 32-byte (private seed / public key), matching motalib / the device. +bool ed25519_verify(const uint8_t sig[64], const uint8_t* msg, size_t msg_len, const uint8_t pub[32]); +// Sign `msg` with a 32-byte raw private seed -> 64-byte signature. Returns false on error. +bool ed25519_sign(uint8_t sig[64], const uint8_t* msg, size_t msg_len, const uint8_t priv[32]); +// Derive the 32-byte raw public key from a 32-byte raw private seed. +bool ed25519_pub_from_priv(uint8_t pub[32], const uint8_t priv[32]); +// Generate a fresh keypair (raw 32-byte each). +bool ed25519_keygen(uint8_t priv[32], uint8_t pub[32]); + +} // namespace mota diff --git a/tools/motatool/src/input.cpp b/tools/motatool/src/input.cpp new file mode 100644 index 00000000..ddc99f27 --- /dev/null +++ b/tools/motatool/src/input.cpp @@ -0,0 +1,34 @@ +#include "input.h" +#include "util.h" +#include + +namespace mota { + +bool is_url(const std::string& s) { + return s.rfind("http://", 0) == 0 || s.rfind("https://", 0) == 0; +} + +std::string read_input(const std::string& src, std::vector& out) { + if (!is_url(src)) { + if (!read_file(src, out)) return "cannot read file: " + src; + if (out.empty()) return "file is empty: " + src; + return ""; + } + // URL: download to a temp file via curl (fallback wget), then read it back. + char tmp[] = "/tmp/motadlXXXXXX"; + int fd = mkstemp(tmp); + if (fd < 0) return "mkstemp failed"; + close(fd); + // curl: -f fail on HTTP error, -L follow redirects, -s silent, -S show errors, -o out + int rc = run_argv({"curl", "-fLsS", "-o", tmp, src}); + if (rc == 127) // no curl -> try wget + rc = run_argv({"wget", "-q", "-O", tmp, src}); + std::string err; + if (rc == 127) err = "neither curl nor wget is available to fetch " + src; + else if (rc != 0) err = "download failed (exit " + std::to_string(rc) + "): " + src; + else if (!read_file(tmp, out) || out.empty()) err = "downloaded file is empty: " + src; + unlink(tmp); + return err; +} + +} // namespace mota diff --git a/tools/motatool/src/input.h b/tools/motatool/src/input.h new file mode 100644 index 00000000..ff92f5fd --- /dev/null +++ b/tools/motatool/src/input.h @@ -0,0 +1,15 @@ +// Read a firmware blob from a local file OR an http(s):// URL. URLs are fetched with the system `curl` +// (or `wget`) binary — no link-time dependency, so the tool stays self-contained and builds anywhere. +#pragma once +#include +#include +#include + +namespace mota { + +// Returns "" on success (fills `out`), else an error string. `what` is a label for error messages. +std::string read_input(const std::string& path_or_url, std::vector& out); + +bool is_url(const std::string& s); + +} // namespace mota diff --git a/tools/motatool/src/main.cpp b/tools/motatool/src/main.cpp new file mode 100644 index 00000000..e38dc3eb --- /dev/null +++ b/tools/motatool/src/main.cpp @@ -0,0 +1,493 @@ +// motatool — build, verify, and serve MeshCore `.mota` firmware-update containers. +// build create a full or delta .mota from a firmware (file or http(s) URL) +// verify validate one or more .mota (merkle / hashes / signature) +// serve serve a folder of .mota to a node (USB serial); invalid files are warned + skipped +// keygen generate an Ed25519 signing keypair (64-char hex) +#include "mota.h" +#include "input.h" +#include "serve.h" +#include "crypto.h" +#include "util.h" +#include +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; +using namespace mota; + +static volatile bool g_stop = false; +static void on_sigint(int) { g_stop = true; } + +// minimal flag parser: --key value / --flag +struct Args { + std::map opt; + std::vector pos; + bool has(const std::string& k) const { return opt.count(k); } + std::string get(const std::string& k, const std::string& d = "") const { + auto it = opt.find(k); return it == opt.end() ? d : it->second; + } +}; +static Args parse_args(int argc, char** argv, int start, + const std::vector& bool_flags) { + Args a; + for (int i = start; i < argc; i++) { + std::string s = argv[i]; + if (s == "-h" || s == "--help" || s == "help") { a.opt["help"] = "1"; continue; } + if (s == "-v") { a.opt["verbose"] = "1"; continue; } + if (s.rfind("--", 0) == 0) { + std::string k = s.substr(2); + if (std::find(bool_flags.begin(), bool_flags.end(), k) != bool_flags.end()) a.opt[k] = "1"; + else if (i + 1 < argc) a.opt[k] = argv[++i]; + else a.opt[k] = ""; + } else a.pos.push_back(s); + } + return a; +} + +static bool load_priv(const std::string& path, std::vector& priv) { + std::vector raw; + if (!read_file(path, raw)) return false; + std::string txt((const char*)raw.data(), raw.size()); // try hex (mota.py format) first + while (!txt.empty() && (std::isspace((unsigned char)txt.back()))) txt.pop_back(); + std::vector hx; + if (from_hex(txt, hx) && hx.size() == 32) { priv = hx; return true; } + if (raw.size() == 32) { priv = raw; return true; } // else accept a raw 32-byte file + return false; +} + +static std::string version_str(uint32_t v) { + char b[24]; std::snprintf(b, sizeof(b), "%u.%u.%u", (v>>24)&0xFF, (v>>16)&0xFF, (v>>8)&0xFF); + return b; +} + +// ---- help text (the tool is meant to be usable from --help alone) --------------------------------- +static void help_top() { + std::cout << +"motatool — build, verify, and serve MeshCore .mota firmware-update containers.\n" +"\n" +"A .mota is a signed, self-verifying package of a firmware update that MeshCore nodes fetch\n" +"over LoRa, block by block. This tool makes those packages, checks them, and serves a folder\n" +"of them to a node over USB.\n" +"\n" +"USAGE\n" +" motatool [options]\n" +" motatool --help detailed help + examples for a command\n" +"\n" +"COMMANDS\n" +" build Package a firmware as a .mota (a full image, or a small delta vs a previous build).\n" +" verify Check that .mota files are valid (block hashes, image hash, signature).\n" +" inspect Print every field of a .mota's manifest (debugging).\n" +" serve Serve a folder of .mota to a node over USB serial (corrupt files are skipped).\n" +" keygen Generate an Ed25519 signing keypair.\n" +"\n" +"TYPICAL WORKFLOW\n" +" 1. Make a signing key once:\n" +" motatool keygen --out signer.key\n" +" 2. Package a firmware (identity is read from the firmware itself):\n" +" motatool build --fw firmware.bin --sign signer.key --out-dir ./motas\n" +" 3. Serve the folder to a node plugged in over USB:\n" +" motatool serve --dir ./motas --serial /dev/ttyUSB0\n"; +} + +static void help_build() { + std::cout << +"motatool build — package a firmware as a .mota update container.\n" +"\n" +"USAGE\n" +" motatool build --fw [--base ] [options]\n" +"\n" +"WHAT IT DOES\n" +" With only --fw: builds a FULL update (the payload is the whole firmware image).\n" +" With --base too: builds a DELTA (a small patch from the base firmware to the new one),\n" +" which is far smaller to send over LoRa. The delta codec is picked for the target hardware\n" +" automatically: nRF52 (single-slot) -> in-place, ESP32 (A/B slots) -> sequential.\n" +"\n" +" Firmware identity (target, version, hardware tag) is read automatically from the firmware's\n" +" EndF trailer, so you normally don't pass --target-*/--fw-version/--hw-id. Pass them only to\n" +" override, or for a raw .bin that has no EndF identity.\n" +"\n" +"INPUT (--fw is required)\n" +" --fw NEW firmware. A local path OR an http(s):// URL (downloaded with curl/wget).\n" +" --base previous firmware to diff against -> makes a delta (omit it for a full image).\n" +"\n" +"IDENTITY (optional — auto-read from the firmware's EndF)\n" +" --target-env PlatformIO env name (e.g. RAK_4631_repeater); hashed into the target id.\n" +" --target-id raw target id instead of --target-env (e.g. 0x04D413FD).\n" +" --fw-version firmware version (e.g. 1.16.0).\n" +" --hw-id hardware tag (e.g. RAK4631, Heltec_v3). Same tag = bootable-compatible;\n" +" a node refuses a .mota whose hw-id is for different hardware (brick-safety).\n" +"\n" +"DELTA OPTIONS\n" +" --codec full|sequential|inplace force the codec (default: full with no --base, else auto-from-hw).\n" +" --inplace-memory nRF52 in-place workspace size (default 0xAE000).\n" +" --inplace-segment nRF52 in-place erase/segment size (default 4096).\n" +" --detools the detools encoder to call (default: 'detools' on PATH). Needed only for deltas.\n" +" --force build the delta even if base and target hardware identities differ.\n" +"\n" +"SIGNING\n" +" --sign Ed25519 private key (hex or raw 32 bytes, from 'motatool keygen'). Signing lets a\n" +" node auto-install the update if it trusts the matching public key. Unsigned still works\n" +" for manual installs.\n" +"\n" +"OUTPUT\n" +" --out-dir where to write the .mota (default: current directory). Keep all your .mota in one\n" +" folder and point 'serve' at it. The file is auto-named:\n" +" __v__.mota\n" +" --out write to exactly this path instead (overrides --out-dir and the auto-name).\n" +"\n" +"EXAMPLES\n" +" # full image, signed, into ./motas\n" +" motatool build --fw firmware.bin --sign signer.key --out-dir ./motas\n" +"\n" +" # full image fetched straight from a release URL\n" +" motatool build --fw https://example.org/RAK_4631_repeater.bin --sign signer.key --out-dir ./motas\n" +"\n" +" # delta from the previous release (codec auto-selected from the hardware)\n" +" motatool build --fw new.bin --base old.bin --sign signer.key --out-dir ./motas\n" +"\n" +" # raw .bin with no EndF identity: supply it explicitly\n" +" motatool build --fw app.bin --hw-id Heltec_v3 --target-env Heltec_v3_repeater --fw-version 1.16.0\n"; +} + +static void help_verify() { + std::cout << +"motatool verify — check that .mota files are valid.\n" +"\n" +"USAGE\n" +" motatool verify [more.mota ...] [--pub ] [--base ]\n" +"\n" +"For each file it checks the structure, that the per-block hashes match the payload, the merkle\n" +"root, the full-image hash (for full images), and the Ed25519 signature (for signed containers).\n" +"It prints 'OK' or 'FAIL ' per file; the exit code is non-zero if any file fails. This is\n" +"the same validation 'serve' runs on a folder before serving.\n" +"\n" +"OPTIONS\n" +" --pub require the container to be signed by THIS public key (hex/raw, *.pub from keygen).\n" +" --base for a sequential delta, apply it to this base and confirm it rebuilds the image.\n" +" (Applies to every file given; full images ignore it; in-place deltas are skipped.)\n" +"\n" +"EXAMPLES\n" +" motatool verify ./motas/*.mota\n" +" motatool verify update.mota --pub signer.key.pub\n" +" motatool verify delta.mota --base old_firmware.bin\n"; +} + +static void help_inspect() { + std::cout << +"motatool inspect — print every field of a .mota's manifest.\n" +"\n" +"USAGE\n" +" motatool inspect \n" +"\n" +"Dumps the parsed manifest (versions, sizes, target/hardware, codec, merkle root, image hash,\n" +"base hash, signer key + signature, approval state, block count) — handy for debugging a package.\n" +"It does not validate integrity; use 'verify' for that.\n" +"\n" +"EXAMPLE\n" +" motatool inspect ./motas/RAK4631_04D413FD_v1.16.0_full_ABCD1234.mota\n"; +} + +static void help_serve() { + std::cout << +"motatool serve — serve a folder of .mota to a MeshCore node over USB serial.\n" +"\n" +"USAGE\n" +" motatool serve --dir --serial [options]\n" +"\n" +"It scans the folder for .mota files, validates each, and serves the valid ones to the node.\n" +"Corrupt/invalid files are reported and skipped — one bad file never stops the rest. The node\n" +"advertises them to the mesh as if it held them, and any node whose hardware matches can fetch\n" +"them. The relay is trustless: fetchers verify every block, so this host never needs the keys.\n" +"\n" +"OPTIONS (--dir and --serial are required)\n" +" --dir folder of .mota to serve (searched recursively by default).\n" +" --serial the node's USB serial port (e.g. /dev/ttyUSB0 or /dev/ttyACM0).\n" +" --baud serial speed (default 115200).\n" +" --no-recursive serve only the top folder; don't descend into sub-folders.\n" +" --no-enable don't auto-send 'ota folder on'/'off' to the node (run them on its CLI yourself).\n" +" -v, --verbose log each request the node makes (COUNT / DESCRIBE / READ).\n" +"\n" +"Leave it running; press Ctrl-C to stop (it tells the node to stop relaying first). It shares the\n" +"same USB cable as the node's text console — the node only pulls bytes while actively fetching.\n" +"\n" +"EXAMPLE\n" +" motatool serve --dir ./motas --serial /dev/ttyUSB0 -v\n"; +} + +static void help_keygen() { + std::cout << +"motatool keygen — generate an Ed25519 signing keypair.\n" +"\n" +"USAGE\n" +" motatool keygen [--out ]\n" +"\n" +"Prints the public key. With --out it writes the private key to and the public key to\n" +".pub (hex). Sign updates with the private key ('build --sign '); trust the\n" +"public key on a node to let it auto-install updates signed by you.\n" +"\n" +"EXAMPLE\n" +" motatool keygen --out signer.key\n"; +} + +static std::string hex8(uint32_t v) { char x[9]; std::snprintf(x, 9, "%08X", v); return x; } + +static int cmd_verify(const Args& a) { + if (a.has("help")) { help_verify(); return 0; } + if (a.pos.empty()) { help_verify(); return 2; } + + std::vector expect_pub; + if (a.has("pub") && !load_priv(a.get("pub"), expect_pub)) { // load_priv accepts a 32-byte hex/raw key + std::cerr << "cannot load --pub key (expect 32-byte hex or raw)\n"; return 2; + } + std::vector base_img; + if (a.has("base")) { + std::vector b; std::string e = read_input(a.get("base"), b); + if (!e.empty()) { std::cerr << "error: " << e << "\n"; return 1; } + std::array bh; base_img = ensure_endf(b, parse_endf_ident(b), bh); + } + + int bad = 0; + for (const auto& f : a.pos) { + std::vector blob; + if (!read_file(f, blob)) { std::cout << "FAIL " << f << " : cannot read\n"; bad++; continue; } + auto probs = verify(blob); + Manifest m; bool parsed = parse(blob, m).empty(); + if (parsed && !expect_pub.empty()) { // --pub: must be signed by this exact key + if (!m.is_signed()) probs.push_back("not signed (but --pub was given)"); + else if (std::memcmp(m.signer.data(), expect_pub.data(), 32) != 0) probs.push_back("signed by a different key than --pub"); + } + if (parsed && !base_img.empty() && !m.is_full()) { // --base: prove a delta rebuilds the image + if (m.codec_id == CODEC_DETOOLS_SEQUENTIAL) { + std::vector patch(blob.begin() + m.payload_off(), blob.begin() + m.payload_off() + m.payload_size); + std::vector recon; + std::string e = detools_apply_seq(a.get("detools", "detools"), base_img, patch, recon); + if (!e.empty()) probs.push_back("delta apply: " + e); + else { auto h = mota::mh32(recon.data(), recon.size()); + if (std::memcmp(h.data(), m.image_hash.data(), 32) != 0) probs.push_back("delta does not rebuild image_hash against --base"); } + } else { + std::cout << "note " << f << " : in-place delta — --base apply-check skipped (bootloader-applied)\n"; + } + } + if (probs.empty()) { + std::cout << "OK " << f << " : " << (m.is_full() ? "full" : "delta") + << " target=" << hex8(m.target_id) + << " v" << version_str(m.fw_version) << " hw=" << (m.hw_id_str().empty()? "?":m.hw_id_str()) + << " " << (m.is_signed() ? "signed" : "unsigned") + << " blocks=" << m.block_count << " size=" << blob.size() << "\n"; + } else { + bad++; + std::cout << "FAIL " << f << " :"; + for (auto& p : probs) std::cout << " [" << p << "]"; + std::cout << "\n"; + } + } + return bad ? 1 : 0; +} + +static int cmd_inspect(const Args& a) { + if (a.has("help")) { help_inspect(); return 0; } + if (a.pos.empty()) { help_inspect(); return 2; } + std::vector blob; + if (!read_file(a.pos[0], blob)) { std::cerr << "cannot read " << a.pos[0] << "\n"; return 1; } + Manifest m; + std::string e = parse(blob, m); + if (!e.empty()) { std::cerr << "not a valid .mota: " << e << "\n"; return 1; } + auto z = [](const uint8_t* p, size_t n){ for (size_t i=0;i in-place, else sequential) +static int infer_codec(const std::string& hw) { + std::string h; for (char c : hw) h.push_back((char)std::tolower((unsigned char)c)); + if (h.rfind("rak", 0) == 0 || h.find("nrf") != std::string::npos || h.find("nordic") != std::string::npos) + return CODEC_DETOOLS_INPLACE; + if (h.find("heltec") != std::string::npos || h.find("esp32") != std::string::npos || + h.find("xiao") != std::string::npos || h.find("tbeam") != std::string::npos || + h.find("tlora") != std::string::npos || h.find("tdeck") != std::string::npos) + return CODEC_DETOOLS_SEQUENTIAL; + return -1; // unknown +} + +static int cmd_build(const Args& a) { + if (a.has("help")) { help_build(); return 0; } + if (!a.has("fw")) { std::cerr << "error: --fw is required\n\n"; help_build(); return 2; } + BuildOpts o; + std::string e = read_input(a.get("fw"), o.fw); + if (!e.empty()) { std::cerr << "error: " << e << "\n"; return 1; } + if (a.has("base")) { + e = read_input(a.get("base"), o.base); + if (!e.empty()) { std::cerr << "error: " << e << "\n"; return 1; } + } + if (a.has("target-id")) { o.have_target = true; o.target_id = (uint32_t)std::strtoul(a.get("target-id").c_str(), nullptr, 0); } + else if (a.has("target-env")) { o.have_target = true; o.target_id = target_id_for_env(a.get("target-env")); } + if (a.has("fw-version")) { + uint32_t v; if (!pack_version(a.get("fw-version"), v)) { std::cerr << "bad --fw-version\n"; return 2; } + o.have_fwver = true; o.fw_version = v; + } + if (a.has("hw-id")) o.hw_id = a.get("hw-id"); + o.force = a.has("force"); + if (a.has("detools")) o.detools = a.get("detools"); + if (a.has("inplace-memory")) o.inplace_memory = (uint32_t)std::strtoul(a.get("inplace-memory").c_str(), nullptr, 0); + if (a.has("inplace-segment")) o.inplace_segment = (uint32_t)std::strtoul(a.get("inplace-segment").c_str(), nullptr, 0); + if (a.has("sign")) { + if (!load_priv(a.get("sign"), o.sign_priv)) { std::cerr << "cannot load signing key (expect 32-byte hex or raw)\n"; return 1; } + } + + bool is_delta = !o.base.empty(); + // resolve hw (flags override EndF) to pick the codec + FwIdent fid = parse_endf_ident(o.fw); + std::string hw = o.hw_id.empty() ? fid.hw_id : o.hw_id; + std::string codec = a.get("codec", is_delta ? "auto" : "full"); + if (!is_delta) o.codec = CODEC_FULL; + else if (codec == "sequential") o.codec = CODEC_DETOOLS_SEQUENTIAL; + else if (codec == "inplace") o.codec = CODEC_DETOOLS_INPLACE; + else if (codec == "full") { std::cerr << "a --base delta cannot use --codec full\n"; return 2; } + else { // auto + int c = infer_codec(hw); + if (c < 0) { std::cerr << "cannot infer codec from hw '" << hw << "' — pass --codec sequential|inplace\n"; return 2; } + o.codec = (uint8_t)c; + std::cerr << "note: codec auto-selected = " << (c == CODEC_DETOOLS_INPLACE ? "inplace" : "sequential") + << " (from hw '" << (hw.empty()?"?":hw) << "')\n"; + } + + std::vector blob; std::string name; + e = build(o, blob, name); + if (!e.empty()) { std::cerr << "error: " << e << "\n"; return 1; } + + // sanity: the tool's own output must verify + auto probs = verify(blob); + if (!probs.empty()) { + std::cerr << "internal error: built .mota fails verification:"; + for (auto& p : probs) std::cerr << " [" << p << "]"; + std::cerr << "\n"; return 1; + } + + std::string outpath; + if (a.has("out")) { // explicit output path (overrides --out-dir + auto-name) + outpath = a.get("out"); + fs::path parent = fs::path(outpath).parent_path(); + if (!parent.empty()) { std::error_code ec; fs::create_directories(parent, ec); } + } else { + std::string outdir = a.get("out-dir", "."); + std::error_code ec; fs::create_directories(outdir, ec); + outpath = (fs::path(outdir) / name).string(); + } + if (!write_file(outpath, blob.data(), blob.size())) { std::cerr << "cannot write " << outpath << "\n"; return 1; } + + Manifest m; parse(blob, m); + std::cout << "wrote " << outpath << "\n" + << " " << (m.is_full() ? "full" : (m.codec_id == CODEC_DETOOLS_INPLACE ? "in-place delta" : "sequential delta")) + << " target=" << [&]{char x[9];std::snprintf(x,9,"%08X",m.target_id);return std::string(x);}() + << " v" << version_str(m.fw_version) << " hw=" << (m.hw_id_str().empty()?"?":m.hw_id_str()) + << " " << (m.is_signed()?"signed":"unsigned") << "\n" + << " image=" << m.image_size << "B payload=" << m.payload_size << "B blocks=" << m.block_count + << " total=" << blob.size() << "B\n"; + return 0; +} + +static int cmd_keygen(const Args& a) { + if (a.has("help")) { help_keygen(); return 0; } + uint8_t priv[32], pub[32]; + if (!ed25519_keygen(priv, pub)) { std::cerr << "keygen failed\n"; return 1; } + std::string out = a.get("out", a.get("out-priv")); + std::string ph = to_hex(priv, 32), kh = to_hex(pub, 32); + if (!out.empty()) { + std::string pp = ph + "\n", kp = kh + "\n"; + if (!write_file(out, (const uint8_t*)pp.data(), pp.size()) || + !write_file(out + ".pub", (const uint8_t*)kp.data(), kp.size())) { std::cerr << "write failed\n"; return 1; } + std::cout << "private -> " << out << "\npublic -> " << out << ".pub\n"; + } + std::cout << "pubkey: " << kh << "\n"; + return 0; +} + +static int cmd_serve(const Args& a) { + if (a.has("help")) { help_serve(); return 0; } + if (!a.has("dir") || !a.has("serial")) { + std::cerr << "error: --dir and --serial are required\n\n"; help_serve(); return 2; + } + bool recursive = !a.has("no-recursive"); + bool verbose = a.has("verbose"); + Folder folder; + size_t n = folder.scan(a.get("dir"), recursive, + [](const std::string& p, const std::string& why) { + std::cerr << " ! skip " << p << " : " << why << "\n"; + }); + std::cout << "motatool serve: " << n << " valid .mota in " << a.get("dir") + << (recursive ? " (recursive)" : "") << "\n"; + for (const auto& s : folder.all()) { + std::cout << " - " << fs::path(s.path).filename().string() + << " : mid=" << to_hex(s.m.merkle_root.data(), 4) + << " target=" << [&]{char x[9];std::snprintf(x,9,"%08X",s.m.target_id);return std::string(x);}() + << " v" << version_str(s.m.fw_version) + << " " << (s.m.is_full() ? "full" : (s.m.codec_id == CODEC_DETOOLS_INPLACE ? "ipdelta" : "seqdelta")) + << " " << (s.m.is_signed() ? "signed" : "unsigned") + << " blocks=" << s.m.block_count << " size=" << s.bytes.size() << "\n"; + } + if (n == 0) std::cerr << " (nothing valid to serve)\n"; + + SerialTransport t; + std::string e = t.open(a.get("serial"), std::atoi(a.get("baud", "115200").c_str())); + if (!e.empty()) { std::cerr << "error: " << e << "\n"; return 1; } + + std::signal(SIGINT, on_sigint); + bool enable = !a.has("no-enable"); + if (enable) { usleep(500000); t.write_str("ota folder on\r\n"); std::cout << "sent `ota folder on`\n"; } + std::cout << "serving on " << a.get("serial") << " @ " << a.get("baud", "115200") << " — Ctrl-C to stop\n"; + + SeederCore core(folder); + serve_serial(t, core, verbose, + [](const std::string& l) { std::cout << " [dev] " << l << "\n"; }, &g_stop); + + if (enable) { t.write_str("ota folder off\r\n"); usleep(200000); } + std::cout << "\nbye\n"; + return 0; +} + +int main(int argc, char** argv) { + if (argc < 2) { help_top(); return 2; } + std::string cmd = argv[1]; + if (cmd == "help" || cmd == "-h" || cmd == "--help") { help_top(); return 0; } + + std::vector bools = {"force","verbose","no-recursive","no-enable"}; + Args a = parse_args(argc, argv, 2, bools); + if (cmd == "build") return cmd_build(a); + if (cmd == "verify") return cmd_verify(a); + if (cmd == "inspect") return cmd_inspect(a); + if (cmd == "serve") return cmd_serve(a); + if (cmd == "keygen") return cmd_keygen(a); + std::cerr << "unknown command: " << cmd << "\n\n"; + help_top(); + return 2; +} diff --git a/tools/motatool/src/mota.cpp b/tools/motatool/src/mota.cpp new file mode 100644 index 00000000..8703c65c --- /dev/null +++ b/tools/motatool/src/mota.cpp @@ -0,0 +1,314 @@ +#include "mota.h" +#include "crypto.h" +#include "util.h" +#include +#include +#include + +namespace mota { + +// ---- merkle (mirror of src/helpers/ota/MerkleTree.cpp) -------------------------------------------- +static void merkle_leaf(uint8_t out[4], const uint8_t* block, size_t len) { sha256_trunc(out, 4, block, len); } +static void merkle_combine(uint8_t out[4], const uint8_t* left, const uint8_t* right) { + uint8_t buf[8]; std::memcpy(buf, left, 4); std::memcpy(buf + 4, right, 4); sha256_trunc(out, 4, buf, 8); +} +static void merkle_root(uint8_t out[4], const uint8_t* leaves, uint32_t count) { + if (count == 0) { std::memset(out, 0, 4); return; } + if (count == 1) { std::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]; std::memcpy(cur, leaves + (size_t)i * 4, 4); + uint32_t level = 0; + while (valid[level]) { merkle_combine(cur, peaks[level], cur); valid[level] = false; level++; } + std::memcpy(peaks[level], cur, 4); valid[level] = true; + } + int level = 0; while (level < 32 && !valid[level]) level++; + uint8_t acc[4]; std::memcpy(acc, peaks[level], 4); + for (int l = level + 1; l < 32; l++) if (valid[l]) merkle_combine(acc, peaks[l], acc); + std::memcpy(out, acc, 4); +} +// leaves[] over the payload (last block short, no padding) +static std::vector leaf_hashes(const uint8_t* payload, uint32_t size, uint32_t bs) { + uint32_t bc = (size + bs - 1) / bs; + std::vector leaves(bc * 4); + for (uint32_t i = 0; i < bc; i++) { + uint32_t off = i * bs, blen = (off + bs <= size) ? bs : (size - off); + merkle_leaf(leaves.data() + (size_t)i * 4, payload + off, blen); + } + return leaves; +} + +std::string Manifest::hw_id_str() const { + size_t n = 0; while (n < hw_id.size() && hw_id[n]) n++; + return std::string((const char*)hw_id.data(), n); +} + +// ---- parse ----------------------------------------------------------------------------------------- +std::string parse(const std::vector& b, Manifest& m) { + m = Manifest(); + if (b.size() < 8 + MOTA_MFL + 5) return "too small for a .mota"; + if (std::memcmp(b.data(), MOTA_MAGIC, 4) != 0) return "bad MAGIC (not a .mota)"; + uint32_t total = rd_u32(b.data() + 4); + if (total != b.size()) return "MOTA_TOTAL_SIZE != file length"; + if (std::memcmp(b.data() + b.size() - 5, MOTA_TRAILER, 5) != 0) return "bad TRAILER"; + const uint8_t* mf = b.data() + 8; // manifest start + m.format_ver = mf[M_OFF_FORMAT_VER]; + if (m.format_ver != FORMAT_VER) return "unsupported format_ver"; + m.flags = mf[M_OFF_FLAGS]; + m.hash_algo = mf[M_OFF_HASH_ALGO]; + m.target_id = rd_u32(mf + M_OFF_TARGET_ID); + m.fw_version = rd_u32(mf + M_OFF_FW_VERSION); + m.image_size = rd_u32(mf + M_OFF_IMAGE_SIZE); + m.payload_size = rd_u32(mf + M_OFF_PAYLOAD_SIZE); + m.block_size_log2 = mf[M_OFF_BLOCK_SIZE_LOG2]; + std::memcpy(m.merkle_root.data(), mf + M_OFF_MERKLE_ROOT, 4); + std::memcpy(m.image_hash.data(), mf + M_OFF_IMAGE_HASH, 32); + m.codec_id = mf[M_OFF_CODEC_ID]; + std::memcpy(m.hw_id.data(), mf + M_OFF_HW_ID, 32); + std::memcpy(m.base_hash.data(), mf + M_OFF_BASE_HASH, 8); + std::memcpy(m.signer.data(), mf + M_OFF_SIGNER, 32); + std::memcpy(m.signature.data(), mf + M_OFF_SIGNATURE, 64); + std::memcpy(m.approval.data(), mf + M_OFF_APPROVAL, 4); + if (m.block_size_log2 == 0 || m.block_size_log2 > 24 || m.payload_size == 0) return "bad block_size/payload"; + m.block_count = (m.payload_size + m.block_size() - 1) / m.block_size(); + if (m.block_count == 0 || m.block_count > 0xFFFFu) return "block_count out of range"; + if (m.total_size() != b.size()) return "geometry (leaves+payload) != file length"; + return ""; +} + +// ---- verify ---------------------------------------------------------------------------------------- +std::vector verify(const std::vector& b) { + std::vector probs; + Manifest m; + std::string e = parse(b, m); + if (!e.empty()) { probs.push_back(e); return probs; } // unparseable: a single, fatal problem + + const uint8_t* leaves = b.data() + m.leaves_off(); + const uint8_t* payload = b.data() + m.payload_off(); + + // recompute leaves[] from the payload -> catches payload corruption + std::vector calc = leaf_hashes(payload, m.payload_size, m.block_size()); + if (calc.size() != m.block_count * 4u || std::memcmp(calc.data(), leaves, calc.size()) != 0) + probs.push_back("leaves[] do not match the payload (corruption)"); + + uint8_t root[4]; merkle_root(root, leaves, m.block_count); + if (std::memcmp(root, m.merkle_root.data(), 4) != 0) + probs.push_back("merkle_root mismatch"); + + if (m.is_full()) { + auto h = mh32(payload, m.payload_size); // full: payload IS the image + if (std::memcmp(h.data(), m.image_hash.data(), 32) != 0) + probs.push_back("image_hash mismatch (full image)"); + } + // (delta image_hash needs the base image -> not checked at relay time; payload integrity is covered above) + + if (m.is_signed()) { + if (!ed25519_verify(m.signature.data(), b.data() + 8, MOTA_SIGNED_LEN, m.signer.data())) + probs.push_back("Ed25519 signature INVALID"); + } + // a distributed .mota must not be pre-approved + if (std::memcmp(m.approval.data(), APPROVAL_YES, 4) == 0) + probs.push_back("container is pre-approved (must be FF FF FF FF on the wire)"); + return probs; +} + +// ---- EndF ----------------------------------------------------------------------------------------- +bool has_endf(const std::vector& img) { + if (img.size() < ENDF_LEN) return false; + const uint8_t* t = img.data() + img.size() - ENDF_LEN; + if (std::memcmp(t, ENDF_MAGIC, 4) != 0) return false; + if (rd_u32(t + 4) != img.size() - ENDF_LEN) return false; + auto h = mh8(img.data(), img.size() - ENDF_LEN); + return std::memcmp(h.data(), t + 8, 8) == 0; +} + +FwIdent parse_endf_ident(const std::vector& img) { + FwIdent id; + if (!has_endf(img)) return id; + const uint8_t* t = img.data() + img.size() - ENDF_LEN; + id.fw_version = rd_u32(t + ENDF_OFF_FWVER); + id.target_id = rd_u32(t + ENDF_OFF_TARGET); + size_t n = 0; while (n < HW_ID_LEN && t[ENDF_OFF_HWID + n]) n++; + id.hw_id.assign((const char*)t + ENDF_OFF_HWID, n); + return id; +} + +std::vector ensure_endf(const std::vector& img, const FwIdent& id, + std::array& body_hash8) { + if (has_endf(img)) { // already trailed: keep its identity, read its hash + const uint8_t* t = img.data() + img.size() - ENDF_LEN; + std::memcpy(body_hash8.data(), t + 8, 8); + return img; + } + body_hash8 = mh8(img.data(), img.size()); + std::vector out = img; + out.insert(out.end(), ENDF_MAGIC, ENDF_MAGIC + 4); + uint8_t u[4]; wr_u32(u, (uint32_t)img.size()); out.insert(out.end(), u, u + 4); + out.insert(out.end(), body_hash8.begin(), body_hash8.end()); + wr_u32(u, id.fw_version); out.insert(out.end(), u, u + 4); + wr_u32(u, id.target_id); out.insert(out.end(), u, u + 4); + uint8_t hw[HW_ID_LEN] = {0}; + std::memcpy(hw, id.hw_id.data(), id.hw_id.size() < HW_ID_LEN ? id.hw_id.size() : HW_ID_LEN); + out.insert(out.end(), hw, hw + HW_ID_LEN); + return out; +} + +uint32_t target_id_for_env(const std::string& env) { + auto h = mh4((const uint8_t*)env.data(), env.size()); + return rd_u32(h.data()); +} + +bool pack_version(const std::string& s, uint32_t& out) { + uint32_t parts[4] = {0,0,0,0}; int n = 0; bool any = false; + std::stringstream ss(s); std::string tok; + while (std::getline(ss, tok, '.') && n < 4) { + if (tok.empty()) return false; + for (char c : tok) if (c < '0' || c > '9') return false; + parts[n++] = (uint32_t)std::strtoul(tok.c_str(), nullptr, 10); any = true; + } + if (!any) return false; + out = ((parts[0] & 0xFF) << 24) | ((parts[1] & 0xFF) << 16) | ((parts[2] & 0xFF) << 8) | (parts[3] & 0xFF); + return true; +} + +// ---- build ---------------------------------------------------------------------------------------- +// detools delta encode: write base/new images to temp files, run the detools CLI, read the patch back. +static std::string detools_delta(const std::string& detools, uint8_t codec, + const std::vector& base_img, const std::vector& new_img, + uint32_t mem, uint32_t seg, std::vector& patch) { + char fb[] = "/tmp/motaXXXXXX", fn[] = "/tmp/motaXXXXXX", fp[] = "/tmp/motaXXXXXX"; + int a = mkstemp(fb), c = mkstemp(fn), d = mkstemp(fp); + if (a < 0 || c < 0 || d < 0) return "mkstemp failed"; + close(a); close(c); close(d); + std::string err; + if (!write_file(fb, base_img.data(), base_img.size()) || !write_file(fn, new_img.data(), new_img.size())) { + err = "writing temp images failed"; + } else { + std::vector argv; + if (codec == CODEC_DETOOLS_SEQUENTIAL) + argv = {detools, "create_patch", "-c", "crle", "-t", "sequential", fb, fn, fp}; + else + argv = {detools, "create_patch_in_place", "-c", "crle", + "--memory-size", std::to_string(mem), "--segment-size", std::to_string(seg), fb, fn, fp}; + int rc = run_argv(argv, /*quiet=*/true); // hide detools' success chatter; errors keep stderr + if (rc == 127) err = "could not run detools (install it / pass --detools )"; + else if (rc != 0) err = "detools exited with code " + std::to_string(rc); + else if (!read_file(fp, patch) || patch.empty()) err = "reading detools patch failed"; + } + unlink(fb); unlink(fn); unlink(fp); + return err; +} + +std::string detools_apply_seq(const std::string& detools, const std::vector& base_img, + const std::vector& patch, std::vector& out) { + char ff[] = "/tmp/motaXXXXXX", fpp[] = "/tmp/motaXXXXXX", ft[] = "/tmp/motaXXXXXX"; + int a = mkstemp(ff), c = mkstemp(fpp), d = mkstemp(ft); + if (a < 0 || c < 0 || d < 0) return "mkstemp failed"; + close(a); close(c); close(d); + std::string err; + if (!write_file(ff, base_img.data(), base_img.size()) || !write_file(fpp, patch.data(), patch.size())) { + err = "writing temp files failed"; + } else { + int rc = run_argv({detools, "apply_patch", ff, fpp, ft}, /*quiet=*/true); // + if (rc == 127) err = "could not run detools (install it / pass --detools )"; + else if (rc != 0) err = "detools apply_patch exited with code " + std::to_string(rc); + else if (!read_file(ft, out) || out.empty()) err = "reading reconstructed image failed"; + } + unlink(ff); unlink(fpp); unlink(ft); + return err; +} + +std::string build(const BuildOpts& o, std::vector& out, std::string& suggested_name) { + bool is_delta = !o.base.empty(); + + // resolve identity: explicit flags override the firmware's self-describing EndF + FwIdent fid = parse_endf_ident(o.fw); + uint32_t target = o.have_target ? o.target_id : fid.target_id; + uint32_t fwver = o.have_fwver ? o.fw_version : fid.fw_version; + std::string hw = !o.hw_id.empty() ? o.hw_id : fid.hw_id; + FwIdent ident{fwver, target, hw}; + + std::array new_bh{}, base_bh{}; + std::vector new_img = ensure_endf(o.fw, ident, new_bh); + + uint8_t codec = o.codec; + std::vector payload; + std::array base_hash{}; + + if (!is_delta) { + codec = CODEC_FULL; + payload = new_img; // full: payload IS the image + } else { + if (codec == CODEC_FULL) return "a base image was given but --codec is full"; + FwIdent base_id = parse_endf_ident(o.base); + std::vector base_img = ensure_endf(o.base, base_id, base_bh); + base_hash = base_bh; + // cross-hardware delta guard (read from EndF identity, not filenames) + if (!o.force && base_id.any() && fid.any()) { + bool hw_ok = base_id.hw_id.empty() || fid.hw_id.empty() || base_id.hw_id == fid.hw_id; + bool tgt_ok = !base_id.target_id || !fid.target_id || base_id.target_id == fid.target_id; + if (!hw_ok || !tgt_ok) + return "base/target firmware identity differ (hw '" + base_id.hw_id + "' vs '" + fid.hw_id + + "') — refusing cross-hardware delta (use --force to override)"; + } + std::string e = detools_delta(o.detools, codec, base_img, new_img, + o.inplace_memory, o.inplace_segment, payload); + if (!e.empty()) return e; + } + + uint32_t bs = o.block_size, image_size = (uint32_t)new_img.size(); + std::vector leaves = leaf_hashes(payload.data(), (uint32_t)payload.size(), bs); + uint32_t bc = (uint32_t)leaves.size() / 4; + if (bc == 0 || bc > 0xFFFFu) return "payload yields an invalid block count"; + uint8_t root[4]; merkle_root(root, leaves.data(), bc); + auto image_hash = mh32(new_img.data(), new_img.size()); + + bool signed_ = !o.sign_priv.empty(); + uint8_t flags = (is_delta ? 0 : MFLAG_FULL) | (signed_ ? MFLAG_SIGNED : 0); + + // assemble the fixed 197-byte manifest-minus-leaves + std::vector mf(MOTA_MFL, 0); + mf[M_OFF_FORMAT_VER] = FORMAT_VER; mf[M_OFF_FLAGS] = flags; mf[M_OFF_HASH_ALGO] = HASH_ALGO_SHA256; + wr_u32(mf.data() + M_OFF_TARGET_ID, target); + wr_u32(mf.data() + M_OFF_FW_VERSION, fwver); + wr_u32(mf.data() + M_OFF_IMAGE_SIZE, image_size); + wr_u32(mf.data() + M_OFF_PAYLOAD_SIZE, (uint32_t)payload.size()); + // block_size_log2 + { uint32_t v = bs, l = 0; while (v > 1) { v >>= 1; l++; } mf[M_OFF_BLOCK_SIZE_LOG2] = (uint8_t)l; } + std::memcpy(mf.data() + M_OFF_MERKLE_ROOT, root, 4); + std::memcpy(mf.data() + M_OFF_IMAGE_HASH, image_hash.data(), 32); + mf[M_OFF_CODEC_ID] = codec; + std::memcpy(mf.data() + M_OFF_HW_ID, hw.data(), hw.size() < HW_ID_LEN ? hw.size() : HW_ID_LEN); + if (is_delta) std::memcpy(mf.data() + M_OFF_BASE_HASH, base_hash.data(), 8); // zero for full + if (signed_) { + uint8_t pub[32]; + if (o.sign_priv.size() != 32) return "signing key must be a 32-byte raw private seed"; + if (!ed25519_pub_from_priv(pub, o.sign_priv.data())) return "bad signing key"; + std::memcpy(mf.data() + M_OFF_SIGNER, pub, 32); + uint8_t sig[64]; + if (!ed25519_sign(sig, mf.data(), MOTA_SIGNED_LEN, o.sign_priv.data())) return "signing failed"; + std::memcpy(mf.data() + M_OFF_SIGNATURE, sig, 64); + } + std::memcpy(mf.data() + M_OFF_APPROVAL, APPROVAL_NOT, 4); + + // container = MAGIC(4) total(4) manifest leaves[] payload trailer(5) + uint32_t total = 8 + MOTA_MFL + bc * 4 + (uint32_t)payload.size() + 5; + out.clear(); out.reserve(total); + out.insert(out.end(), MOTA_MAGIC, MOTA_MAGIC + 4); + uint8_t u[4]; wr_u32(u, total); out.insert(out.end(), u, u + 4); + out.insert(out.end(), mf.begin(), mf.end()); + out.insert(out.end(), leaves.begin(), leaves.end()); + out.insert(out.end(), payload.begin(), payload.end()); + out.insert(out.end(), MOTA_TRAILER, MOTA_TRAILER + 5); + + // suggested name: __v__.mota (descriptive + unique, one folder) + const char* kind = !is_delta ? "full" : (codec == CODEC_DETOOLS_INPLACE ? "ipdelta" : "seqdelta"); + char tgt[9]; std::snprintf(tgt, sizeof(tgt), "%08X", target); + char vbuf[24]; std::snprintf(vbuf, sizeof(vbuf), "%u.%u.%u", + (fwver >> 24) & 0xFF, (fwver >> 16) & 0xFF, (fwver >> 8) & 0xFF); + suggested_name = (hw.empty() ? std::string("fw") : hw) + "_" + tgt + "_v" + vbuf + "_" + kind + + "_" + to_hex(root, 4) + ".mota"; + return ""; +} + +} // namespace mota diff --git a/tools/motatool/src/mota.h b/tools/motatool/src/mota.h new file mode 100644 index 00000000..cff21e3c --- /dev/null +++ b/tools/motatool/src/mota.h @@ -0,0 +1,79 @@ +// `.mota` container: parse, integrity-verify, and build (full / detools-delta). EndF identity helpers. +// Mirrors tools/mota/motalib.py and src/helpers/ota/MotaContainer.cpp (the single source of truth). +#pragma once +#include "mota_format.h" +#include +#include +#include + +namespace mota { + +struct FwIdent { + uint32_t fw_version = 0; + uint32_t target_id = 0; + std::string hw_id; // NUL-trimmed + bool any() const { return fw_version || target_id || !hw_id.empty(); } +}; + +struct Manifest { + uint8_t format_ver = 0, flags = 0, hash_algo = 0, codec_id = 0, block_size_log2 = 0; + uint32_t target_id = 0, fw_version = 0, image_size = 0, payload_size = 0, block_count = 0; + std::array merkle_root{}; + std::array image_hash{}; + std::array hw_id{}; // raw 32 bytes (NUL-padded) + std::array base_hash{}; + std::array signer{}; + std::array signature{}; + std::array approval{}; + + 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; } + uint32_t leaves_off() const { return 8 + MOTA_MFL; } + uint32_t payload_off() const { return leaves_off() + block_count * 4; } + uint32_t total_size() const { return payload_off() + payload_size + 5; } + std::string hw_id_str() const; +}; + +// Parse + validate framing and the fixed layout. Returns "" on success (fills `out`), else an error. +std::string parse(const std::vector& blob, Manifest& out); + +// Content-integrity check: recompute leaves[] from the payload vs merkle_root, the merkle root, the +// FULL image_hash, and (if signed) the Ed25519 signature against the embedded signer key. Returns a list +// of problems (empty => valid). A delta's image_hash is not checked here (it needs the base image). +std::vector verify(const std::vector& blob); + +// ---- EndF identity (fixed 56-byte trailer) ---- +bool has_endf(const std::vector& image); +FwIdent parse_endf_ident(const std::vector& image); // zeros if no EndF +// Append a 56-byte EndF (with identity) if absent; returns the image and sets body_hash8. Idempotent. +std::vector ensure_endf(const std::vector& image, const FwIdent& id, + std::array& body_hash8); + +uint32_t target_id_for_env(const std::string& env); // sha2-256:4(env) as LE uint32 +bool pack_version(const std::string& s, uint32_t& out); // "1.16.0[.pre]" -> packed uint32 + +// ---- build ---- +struct BuildOpts { + std::vector fw; // NEW firmware (raw or already-EndF'd) + std::vector base; // base image for a delta (empty => full) + uint8_t codec = CODEC_FULL; // CODEC_FULL / _SEQUENTIAL / _INPLACE + bool have_target = false; uint32_t target_id = 0; // overrides EndF + bool have_fwver = false; uint32_t fw_version = 0; // overrides EndF + std::string hw_id; // override; else from EndF + std::vector sign_priv; // 32-byte raw private seed (empty => unsigned) + uint32_t block_size = DEFAULT_BLOCK_SIZE; + uint32_t inplace_memory = NRF52_INPLACE_MEMORY, inplace_segment = NRF52_INPLACE_SEGMENT; + std::string detools = "detools"; // detools CLI path (delta encoding only) + bool force = false; // override the cross-hardware delta guard +}; +// Returns "" + fills `out` and a suggested file name on success; else an error string. +std::string build(const BuildOpts& o, std::vector& out, std::string& suggested_name); + +// Apply a SEQUENTIAL delta `patch` to `base_img` via the detools CLI -> reconstructed image in `out`. +// Used by `verify --base` to confirm a delta actually rebuilds the expected image (matches mota.py: +// in-place deltas aren't apply-checked here — the bootloader host-harness covers that path). +std::string detools_apply_seq(const std::string& detools, const std::vector& base_img, + const std::vector& patch, std::vector& out); + +} // namespace mota diff --git a/tools/motatool/src/mota_format.h b/tools/motatool/src/mota_format.h new file mode 100644 index 00000000..1c0f875d --- /dev/null +++ b/tools/motatool/src/mota_format.h @@ -0,0 +1,90 @@ +// MeshCore `.mota` on-wire constants and fixed layout — a C++-friendly mirror of +// src/helpers/ota/OtaFormat.h. Keep byte-identical with that file and docs/ota_protocol.md. +// +// The format is FIXED-LAYOUT: every manifest field is at a constant offset and always present +// (base_hash/signer_pubkey/signature are zero-filled when not applicable). Only leaves[] varies. +#pragma once +#include +#include + +namespace mota { + +// container framing +static constexpr uint8_t MOTA_MAGIC[4] = {'m','O','T','A'}; +static constexpr uint8_t MOTA_TRAILER[5] = {'v','k','4','9','6'}; +static constexpr uint8_t ENDF_MAGIC[4] = {'E','n','d','F'}; + +static constexpr uint8_t HASH_ALGO_SHA256 = 0x12; +static constexpr uint8_t FORMAT_VER = 0x02; + +static constexpr uint8_t MFLAG_FULL = 0x01; +static constexpr uint8_t MFLAG_SIGNED = 0x02; + +static constexpr uint8_t CODEC_FULL = 0; +static constexpr uint8_t CODEC_DETOOLS_SEQUENTIAL = 1; // ESP32 A/B +static constexpr uint8_t CODEC_DETOOLS_INPLACE = 2; // nRF52 single-slot + +// hash truncations (sha2-256:N = first N bytes of the SHA-256 digest) +static constexpr size_t MH4 = 4, MH8 = 8, MH32 = 32; + +static constexpr uint8_t APPROVAL_NOT[4] = {0xFF,0xFF,0xFF,0xFF}; +static constexpr uint8_t APPROVAL_YES[4] = {'A','P','R','V'}; + +// EndF trailer (fixed 56 bytes): marker(4) body_len(4) body_hash8(8) fw_version(4) target_id(4) hw_id(32) +static constexpr uint32_t ENDF_LEN = 56; +static constexpr uint32_t ENDF_OFF_FWVER = 16; +static constexpr uint32_t ENDF_OFF_TARGET = 20; +static constexpr uint32_t ENDF_OFF_HWID = 24; + +static constexpr uint8_t HW_ID_LEN = 32; + +// manifest fixed offsets (within the manifest, i.e. container offset = 8 + these) +static constexpr uint32_t M_OFF_FORMAT_VER = 0; +static constexpr uint32_t M_OFF_FLAGS = 1; +static constexpr uint32_t M_OFF_HASH_ALGO = 2; +static constexpr uint32_t M_OFF_TARGET_ID = 3; +static constexpr uint32_t M_OFF_FW_VERSION = 7; +static constexpr uint32_t M_OFF_IMAGE_SIZE = 11; +static constexpr uint32_t M_OFF_PAYLOAD_SIZE = 15; +static constexpr uint32_t M_OFF_BLOCK_SIZE_LOG2 = 19; +static constexpr uint32_t M_OFF_MERKLE_ROOT = 20; // 4 +static constexpr uint32_t M_OFF_IMAGE_HASH = 24; // 32 +static constexpr uint32_t M_OFF_CODEC_ID = 56; // 1 +static constexpr uint32_t M_OFF_HW_ID = 57; // 32 +static constexpr uint32_t M_OFF_BASE_HASH = 89; // 8 (zero if FULL) +static constexpr uint32_t M_OFF_SIGNER = 97; // 32 (zero if unsigned) +static constexpr uint32_t M_OFF_SIGNATURE = 129; // 64 (zero if unsigned) +static constexpr uint32_t M_OFF_APPROVAL = 193; // 4 +static constexpr uint32_t MOTA_MFL = 197; // manifest-minus-leaves length (constant) +static constexpr uint32_t MOTA_SIGNED_LEN = 129; // signature covers manifest[0, 129) + +static constexpr uint32_t DEFAULT_BLOCK_SIZE = 1024; +static constexpr uint8_t DEFAULT_BLOCK_SIZE_LOG2 = 10; + +// nRF52 in-place workspace (FS_START 0xD4000 - APP_BASE 0x26000), segment = flash page. +// Mirror of OtaFlashLayout_nrf52.h; used as the default --inplace-memory. +static constexpr uint32_t NRF52_INPLACE_MEMORY = 0x000D4000u - 0x00026000u; // 0xAE000 +static constexpr uint32_t NRF52_INPLACE_SEGMENT = 4096; + +// ---- mota-seeder transport protocol (mirror of src/helpers/ota/MotaSeederProto.h) ---- +// Request (client -> server): 'M' 'S' op(1) args... xsum(1 = XOR of op+args) +// Response (server -> client): 'm' 's' op(1) status(1) payload... xsum(1 = XOR of all prior) +static constexpr uint8_t MS_REQ_MAGIC0 = 'M', MS_REQ_MAGIC1 = 'S'; +static constexpr uint8_t MS_RSP_MAGIC0 = 'm', MS_RSP_MAGIC1 = 's'; +static constexpr uint8_t MS_OP_COUNT = 0x01; // -> count(1) +static constexpr uint8_t MS_OP_DESCRIBE = 0x02; // idx(1) -> MotaDesc(38) +static constexpr uint8_t MS_OP_READ = 0x03; // idx(1) off(4) len(2) -> bytes +static constexpr uint8_t MS_STATUS_OK = 0x00; +static constexpr uint8_t MS_STATUS_ERR = 0x01; +static constexpr uint16_t MOTA_DESC_WIRE = 38; +// MotaDesc wire (38 B): mid[4] target_id(4) fw_version(4) codec(1) flags(1) total_size(4) +// leaves_off(4) block_count(4) payload_off(4) payload_size(4) [+2 reserved] + +inline 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); +} +inline void wr_u32(uint8_t* p, uint32_t v) { + p[0] = (uint8_t)v; p[1] = (uint8_t)(v >> 8); p[2] = (uint8_t)(v >> 16); p[3] = (uint8_t)(v >> 24); +} + +} // namespace mota diff --git a/tools/motatool/src/serve.cpp b/tools/motatool/src/serve.cpp new file mode 100644 index 00000000..5812f0a9 --- /dev/null +++ b/tools/motatool/src/serve.cpp @@ -0,0 +1,209 @@ +#include "serve.h" +#include +#include +#include +#include +#include +#include +#include "util.h" + +namespace fs = std::filesystem; + +namespace mota { + +// ---- Folder: recursive scan + per-file validation ------------------------------------------------- +size_t Folder::scan(const std::string& dir, bool recursive, + const std::function& warn) { + motas_.clear(); + std::error_code ec; + auto consider = [&](const fs::path& p) { + if (p.extension() != ".mota") return; // skip non-.mota files + ServedMota sm; sm.path = p.string(); + if (!read_file(sm.path, sm.bytes)) { warn(sm.path, "cannot read"); return; } + auto probs = verify(sm.bytes); // merkle + image_hash + signature + if (!probs.empty()) { + std::string why = probs[0]; + for (size_t i = 1; i < probs.size(); i++) why += "; " + probs[i]; + warn(sm.path, why); // warn + exclude (don't sink the rest) + return; + } + parse(sm.bytes, sm.m); // already validated above + motas_.push_back(std::move(sm)); + }; + if (recursive) { + for (auto it = fs::recursive_directory_iterator(dir, ec); + !ec && it != fs::recursive_directory_iterator(); it.increment(ec)) + if (it->is_regular_file(ec)) consider(it->path()); + } else { + for (auto it = fs::directory_iterator(dir, ec); + !ec && it != fs::directory_iterator(); it.increment(ec)) + if (it->is_regular_file(ec)) consider(it->path()); + } + // stable, deterministic catalog order (indices are how the device addresses motas) + std::sort(motas_.begin(), motas_.end(), + [](const ServedMota& a, const ServedMota& b) { return a.path < b.path; }); + return motas_.size(); +} + +// ---- SeederCore (transport-agnostic) -------------------------------------------------------------- +std::array SeederCore::describe(const ServedMota& s) { + std::array w{}; + std::memcpy(w.data(), s.m.merkle_root.data(), 4); // mid + wr_u32(w.data() + 4, s.m.target_id); + wr_u32(w.data() + 8, s.m.fw_version); + w[12] = s.m.codec_id; + w[13] = s.m.flags; + wr_u32(w.data() + 14, (uint32_t)s.bytes.size()); // total_size + wr_u32(w.data() + 18, s.m.leaves_off()); + wr_u32(w.data() + 22, s.m.block_count); + wr_u32(w.data() + 26, s.m.payload_off()); + wr_u32(w.data() + 30, s.m.payload_size); + // [34..38) reserved 0 + return w; +} + +bool SeederCore::handle(uint8_t op, const uint8_t* args, size_t arglen, + uint8_t& status, std::vector& payload) const { + payload.clear(); + status = MS_STATUS_OK; + if (op == MS_OP_COUNT) { + payload.push_back((uint8_t)(folder_.count() > 255 ? 255 : folder_.count())); + return true; + } + if (op == MS_OP_DESCRIBE) { + if (arglen < 1) return false; + const ServedMota* s = folder_.at(args[0]); + if (!s) { status = MS_STATUS_ERR; return true; } + auto w = describe(*s); + payload.assign(w.begin(), w.end()); + return true; + } + if (op == MS_OP_READ) { + if (arglen < 7) return false; + const ServedMota* s = folder_.at(args[0]); + uint32_t off = rd_u32(args + 1); + uint16_t len = (uint16_t)(args[5] | (args[6] << 8)); + if (!s || (uint64_t)off + len > s->bytes.size()) { status = MS_STATUS_ERR; return true; } + payload.assign(s->bytes.begin() + off, s->bytes.begin() + off + len); + return true; + } + return false; // unknown op -> ignore (device retries) +} + +// ---- SerialTransport ------------------------------------------------------------------------------ +static speed_t baud_const(int b) { + switch (b) { + case 9600: return B9600; case 19200: return B19200; case 38400: return B38400; + case 57600: return B57600; case 115200: return B115200; case 230400: return B230400; + case 460800: return B460800; case 921600: return B921600; default: return B115200; + } +} + +std::string SerialTransport::open(const std::string& dev, int baud) { + fd_ = ::open(dev.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK); + if (fd_ < 0) return "cannot open serial device: " + dev; + termios t{}; + if (tcgetattr(fd_, &t) != 0) { ::close(fd_); fd_ = -1; return "tcgetattr failed"; } + cfmakeraw(&t); + speed_t s = baud_const(baud); + cfsetispeed(&t, s); cfsetospeed(&t, s); + t.c_cflag |= (CLOCAL | CREAD); + t.c_cflag &= ~CRTSCTS; // no flow control (matches the daemon) + t.c_cc[VMIN] = 0; t.c_cc[VTIME] = 0; + if (tcsetattr(fd_, TCSANOW, &t) != 0) { ::close(fd_); fd_ = -1; return "tcsetattr failed"; } + return ""; +} + +SerialTransport::~SerialTransport() { if (fd_ >= 0) ::close(fd_); } + +int SerialTransport::read_byte(int timeout_ms) { + if (fd_ < 0) return -1; + fd_set rs; FD_ZERO(&rs); FD_SET(fd_, &rs); + timeval tv{ timeout_ms / 1000, (timeout_ms % 1000) * 1000 }; + int r = select(fd_ + 1, &rs, nullptr, nullptr, &tv); + if (r <= 0) return -1; + uint8_t b; + ssize_t n = ::read(fd_, &b, 1); + return n == 1 ? (int)b : -1; +} + +bool SerialTransport::write(const uint8_t* p, size_t n) { + if (fd_ < 0) return false; + size_t off = 0; + while (off < n) { + ssize_t w = ::write(fd_, p + off, n - off); + if (w < 0) return false; + off += (size_t)w; + } + return true; +} + +// ---- serial framing loop -------------------------------------------------------------------------- +static uint8_t xor_bytes(const uint8_t* p, size_t n, uint8_t seed = 0) { + uint8_t x = seed; for (size_t i = 0; i < n; i++) x ^= p[i]; return x; +} + +static bool read_exact(Transport& t, uint8_t* buf, size_t n) { + for (size_t i = 0; i < n; i++) { + int b = t.read_byte(500); + if (b < 0) return false; + buf[i] = (uint8_t)b; + } + return true; +} + +static void send_response(Transport& t, uint8_t op, uint8_t status, const std::vector& payload) { + std::vector frame; + frame.reserve(4 + payload.size() + 1); + frame.push_back(MS_RSP_MAGIC0); frame.push_back(MS_RSP_MAGIC1); + frame.push_back(op); frame.push_back(status); + frame.insert(frame.end(), payload.begin(), payload.end()); + frame.push_back(xor_bytes(frame.data(), frame.size())); // xsum over all prior bytes (incl. magic) + t.write(frame.data(), frame.size()); +} + +void serve_serial(Transport& t, const SeederCore& core, bool verbose, + const std::function& devline, + const volatile bool* stop) { + std::string line; + int prev = -1; + auto flush_line = [&]() { + while (!line.empty() && (line.back() == '\r' || line.back() == '\n')) line.pop_back(); + if (!line.empty() && devline) devline(line); + line.clear(); + }; + while (!stop || !*stop) { + int b = t.read_byte(200); + if (b < 0) continue; + if (prev == MS_REQ_MAGIC0 && b == MS_REQ_MAGIC1) { // 'M''S' -> a request follows + prev = -1; + uint8_t op; + if (!read_exact(t, &op, 1)) continue; + size_t arglen = (op == MS_OP_DESCRIBE) ? 1 : (op == MS_OP_READ ? 7 : (op == MS_OP_COUNT ? 0 : SIZE_MAX)); + if (arglen == SIZE_MAX) continue; // unknown op + uint8_t args[7]; + if (arglen && !read_exact(t, args, arglen)) continue; + uint8_t xs; + if (!read_exact(t, &xs, 1)) continue; + if (xs != xor_bytes(args, arglen, op)) continue; // bad checksum -> ignore; device retries + uint8_t status; std::vector payload; + if (!core.handle(op, args, arglen, status, payload)) continue; + send_response(t, op, status, payload); + if (verbose) { + if (op == MS_OP_COUNT) devline("COUNT -> " + std::to_string(payload.empty() ? 0 : payload[0])); + else if (op == MS_OP_DESCRIBE) devline("DESCRIBE " + std::to_string(args[0]) + (status ? " ERR" : " OK")); + else if (op == MS_OP_READ) devline("READ " + std::to_string(args[0]) + " @" + + std::to_string(rd_u32(args + 1)) + (status ? " ERR" : " OK")); + } + continue; + } + if (prev >= 0) { // confirmed device text (not a frame start) + line.push_back((char)prev); + if (prev == '\n') flush_line(); + if (line.size() > 512) flush_line(); // guard runaway lines + } + prev = b; + } +} + +} // namespace mota diff --git a/tools/motatool/src/serve.h b/tools/motatool/src/serve.h new file mode 100644 index 00000000..7bff708e --- /dev/null +++ b/tools/motatool/src/serve.h @@ -0,0 +1,68 @@ +// Serve a folder of .mota to a MeshCore node. Split into a transport-AGNOSTIC protocol core (reusable +// over USB-serial today and BLE/GATT in the future) and a serial byte-stream framing layer. +#pragma once +#include "mota.h" +#include "mota_format.h" +#include +#include +#include +#include + +namespace mota { + +struct ServedMota { + std::string path; + std::vector bytes; + Manifest m; +}; + +// Recursively collect every *.mota under `dir`, validate each, keep only the valid ones. Corrupt/invalid +// files are reported through `warn(path, reason)` and excluded — one bad file never sinks the rest. +class Folder { +public: + size_t scan(const std::string& dir, bool recursive, + const std::function& warn); + size_t count() const { return motas_.size(); } + const ServedMota* at(size_t i) const { return i < motas_.size() ? &motas_[i] : nullptr; } + const std::vector& all() const { return motas_; } +private: + std::vector motas_; +}; + +// Transport-agnostic seeder: turns a (op, args) request into a (status, payload) response. The BLE path +// would call this directly from a characteristic-write handler and notify the reply — no framing needed. +class SeederCore { +public: + explicit SeederCore(const Folder& f) : folder_(f) {} + bool handle(uint8_t op, const uint8_t* args, size_t arglen, + uint8_t& status, std::vector& payload) const; + static std::array describe(const ServedMota& s); +private: + const Folder& folder_; +}; + +// A bidirectional byte link (the serial seeder needs this; BLE would reuse SeederCore directly). +struct Transport { + virtual ~Transport() {} + virtual int read_byte(int timeout_ms) = 0; // a byte 0..255, or -1 on timeout/closed + virtual bool write(const uint8_t* p, size_t n) = 0; + bool write_str(const std::string& s) { return write((const uint8_t*)s.data(), s.size()); } +}; + +class SerialTransport : public Transport { +public: + std::string open(const std::string& dev, int baud); // "" on success + ~SerialTransport() override; + int read_byte(int timeout_ms) override; + bool write(const uint8_t* p, size_t n) override; +private: + int fd_ = -1; +}; + +// Serial framing loop: resync on 'M''S', verify the request checksum, dispatch to `core`, frame the +// reply. Device text/log lines sharing the wire are surfaced via `devline`. Runs until *stop becomes true. +void serve_serial(Transport& t, const SeederCore& core, bool verbose, + const std::function& devline, + const volatile bool* stop); + +} // namespace mota diff --git a/tools/motatool/src/util.h b/tools/motatool/src/util.h new file mode 100644 index 00000000..9e5e51af --- /dev/null +++ b/tools/motatool/src/util.h @@ -0,0 +1,77 @@ +// Small host-side helpers: file I/O, subprocess exec (no shell), hex. Header-only. +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mota { + +inline bool read_file(const std::string& path, std::vector& out) { + std::ifstream f(path, std::ios::binary); + if (!f) return false; + out.assign(std::istreambuf_iterator(f), std::istreambuf_iterator()); + return (bool)f || f.eof(); +} + +inline bool write_file(const std::string& path, const uint8_t* data, size_t len) { + std::ofstream f(path, std::ios::binary | std::ios::trunc); + if (!f) return false; + f.write((const char*)data, (std::streamsize)len); + return (bool)f; +} + +// Run argv[0] with argv (no shell, so no quoting pitfalls). Returns the child exit code, or -1 to spawn. +// `quiet` redirects the child's stdout to /dev/null (its chatter), keeping stderr so errors still show. +inline int run_argv(const std::vector& argv, bool quiet = false) { + if (argv.empty()) return -1; + pid_t pid = fork(); + if (pid < 0) return -1; + if (pid == 0) { + if (quiet) { + int n = ::open("/dev/null", O_WRONLY); + if (n >= 0) { dup2(n, 1); if (n > 2) ::close(n); } + } + std::vector a; + for (auto& s : argv) a.push_back(const_cast(s.c_str())); + a.push_back(nullptr); + execvp(a[0], a.data()); + _exit(127); // exec failed + } + int st = 0; + if (waitpid(pid, &st, 0) < 0) return -1; + return WIFEXITED(st) ? WEXITSTATUS(st) : -1; +} + +inline std::string to_hex(const uint8_t* p, size_t n) { + static const char* H = "0123456789ABCDEF"; + std::string s; s.reserve(n * 2); + for (size_t i = 0; i < n; i++) { s.push_back(H[p[i] >> 4]); s.push_back(H[p[i] & 0xF]); } + return s; +} + +// Parse a hex string (optionally 0x-prefixed) into bytes. Returns false on odd length / bad char. +inline bool from_hex(const std::string& in, std::vector& out) { + std::string s = in; + if (s.size() >= 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) s = s.substr(2); + if (s.size() % 2) return false; + out.clear(); + auto nib = [](char c) -> int { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + return -1; + }; + for (size_t i = 0; i < s.size(); i += 2) { + int hi = nib(s[i]), lo = nib(s[i + 1]); + if (hi < 0 || lo < 0) return false; + out.push_back((uint8_t)((hi << 4) | lo)); + } + return true; +} + +} // namespace mota diff --git a/tools/motatool/tests/test_motatool.cpp b/tools/motatool/tests/test_motatool.cpp new file mode 100644 index 00000000..a5741264 --- /dev/null +++ b/tools/motatool/tests/test_motatool.cpp @@ -0,0 +1,274 @@ +// Unit tests for motatool's core logic (no external framework — a tiny built-in harness keeps the +// project self-contained). Run: ./build/motatool_tests or ctest --test-dir build --output-on-failure +#include "mota.h" +#include "crypto.h" +#include "serve.h" +#include "util.h" +#include "mota_format.h" +#include +#include +#include +#include + +namespace fs = std::filesystem; +using namespace mota; + +static int g_checks = 0, g_fail = 0; +static const char* g_test = ""; +#define CHECK(cond) do { g_checks++; if (!(cond)) { g_fail++; \ + std::cerr << " FAIL [" << g_test << "] " << __LINE__ << ": " #cond "\n"; } } while (0) + +// deterministic synthetic firmware body +static std::vector body(unsigned seed, size_t n) { + std::vector v(n); + uint32_t s = seed * 2654435761u + 1; + for (size_t i = 0; i < n; i++) { s = s * 1103515245u + 12345u; v[i] = (uint8_t)(s >> 16); } + return v; +} + +static bool detools_available() { + return run_argv({"detools", "--help"}, /*quiet=*/true) != 127; // 127 = exec failed (not installed) +} + +// --------------------------------------------------------------------------- +static void t_crypto() { + g_test = "crypto"; + // sha256("abc") = ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad + const char* abc = "abc"; + auto h4 = mh4((const uint8_t*)abc, 3); + auto h8 = mh8((const uint8_t*)abc, 3); + auto h32 = mh32((const uint8_t*)abc, 3); + std::vector exp; + from_hex("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", exp); + CHECK(std::memcmp(h4.data(), exp.data(), 4) == 0); + CHECK(std::memcmp(h8.data(), exp.data(), 8) == 0); + CHECK(std::memcmp(h32.data(), exp.data(), 32) == 0); + + uint8_t priv[32], pub[32], pub2[32]; + CHECK(ed25519_keygen(priv, pub)); + CHECK(ed25519_pub_from_priv(pub2, priv)); + CHECK(std::memcmp(pub, pub2, 32) == 0); + auto msg = body(1, 100); + uint8_t sig[64]; + CHECK(ed25519_sign(sig, msg.data(), msg.size(), priv)); + CHECK(ed25519_verify(sig, msg.data(), msg.size(), pub)); + msg[0] ^= 0xFF; // tampered message -> reject + CHECK(!ed25519_verify(sig, msg.data(), msg.size(), pub)); + msg[0] ^= 0xFF; + uint8_t other[32], opub[32]; ed25519_keygen(other, opub); + CHECK(!ed25519_verify(sig, msg.data(), msg.size(), opub)); // wrong key -> reject +} + +static void t_version_target() { + g_test = "version/target"; + uint32_t v; + CHECK(pack_version("1.16.0", v) && v == 0x01100000u); + CHECK(pack_version("2.0.0", v) && v == 0x02000000u); + CHECK(pack_version("1.16.0.2", v) && (v & 0xFF) == 2); + CHECK(!pack_version("", v)); + CHECK(!pack_version("x.y", v)); + // known target ids from the firmware builds (ties motatool's hashing to the device) + CHECK(target_id_for_env("Heltec_v3_repeater") == 0xd1b29b18u); + CHECK(target_id_for_env("RAK_4631_repeater") == 0x04d413fdu); +} + +static void t_endf() { + g_test = "endf"; + auto b = body(2, 1500); + std::array bh{}; + FwIdent id{0x01100000u, 0x04d413fdu, "RAK4631"}; + auto img = ensure_endf(b, id, bh); + CHECK(img.size() == b.size() + ENDF_LEN); + CHECK(has_endf(img)); + CHECK(std::memcmp(bh.data(), mh8(b.data(), b.size()).data(), 8) == 0); // body_hash is over BODY only + auto gi = parse_endf_ident(img); + CHECK(gi.fw_version == 0x01100000u && gi.target_id == 0x04d413fdu && gi.hw_id == "RAK4631"); + // idempotent: feeding an already-EndF'd image keeps it + reads the same hash + std::array bh2{}; + auto img2 = ensure_endf(img, FwIdent{}, bh2); + CHECK(img2 == img && bh2 == bh); + // zero identity -> still a 56-byte trailer, identity reads empty + std::array bz{}; + auto z = ensure_endf(b, FwIdent{}, bz); + CHECK(z.size() == b.size() + ENDF_LEN); + auto zi = parse_endf_ident(z); + CHECK(zi.fw_version == 0 && zi.target_id == 0 && zi.hw_id.empty()); + CHECK(!has_endf(std::vector{1,2,3})); // too short +} + +static BuildOpts full_opts(const std::vector& fw, const std::vector& priv = {}) { + BuildOpts o; o.fw = fw; o.codec = CODEC_FULL; + o.have_target = true; o.target_id = 0x04d413fdu; + o.have_fwver = true; o.fw_version = 0x01100000u; + o.hw_id = "RAK4631"; o.sign_priv = priv; + return o; +} + +static void t_build_full() { + g_test = "build full"; + auto fw = body(3, 5 * 1024 + 100); // 6 blocks @1024 + std::vector blob; std::string name; + CHECK(build(full_opts(fw), blob, name).empty()); + Manifest m; + CHECK(parse(blob, m).empty()); + CHECK(m.format_ver == FORMAT_VER && m.is_full() && !m.is_signed()); + CHECK(m.codec_id == CODEC_FULL); + CHECK(m.target_id == 0x04d413fdu && m.fw_version == 0x01100000u && m.hw_id_str() == "RAK4631"); + CHECK(m.block_count == 6); + // fixed layout offsets + CHECK(m.leaves_off() == 8 + MOTA_MFL && m.leaves_off() == 205); + CHECK(m.payload_off() == 205 + m.block_count * 4); + CHECK(m.total_size() == blob.size()); + // payload IS the image (body+EndF); image_hash matches + uint32_t poff = m.payload_off(); + auto img_h = mh32(blob.data() + poff, m.payload_size); + CHECK(std::memcmp(img_h.data(), m.image_hash.data(), 32) == 0); + CHECK(m.image_size == m.payload_size); + CHECK(verify(blob).empty()); // clean + CHECK(name.find(".mota") != std::string::npos && name.find("full") != std::string::npos); +} + +static void t_build_signed_and_tamper() { + g_test = "build signed/tamper"; + uint8_t priv[32], pub[32]; ed25519_keygen(priv, pub); + std::vector pv(priv, priv + 32); + auto fw = body(4, 3000); + std::vector blob; std::string name; + CHECK(build(full_opts(fw, pv), blob, name).empty()); + Manifest m; CHECK(parse(blob, m).empty()); + CHECK(m.is_signed()); + CHECK(std::memcmp(m.signer.data(), pub, 32) == 0); + CHECK(verify(blob).empty()); + // tamper a payload byte -> integrity fails + auto t1 = blob; t1[m.payload_off() + 10] ^= 0xFF; + CHECK(!verify(t1).empty()); + // tamper a signed-region byte (target_id @ manifest+3 = blob+11) -> signature invalid + auto t2 = blob; t2[8 + M_OFF_TARGET_ID] ^= 0xFF; + bool sig_flagged = false; + for (auto& p : verify(t2)) if (p.find("signature") != std::string::npos) sig_flagged = true; + CHECK(sig_flagged); +} + +static void t_corruption_and_approval() { + g_test = "corruption/approval"; + auto fw = body(5, 4096); + std::vector blob; std::string name; + build(full_opts(fw), blob, name); + Manifest m; parse(blob, m); + // flip a payload byte -> leaves/merkle/image_hash problems reported + auto c = blob; c[m.payload_off() + 1] ^= 0xFF; + CHECK(!verify(c).empty()); + // a pre-approved container must be flagged (approval is outside the signed region) + auto ap = blob; std::memcpy(ap.data() + 8 + M_OFF_APPROVAL, APPROVAL_YES, 4); + bool approved_flagged = false; + for (auto& p : verify(ap)) if (p.find("approved") != std::string::npos) approved_flagged = true; + CHECK(approved_flagged); +} + +static void t_parse_rejects() { + g_test = "parse rejects"; + auto fw = body(6, 2048); + std::vector blob; std::string name; build(full_opts(fw), blob, name); + Manifest m; + CHECK(!parse(std::vector(10, 0), m).empty()); // too small + auto bad = blob; bad[0] ^= 0xFF; CHECK(!parse(bad, m).empty()); // bad magic + bad = blob; bad[bad.size() - 1] ^= 0xFF; CHECK(!parse(bad, m).empty()); // bad trailer + bad = blob; bad[4] ^= 0xFF; CHECK(!parse(bad, m).empty()); // wrong total_size + bad = blob; bad[8 + M_OFF_FORMAT_VER] = 9; CHECK(!parse(bad, m).empty()); // bad format_ver +} + +static void t_delta() { + g_test = "delta"; + if (!detools_available()) { std::cerr << " SKIP [delta] detools not on PATH\n"; return; } + auto base_body = body(10, 4000); + auto new_body = base_body; + for (int i : {100, 101, 2000, 3999}) new_body[i] ^= 0x33; + auto tail = body(11, 250); + new_body.insert(new_body.end(), tail.begin(), tail.end()); + + for (uint8_t codec : {CODEC_DETOOLS_SEQUENTIAL, CODEC_DETOOLS_INPLACE}) { + BuildOpts o; o.fw = new_body; o.base = base_body; o.codec = codec; + o.have_target = true; o.target_id = 0x04d413fdu; o.have_fwver = true; o.fw_version = 0x02000000u; + o.hw_id = "RAK4631"; + std::vector blob; std::string name; + std::string e = build(o, blob, name); + CHECK(e.empty()); + if (!e.empty()) continue; + Manifest m; CHECK(parse(blob, m).empty()); + CHECK(!m.is_full() && m.codec_id == codec); + // base_hash == mh8 of the base BODY + CHECK(std::memcmp(m.base_hash.data(), mh8(base_body.data(), base_body.size()).data(), 8) == 0); + CHECK(verify(blob).empty()); + if (codec == CODEC_DETOOLS_SEQUENTIAL) { // round-trip: apply -> rebuilds the new image + std::array bh{}; + auto base_img = ensure_endf(base_body, FwIdent{}, bh); + std::vector patch(blob.begin() + m.payload_off(), blob.begin() + m.payload_off() + m.payload_size); + std::vector recon; + CHECK(detools_apply_seq("detools", base_img, patch, recon).empty()); + CHECK(std::memcmp(mh32(recon.data(), recon.size()).data(), m.image_hash.data(), 32) == 0); + } + } +} + +static void t_folder_and_seeder() { + g_test = "folder/seeder"; + fs::path dir = fs::temp_directory_path() / "motatool_test_folder"; + fs::remove_all(dir); fs::create_directories(dir / "sub"); + + // two valid motas (one in a sub-folder, to exercise recursion) + one corrupt + std::vector a, b; std::string n; + build(full_opts(body(20, 3000)), a, n); write_file((dir / "a.mota").string(), a.data(), a.size()); + build(full_opts(body(21, 6000)), b, n); write_file((dir / "sub" / "b.mota").string(), b.data(), b.size()); + auto bad = a; bad[a.size() / 2] ^= 0xFF; write_file((dir / "bad.mota").string(), bad.data(), bad.size()); + write_file((dir / "ignore.txt").string(), a.data(), 10); // non-.mota -> skipped silently + + int warns = 0; std::string warned_path; + Folder folder; + size_t kept = folder.scan(dir.string(), true, + [&](const std::string& p, const std::string&){ warns++; warned_path = p; }); + CHECK(kept == 2); // two valid kept + CHECK(warns == 1 && warned_path.find("bad.mota") != std::string::npos); // corrupt warned + excluded + + SeederCore core(folder); + uint8_t status; std::vector pl; + CHECK(core.handle(MS_OP_COUNT, nullptr, 0, status, pl) && status == MS_STATUS_OK && pl.size() == 1 && pl[0] == 2); + + uint8_t arg0[1] = {0}; + CHECK(core.handle(MS_OP_DESCRIBE, arg0, 1, status, pl) && status == MS_STATUS_OK && pl.size() == MOTA_DESC_WIRE); + const ServedMota* s0 = folder.at(0); + CHECK(std::memcmp(pl.data(), s0->m.merkle_root.data(), 4) == 0); // mid + CHECK(rd_u32(pl.data() + 4) == s0->m.target_id); + CHECK(rd_u32(pl.data() + 14) == (uint32_t)s0->bytes.size()); // total_size + CHECK(rd_u32(pl.data() + 18) == s0->m.leaves_off() && rd_u32(pl.data() + 18) == 205); + CHECK(rd_u32(pl.data() + 22) == s0->m.block_count); + CHECK(rd_u32(pl.data() + 26) == s0->m.payload_off()); + CHECK(rd_u32(pl.data() + 30) == s0->m.payload_size); + + uint8_t bad_idx[1] = {99}; + CHECK(core.handle(MS_OP_DESCRIBE, bad_idx, 1, status, pl) && status == MS_STATUS_ERR); + + // READ idx=0 off=0 len=8 -> first 8 bytes (MAGIC + total_size) + uint8_t rd[7] = {0, 0,0,0,0, 8,0}; + CHECK(core.handle(MS_OP_READ, rd, 7, status, pl) && status == MS_STATUS_OK && pl.size() == 8); + CHECK(std::memcmp(pl.data(), s0->bytes.data(), 8) == 0); + // READ past EOF -> error + uint8_t reof[7]; reof[0] = 0; wr_u32(reof + 1, (uint32_t)s0->bytes.size()); reof[5] = 16; reof[6] = 0; + CHECK(core.handle(MS_OP_READ, reof, 7, status, pl) && status == MS_STATUS_ERR); + + fs::remove_all(dir); +} + +int main() { + t_crypto(); + t_version_target(); + t_endf(); + t_build_full(); + t_build_signed_and_tamper(); + t_corruption_and_approval(); + t_parse_rejects(); + t_delta(); + t_folder_and_seeder(); + + std::cout << (g_fail ? "FAILED " : "OK ") << (g_checks - g_fail) << "/" << g_checks << " checks passed\n"; + return g_fail ? 1 : 0; +}