Files
reverse-engineering-anything/tests/acceptance/elfNotesAndroid.test.ts
T
Ryan S e5ddbe8909 feat(native): decode Android .note.android.ident ELF notes (#9502)
* feat(native): decode Android .note.android.ident ELF notes

Decode Android NDK NT_ANDROID_TYPE_IDENT (.note.android.ident, owner Android, type 1) notes in inspect_elf_notes across legacy 4-byte and modern >=132-byte descriptors with bounded TextBudget accounting.

* fix(native): isolate Android note scan budget test, scope contract import, and add feature spec

* test(native): use domain tool contract in elfNotesMcp boundary test
2026-11-09 10:21:38 -05:00

103 lines
3.3 KiB
TypeScript

import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect } from "vitest";
import { BINARY_DIAGNOSTICS_TOOL_CONTRACTS } from "../../src/contracts/native/binaryDiagnosticsToolContracts.js";
import {
androidIdentNote,
elfNote,
elfNoteFixture,
gnuBuildIdNote,
} from "../../src/native/elf/ElfNotes.fixture.js";
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
import { cliTest } from "../support/cli/cliFixture.js";
const inspectElfNotesContract = BINARY_DIAGNOSTICS_TOOL_CONTRACTS.find(
(
candidate,
): candidate is Extract<
(typeof BINARY_DIAGNOSTICS_TOOL_CONTRACTS)[number],
{ readonly name: "inspect_elf_notes" }
> => candidate.name === "inspect_elf_notes",
);
if (inspectElfNotesContract === undefined)
throw new Error("Missing inspect_elf_notes contract");
for (const target of [
{
label: "little-endian ELF64",
bits: 64 as const,
little: true,
machine: 183,
},
{ label: "big-endian ELF32", bits: 32 as const, little: false, machine: 8 },
])
cliTest(
`decodes ${target.label} Android .note.android.ident through CLI and stdio MCP`,
async ({ cli }) => {
const root = await createTestTempDirectory("rea-elf-android-notes-");
const path = join(root, "libtarget.so");
const notes = Buffer.concat([
androidIdentNote({
androidApi: 24,
ndkVersion: "r27c",
ndkBuildNumber: "12297006",
little: target.little,
}),
androidIdentNote({
androidApi: 21,
ndkVersion: null,
ndkBuildNumber: null,
little: target.little,
}),
elfNote("Android", 2, Buffer.from([0xde, 0xad]), target.little),
gnuBuildIdNote(undefined, target.little),
]);
await writeFile(
path,
elfNoteFixture({
bits: target.bits,
little: target.little,
machine: target.machine,
ownership: "section",
sectionName: ".note.android.ident",
notes,
}),
);
const cliOut = await cli.sharedCliRun!({
arguments: ["inspect-elf-notes", path, "--json"],
});
expect(cliOut.exitCode, cliOut.stderr).toBe(0);
const call = await cli.sharedMcpCall!();
const mcpOut = await call("inspect_elf_notes", { path });
expect(mcpOut.isError, JSON.stringify(mcpOut.content)).not.toBe(true);
const schema = inspectElfNotesContract.outputSchema;
const cliReport = schema.parse(cliOut.json).normalized_result;
const mcpReport = schema.parse(
mcpOut.structuredContent,
).normalized_result;
expect(cliReport).toEqual(mcpReport);
expect(cliReport.byte_order).toBe(target.little ? "little" : "big");
expect(cliReport.entries.map((entry) => entry.type_name)).toEqual([
"android_ident",
"android_ident",
"unknown",
"gnu_build_id",
]);
expect(cliReport.entries[0]?.android).toEqual({
android_api: 24,
ndk_version: "r27c",
ndk_build_number: "12297006",
});
expect(cliReport.entries[1]?.android).toEqual({
android_api: 21,
ndk_version: null,
ndk_build_number: null,
});
expect(cliReport.entries[2]?.android).toBeNull();
expect(cliReport.entries[3]?.android).toBeNull();
},
);