mirror of
https://github.com/morluto/rea.git
synced 2026-11-09 16:26:36 +00:00
fix(firmware): exclude UF2 payloads overlapping MD5 metadata (#9593)
* fix(artifacts): exclude UF2 payloads overlapping MD5 metadata * fix(artifacts): clarify DfuSe overlap ordering
This commit is contained in:
1 parent
8358cfcab3
commit
9536ebb7ca
4 files changed
+84
-11
No files matched your search
+7
-7
@@ -127,13 +127,13 @@ its declaration, so the same bytes moved to another device memory are
|
||||
|
||||
Root `format_metadata` (`dfuse`) records:
|
||||
|
||||
| Field | Meaning |
|
||||
| ---------------------------------------------------- | ------------------------------------------------------------------------------- |
|
||||
| `image_size`, `target_count` | `DFUImageSize` and `bTargets` from the prefix |
|
||||
| `element_count`, `empty_element_count`, `data_bytes` | All elements, elements of 0 bytes (which have no entry), and element data bytes |
|
||||
| `overlapping_element_count` | Elements that overlap an earlier element of the same alternate setting |
|
||||
| `device_version`, `product_id`, `vendor_id` | `bcdDevice`, `idProduct` and `idVendor` from the suffix |
|
||||
| `suffix_crc`, `computed_crc` | The stored `dwCRC`, and the same CRC computed over every byte before it |
|
||||
| Field | Meaning |
|
||||
| ---------------------------------------------------- | ------------------------------------------------------------------------------------------- |
|
||||
| `image_size`, `target_count` | `DFUImageSize` and `bTargets` from the prefix |
|
||||
| `element_count`, `empty_element_count`, `data_bytes` | All elements, elements of 0 bytes (which have no entry), and element data bytes |
|
||||
| `overlapping_element_count` | Elements that overlap an earlier element in address order within the same alternate setting |
|
||||
| `device_version`, `product_id`, `vendor_id` | `bcdDevice`, `idProduct` and `idVendor` from the suffix |
|
||||
| `suffix_crc`, `computed_crc` | The stored `dwCRC`, and the same CRC computed over every byte before it |
|
||||
|
||||
Recognition already requires `bVersion` 1 and `bcdDFU` `0x011A`, so they are
|
||||
not repeated. The DFU suffix CRC is CRC-32 without the final inversion, as
|
||||
|
||||
@@ -212,7 +212,7 @@ const parseDfuSe = (
|
||||
...(overlaps.length === 0
|
||||
? []
|
||||
: [
|
||||
`${String(overlaps.length)} DfuSe element(s) overlap an earlier element of the same alternate setting; each stays a separate entry and no bytes were merged: ${named(overlaps)}.`,
|
||||
`${String(overlaps.length)} DfuSe element(s) overlap an element earlier in address order within the same alternate setting; each stays a separate entry and no bytes were merged: ${named(overlaps)}.`,
|
||||
]),
|
||||
...(walk.unlistedCount === 0
|
||||
? []
|
||||
|
||||
@@ -8,6 +8,7 @@ import { UF2_FAMILY_REGISTRY, uf2FamilyName } from "./uf2Families.js";
|
||||
export const UF2_BLOCK_BYTES = 512;
|
||||
const UF2_DATA_OFFSET = 32;
|
||||
const UF2_DATA_BYTES = 476;
|
||||
const UF2_MD5_BYTES = 24;
|
||||
const UF2_MAGIC_START0 = 0x0a32_4655;
|
||||
const UF2_MAGIC_START1 = 0x9e5d_5157;
|
||||
const UF2_MAGIC_END = 0x0ab1_6f30;
|
||||
@@ -236,9 +237,12 @@ class StreamingUf2Decoder implements RegionDecoder<Uf2Image> {
|
||||
? "end magic number does not match"
|
||||
: size > UF2_DATA_BYTES
|
||||
? `payload size ${String(size)} exceeds the ${String(UF2_DATA_BYTES)}-byte data area`
|
||||
: address + size > ADDRESS_SPACE
|
||||
? `payload at ${hex32(address)} with ${String(size)} bytes wraps past 0xffffffff`
|
||||
: undefined;
|
||||
: (flags & UF2_FLAG_MD5) !== 0 &&
|
||||
size > UF2_DATA_BYTES - UF2_MD5_BYTES
|
||||
? "payload overlaps the reserved MD5 checksum metadata"
|
||||
: address + size > ADDRESS_SPACE
|
||||
? `payload at ${hex32(address)} with ${String(size)} bytes wraps past 0xffffffff`
|
||||
: undefined;
|
||||
if (fault !== undefined) {
|
||||
this.#invalid += 1;
|
||||
this.#firstInvalid ??= { block: index, reason: fault };
|
||||
|
||||
@@ -14,6 +14,7 @@ import { artifactInspectionResultSchema } from "../../../src/domain/artifactInsp
|
||||
import { dfuseFile } from "../../../src/domain/firmware/dfuse.fixture.js";
|
||||
import {
|
||||
RP2040_FAMILY,
|
||||
UF2_FLAGS,
|
||||
RP2XXX_ABSOLUTE_FAMILY,
|
||||
patternBytes,
|
||||
uf2Block,
|
||||
@@ -113,6 +114,74 @@ it.each(["full", "compact"] as const)(
|
||||
},
|
||||
);
|
||||
|
||||
it("preserves valid regions while excluding a UF2 payload overlapping MD5 metadata", async () => {
|
||||
const root = await createTestTempDirectory("rea-uf2-md5-mcp-");
|
||||
const path = join(root, "firmware.uf2");
|
||||
const valid = patternBytes(452, 1);
|
||||
const plain = patternBytes(476, 2);
|
||||
await writeFile(
|
||||
path,
|
||||
uf2File([
|
||||
uf2Block({
|
||||
address: 0x1000,
|
||||
payload: valid,
|
||||
blockNo: 0,
|
||||
numBlocks: 3,
|
||||
flags: UF2_FLAGS.md5,
|
||||
}),
|
||||
uf2Block({
|
||||
address: 0x2000,
|
||||
payload: patternBytes(453, 3),
|
||||
blockNo: 1,
|
||||
numBlocks: 3,
|
||||
flags: UF2_FLAGS.md5,
|
||||
}),
|
||||
uf2Block({ address: 0x3000, payload: plain, blockNo: 2, numBlocks: 3 }),
|
||||
]),
|
||||
);
|
||||
const { call } = await connectLocalToolsMcp({});
|
||||
let outputRoot: string | undefined;
|
||||
try {
|
||||
const opened = await call("open_binary", { path });
|
||||
expect(opened.isError, JSON.stringify(opened)).not.toBe(true);
|
||||
const inspected = await call("inspect_artifact", {});
|
||||
expect(inspected.isError, JSON.stringify(inspected)).not.toBe(true);
|
||||
const inspection = artifactInspectionResultSchema.parse(
|
||||
parseEvidence(inspected.structuredContent).normalized_result,
|
||||
);
|
||||
expect(inspection.coverage.status).toBe("partial");
|
||||
expect(inspection.limitations).toContainEqual(
|
||||
expect.stringMatching(/invalid.*block 1.*MD5/iu),
|
||||
);
|
||||
const extracted = await call("extract_artifact", {});
|
||||
expect(extracted.isError, JSON.stringify(extracted)).not.toBe(true);
|
||||
const result = artifactExtractionResultSchema.parse(
|
||||
parseEvidence(extracted.structuredContent).normalized_result,
|
||||
);
|
||||
outputRoot = result.output_root;
|
||||
expect(result.coverage.status).toBe("unknown");
|
||||
expect(
|
||||
Buffer.compare(
|
||||
await readFile(join(outputRoot, "no-family/0x00001000-0x000011c3.bin")),
|
||||
valid,
|
||||
),
|
||||
).toBe(0);
|
||||
expect(
|
||||
Buffer.compare(
|
||||
await readFile(join(outputRoot, "no-family/0x00003000-0x000031db.bin")),
|
||||
plain,
|
||||
),
|
||||
).toBe(0);
|
||||
await expect(
|
||||
readFile(join(outputRoot, "no-family/0x00002000-0x000021c4.bin")),
|
||||
).rejects.toMatchObject({ code: "ENOENT" });
|
||||
await call("close_binary", {});
|
||||
} finally {
|
||||
if (outputRoot !== undefined)
|
||||
await rm(outputRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("delivers a DfuSe CRC contradiction under both integrity policies through MCP", async () => {
|
||||
const root = await createTestTempDirectory("rea-dfuse-mcp-");
|
||||
const path = join(root, "firmware.dfu");
|
||||
|
||||
Reference in new issue
Block a user