mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
fix(artifacts): describe ZIP non-entry coverage accurately (#2059)
* fix(artifacts): describe ZIP non-entry coverage accurately * fix(process): report no remaining time after a provider deadline fires Node timers can fire while performance.now() is still a fraction of a millisecond short of the monotonic deadline. ProviderStartupDeadline then reported interruption "timeout" and an aborted signal while remainingMs() returned ceil(0.x) = 1, so polling loops guarded by remainingMs() > 0 (Hopper and Ghidra startup) could run past an elapsed deadline, and the real-timer test "aborts a bounded wait when its real monotonic deadline expires" failed intermittently in CI (expected 1 to be 0). Once the deadline's own timer has classified a timeout, report zero remaining time. A deterministic regression test fires the timer with the monotonic clock 0.4 ms short of the deadline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * style: format comparison and IDA sources --------- Co-authored-by: morluto <76467478+morluto@users.noreply.github.com> Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
5 files changed
+103
-9
No files matched your search
@@ -13,7 +13,7 @@ import type {
|
||||
} from "../../domain/artifactGraph.js";
|
||||
import {
|
||||
isZipPackageFormat,
|
||||
ZIP_NON_ENTRY_TAIL_LIMITATION,
|
||||
ZIP_NON_ENTRY_LIMITATION,
|
||||
} from "../../domain/zipPackageFormat.js";
|
||||
import type { StableRegularFileDescriptor } from "../../filesystem/RegularFile.js";
|
||||
|
||||
@@ -56,7 +56,7 @@ export const inventoryCoverage = (
|
||||
coverage: { scope: "container-entries", status: traversal.status },
|
||||
limitations: [
|
||||
...(isZipPackageFormat(reader.format)
|
||||
? [ZIP_NON_ENTRY_TAIL_LIMITATION]
|
||||
? [ZIP_NON_ENTRY_LIMITATION]
|
||||
: []),
|
||||
...traversal.limitations,
|
||||
],
|
||||
|
||||
@@ -20,7 +20,7 @@ import { parseEvidence, type Evidence } from "./evidence.js";
|
||||
import { artifactInspectionResultSchema } from "./artifactInspection.js";
|
||||
import {
|
||||
isZipPackageFormat,
|
||||
ZIP_NON_ENTRY_TAIL_LIMITATION,
|
||||
isZipInventoryScopeLimitation,
|
||||
} from "./zipPackageFormat.js";
|
||||
|
||||
/** Complete or partially assembled inventory used by comparison workflows. */
|
||||
@@ -279,7 +279,7 @@ const legacyInventoryCoverage = (
|
||||
status: limitations.some(
|
||||
(limitation) =>
|
||||
limitation !== "Artifact has no child container entries." &&
|
||||
limitation !== ZIP_NON_ENTRY_TAIL_LIMITATION &&
|
||||
!isZipInventoryScopeLimitation(limitation) &&
|
||||
!(
|
||||
contradictionCount > 0 &&
|
||||
limitation ===
|
||||
|
||||
@@ -52,6 +52,12 @@ export const zipPackageFormatForPath = (
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/** Inventory cannot see bytes that are not central-directory entries. */
|
||||
export const ZIP_NON_ENTRY_TAIL_LIMITATION =
|
||||
"ZIP inventory lists central-directory entries only. Bytes after the central directory, including an APK Signing Block or a ZIP comment, are not occurrences.";
|
||||
/** Inventory lists entries, not all bytes surrounding the central directory. */
|
||||
export const ZIP_NON_ENTRY_LIMITATION =
|
||||
"ZIP inventory lists central-directory entries only. Non-entry bytes, including an APK Signing Block before the central directory or a ZIP comment after it, are not occurrences.";
|
||||
|
||||
/** Recognize the same inventory scope in current and previously retained Evidence. */
|
||||
export const isZipInventoryScopeLimitation = (limitation: string): boolean =>
|
||||
limitation === ZIP_NON_ENTRY_LIMITATION ||
|
||||
limitation ===
|
||||
"ZIP inventory lists central-directory entries only. Bytes after the central directory, including an APK Signing Block or a ZIP comment, are not occurrences.";
|
||||
@@ -2,6 +2,7 @@ import { chmod, mkdir, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { TextReader, Uint8ArrayWriter, ZipWriter } from "@zip.js/zip.js";
|
||||
|
||||
import { fragmentInventoryEvidence } from "../../fixtures/artifactEvidence.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
@@ -553,3 +554,90 @@ describe("artifact comparison completeness", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("ZIP non-entry coverage", () => {
|
||||
it("retains pre-directory APK block changes and compares historical scope descriptions", async () => {
|
||||
const root = await createTestTempDirectory("rea-apk-block-compare-");
|
||||
const writer = new ZipWriter(new Uint8ArrayWriter());
|
||||
await writer.add("classes.dex", new TextReader("unchanged entry"));
|
||||
const zip = Buffer.from(await writer.close());
|
||||
const eocd = zip.length - 22;
|
||||
expect(zip.readUInt32LE(eocd)).toBe(0x06054b50);
|
||||
const directory = zip.readUInt32LE(eocd + 16);
|
||||
// APK Signing Block framing with one opaque padding pair, not a verified signature.
|
||||
// https://source.android.com/docs/security/features/apksigning/v2#apk-signing-block
|
||||
const block = Buffer.alloc(48);
|
||||
block.writeBigUInt64LE(40n, 0);
|
||||
block.writeBigUInt64LE(8n, 8);
|
||||
block.writeUInt32LE(0x42726577, 16);
|
||||
block.writeBigUInt64LE(40n, 24);
|
||||
block.write("APK Sig Block 42", 32, "ascii");
|
||||
const paths = [join(root, "before.apk"), join(root, "after.apk")];
|
||||
for (const [index, path] of paths.entries()) {
|
||||
block.writeUInt32LE(index, 20);
|
||||
const apk = Buffer.concat([
|
||||
zip.subarray(0, directory),
|
||||
block,
|
||||
zip.subarray(directory),
|
||||
]);
|
||||
apk.writeUInt32LE(directory + block.length, eocd + block.length + 16);
|
||||
await writeFile(path, apk);
|
||||
}
|
||||
const left = await observe(paths[0]!);
|
||||
const right = await observe(paths[1]!);
|
||||
const inventory = artifactInventoryResultSchema.parse(
|
||||
left.normalized_result,
|
||||
);
|
||||
expect(inventory.limitations).toContain(
|
||||
"ZIP inventory lists central-directory entries only. Non-entry bytes, including an APK Signing Block before the central directory or a ZIP comment after it, are not occurrences.",
|
||||
);
|
||||
expect(
|
||||
inventory.occurrences.map(({ logical_path }) => logical_path).sort(),
|
||||
).toEqual([".", "classes.dex"]);
|
||||
expect(compareArtifacts(left, right)).toMatchObject({
|
||||
status: "changed",
|
||||
summary: { changed: 1, unchanged: 1, unknown: 0 },
|
||||
changes: [
|
||||
expect.objectContaining({ logical_path: ".", dimensions: ["content"] }),
|
||||
],
|
||||
});
|
||||
const {
|
||||
coverage: _coverage,
|
||||
coverage_status: _coverageStatus,
|
||||
...legacyInventory
|
||||
} = inventory;
|
||||
const withLimitation = (limitation: string) =>
|
||||
createEvidence(
|
||||
{
|
||||
path: paths[0]!,
|
||||
sha256: inventory.manifest.root_sha256,
|
||||
format: "apk",
|
||||
},
|
||||
PROVIDER,
|
||||
{
|
||||
operation: "inventory_artifact",
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse({
|
||||
...legacyInventory,
|
||||
limitations: [limitation],
|
||||
}),
|
||||
confidence: "observed",
|
||||
authority: "shipped-artifact",
|
||||
},
|
||||
);
|
||||
const historical = withLimitation(
|
||||
"ZIP inventory lists central-directory entries only. Bytes after the central directory, including an APK Signing Block or a ZIP comment, are not occurrences.",
|
||||
);
|
||||
expect(compareArtifacts(historical, left)).toMatchObject({
|
||||
status: "unchanged",
|
||||
summary: { unchanged: 2, unknown: 0 },
|
||||
changes: [],
|
||||
});
|
||||
expect(
|
||||
compareArtifacts(
|
||||
withLimitation("ZIP entry bytes could not be read."),
|
||||
left,
|
||||
).status,
|
||||
).toBe("unknown");
|
||||
});
|
||||
});
|
||||
@@ -20,7 +20,7 @@ import { artifactInventoryResultSchema } from "../../../../src/domain/artifactGr
|
||||
import { parseBinaryTarget } from "../../../../src/application/BinaryTargetResolver.js";
|
||||
import {
|
||||
ZIP_PACKAGE_FORMATS,
|
||||
ZIP_NON_ENTRY_TAIL_LIMITATION,
|
||||
ZIP_NON_ENTRY_LIMITATION,
|
||||
} from "../../../../src/domain/zipPackageFormat.js";
|
||||
|
||||
const { runProviderAnalysis } = createDirectAnalysis({});
|
||||
@@ -264,7 +264,7 @@ describe("HarmonyOS inventory coverage", () => {
|
||||
artifact_kind: "container",
|
||||
artifact_format: format,
|
||||
});
|
||||
expect(inventory.limitations).toContain(ZIP_NON_ENTRY_TAIL_LIMITATION);
|
||||
expect(inventory.limitations).toContain(ZIP_NON_ENTRY_LIMITATION);
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
Reference in new issue
Block a user