2026-11-06 23:48:59 +11:00
|
|
|
import { chmod, writeFile } from "node:fs/promises";
|
2026-10-21 02:03:17 +08:00
|
|
|
import { join } from "node:path";
|
|
|
|
|
|
|
|
|
|
import { expect } from "vitest";
|
|
|
|
|
|
|
|
|
|
import { analysisErrorProjectionSchema } from "../../../src/contracts/errorSchemas.js";
|
2026-11-06 23:48:59 +11:00
|
|
|
import { REVERSE_ENGINEERING_TOOL_CONTRACTS } from "../../../src/contracts/reverseEngineeringToolContracts.js";
|
2026-10-21 02:03:17 +08:00
|
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
|
|
|
import { cliTest } from "../../support/cli/cliFixture.js";
|
|
|
|
|
|
|
|
|
|
cliTest(
|
|
|
|
|
"reports missing and directory objdump inputs with structured reasons",
|
|
|
|
|
async ({ cli }) => {
|
|
|
|
|
const root = await createTestTempDirectory("rea-objdump-invalid-target-");
|
|
|
|
|
const missing = join(root, "missing.bin");
|
|
|
|
|
const directory = root;
|
|
|
|
|
|
|
|
|
|
for (const [path, expectedReason] of [
|
|
|
|
|
[missing, "not found"],
|
|
|
|
|
[directory, "not a regular file"],
|
|
|
|
|
] as const) {
|
|
|
|
|
const result = await cli.run({
|
|
|
|
|
arguments: [
|
|
|
|
|
"inspect-with-objdump",
|
|
|
|
|
path,
|
|
|
|
|
"symbols",
|
|
|
|
|
"--format",
|
|
|
|
|
"json",
|
|
|
|
|
],
|
|
|
|
|
cwd: root,
|
|
|
|
|
});
|
|
|
|
|
expect(result.exitCode).toBe(1);
|
|
|
|
|
expect(analysisErrorProjectionSchema.safeParse(result.json).success).toBe(
|
|
|
|
|
true,
|
|
|
|
|
);
|
|
|
|
|
expect(result.json).toMatchObject({
|
|
|
|
|
code: "invalid_request",
|
|
|
|
|
category: "invalid_input",
|
|
|
|
|
retryable: false,
|
|
|
|
|
message: expect.stringContaining(path),
|
|
|
|
|
details: {
|
|
|
|
|
operation: "inspect_with_objdump",
|
|
|
|
|
issues: [
|
|
|
|
|
{
|
|
|
|
|
path: ["path"],
|
|
|
|
|
reason: "invalid_value",
|
|
|
|
|
message: expect.stringContaining(expectedReason),
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
);
|
2026-10-23 04:29:35 +08:00
|
|
|
|
2026-11-06 23:48:59 +11:00
|
|
|
cliTest(
|
|
|
|
|
"passes Rizin decoder and map flags before the artifact",
|
|
|
|
|
async ({ cli }) => {
|
|
|
|
|
const root = await createTestTempDirectory("rea-rizin-load-flags-");
|
|
|
|
|
const provider = join(root, "argv-rizin");
|
|
|
|
|
const artifact = join(root, "flash.bin");
|
|
|
|
|
await writeFile(artifact, "flash");
|
|
|
|
|
await writeFile(
|
|
|
|
|
provider,
|
|
|
|
|
`#!${process.execPath}\nprocess.stdout.write(JSON.stringify(process.argv.slice(2)));\n`,
|
|
|
|
|
);
|
|
|
|
|
await chmod(provider, 0o755);
|
|
|
|
|
const result = await cli.run({
|
|
|
|
|
arguments: [
|
|
|
|
|
"execute-rizin-command",
|
|
|
|
|
artifact,
|
|
|
|
|
"oml",
|
|
|
|
|
"--asm-arch",
|
|
|
|
|
"riscv",
|
|
|
|
|
"--asm-bits",
|
|
|
|
|
"32",
|
|
|
|
|
"--asm-cpu",
|
|
|
|
|
"rv32imc",
|
|
|
|
|
"--base-address",
|
|
|
|
|
"0x42000000",
|
|
|
|
|
"--format",
|
|
|
|
|
"json",
|
|
|
|
|
],
|
|
|
|
|
cwd: root,
|
|
|
|
|
environment: { REA_RIZIN_COMMAND: provider },
|
|
|
|
|
});
|
|
|
|
|
expect(result.exitCode, result.stderr).toBe(0);
|
|
|
|
|
const rizinContract = REVERSE_ENGINEERING_TOOL_CONTRACTS.find(
|
|
|
|
|
(contract) => contract.name === "execute_rizin_command",
|
|
|
|
|
);
|
|
|
|
|
if (rizinContract === undefined)
|
|
|
|
|
throw new Error("execute_rizin_command contract is missing");
|
|
|
|
|
const evidence = rizinContract.outputSchema.parse(result.json);
|
|
|
|
|
const argv = [
|
|
|
|
|
"-N",
|
|
|
|
|
"-q",
|
|
|
|
|
"-e",
|
|
|
|
|
"scr.color=0",
|
|
|
|
|
"-a",
|
|
|
|
|
"riscv",
|
|
|
|
|
"-b",
|
|
|
|
|
"32",
|
|
|
|
|
"-e",
|
|
|
|
|
"asm.cpu=rv32imc",
|
|
|
|
|
"-n",
|
|
|
|
|
"-m",
|
|
|
|
|
"0x42000000",
|
|
|
|
|
"-c",
|
|
|
|
|
"oml",
|
|
|
|
|
artifact,
|
|
|
|
|
];
|
|
|
|
|
expect(JSON.parse(evidence.normalized_result.stdout)).toEqual(argv);
|
|
|
|
|
expect(evidence.parameters).toMatchObject({
|
|
|
|
|
asm_arch: "riscv",
|
|
|
|
|
asm_bits: 32,
|
|
|
|
|
asm_cpu: "rv32imc",
|
|
|
|
|
base_address: "0x42000000",
|
|
|
|
|
command_arguments: argv,
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
2026-10-23 04:29:35 +08:00
|
|
|
cliTest(
|
|
|
|
|
"reports the rejected Rizin command before inspecting the target",
|
|
|
|
|
async ({ cli }) => {
|
|
|
|
|
const root = await createTestTempDirectory("rea-rizin-invalid-command-");
|
|
|
|
|
const result = await cli.run({
|
|
|
|
|
arguments: [
|
|
|
|
|
"execute-rizin-command",
|
|
|
|
|
join(root, "unopened.bin"),
|
|
|
|
|
"iI\nq",
|
|
|
|
|
"--format",
|
|
|
|
|
"json",
|
|
|
|
|
],
|
|
|
|
|
cwd: root,
|
|
|
|
|
});
|
|
|
|
|
expect(result.exitCode).toBe(1);
|
|
|
|
|
expect(analysisErrorProjectionSchema.parse(result.json)).toMatchObject({
|
|
|
|
|
code: "invalid_request",
|
|
|
|
|
details: {
|
|
|
|
|
operation: "execute_rizin_command",
|
|
|
|
|
issues: [
|
|
|
|
|
{
|
|
|
|
|
path: ["command"],
|
|
|
|
|
reason: "invalid_value",
|
|
|
|
|
message: "A single command line is required",
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
);
|