Files
MeshCore/src/helpers/ota/MotaContainer.cpp
T

100 lines
4.0 KiB
C++

#include "MotaContainer.h"
#include "MerkleTree.h"
#include "Multihash.h"
#include "OtaByteIO.h"
#include <string.h>
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