#include "MotaContainer.h" #include "MerkleTree.h" #include "Multihash.h" #include "OtaByteIO.h" #include namespace mesh { namespace ota { bool MotaManifest::is_approved() const { return approval && memcmp(approval, APPROVAL_YES, 4) == 0; } // Fixed-layout parse (docs/ota_protocol.md Section 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(); out.hash_algo = r.u8(); out.target_id = r.u32(); out.fw_version = r.u32(); out.image_size = r.u32(); out.payload_size = r.u32(); out.block_size_log2 = r.u8(); out.merkle_root = r.take(4); out.image_hash = r.take(32); out.codec_id = r.u8(); out.hw_id = r.take(32); // 32-byte NUL-padded hardware tag (signed) 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(); // block_idx is uint16 on the wire; capping here also keeps block_count*4 (leaves length) from overflowing. return out.block_count != 0 && out.block_count <= 0xFFFFu; } bool mota_parse(const uint8_t* buf, uint32_t len, MotaManifest& out) { out = MotaManifest(); if (len < 4 + 4 + 5) return false; if (memcmp(buf, MOTA_MAGIC, 4) != 0) return false; if (memcmp(buf + len - 5, MOTA_TRAILER, 5) != 0) return false; if (rd_u32le(buf + 4) != len) return false; // MOTA_TOTAL_SIZE must equal the actual length 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, out)) return false; out.leaves = r.take(out.block_count * 4); out.payload = r.take(out.payload_size); if (!r.ok) return false; return r.pos() == len - 5; // payload must end exactly at the trailer } 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, out); } bool mota_check_root(const MotaManifest& m) { if (!m.leaves || m.block_count == 0) return false; uint8_t root[4]; merkle_root(root, m.leaves, m.block_count); return memcmp(root, m.merkle_root, 4) == 0; } bool mota_check_payload(const MotaManifest& m) { if (!m.payload || !m.leaves || m.block_count == 0) return false; uint32_t off = 0; uint32_t bs = m.block_size(); for (uint32_t i = 0; i < m.block_count; i++) { if (off >= m.payload_size) return false; uint32_t len = m.payload_size - off; if (len > bs) len = bs; uint8_t leaf[4]; merkle_leaf(leaf, m.payload + off, len); if (memcmp(leaf, m.leaves + i * 4, 4) != 0) return false; off += len; } return off == m.payload_size; } bool mota_check_image_hash_full(const MotaManifest& m) { if (!m.is_full() || !m.payload || !m.image_hash) return false; uint8_t h[32]; mh32(h, m.payload, m.payload_size); return memcmp(h, m.image_hash, 32) == 0; } } // namespace ota } // namespace mesh