refactor(ida): consolidate pagination progress checks
This commit is contained in:
1 parent
8f894546c8
commit
21b6ea7fbd
3 files changed
+58
-23
No files matched your search
@@ -188,3 +188,47 @@ it.each(["legacy", "modern", "native"] as const)(
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
["legacy", "functions"],
|
||||
["modern", "functions"],
|
||||
["native", "functions"],
|
||||
["legacy", "strings"],
|
||||
["modern", "strings"],
|
||||
["native", "strings"],
|
||||
] as const)(
|
||||
"%s %s inventory rejects a repeated or backward offset before another request",
|
||||
async (protocol, inventory) => {
|
||||
const { root, target } = await createIdaTarget();
|
||||
const producer = new RecordingIdaMcp(target);
|
||||
try {
|
||||
for (const next of [2, 1]) {
|
||||
let pages = 0;
|
||||
producer.overrideCall = (tool) => {
|
||||
if (
|
||||
![
|
||||
"list_functions",
|
||||
"list_funcs",
|
||||
"list_strings",
|
||||
"find_regex",
|
||||
].includes(tool)
|
||||
)
|
||||
return undefined;
|
||||
const nextOffset = pages++ === 0 ? 2 : next;
|
||||
if (tool === "find_regex")
|
||||
return { matches: [], cursor: { next: nextOffset } };
|
||||
const page = { data: [], next_offset: nextOffset };
|
||||
return tool === "list_funcs" ? [page] : page;
|
||||
};
|
||||
const runner = new IdaOperationRunner(producer, protocol);
|
||||
await expect(runner[inventory]()).rejects.toMatchObject({
|
||||
_tag: "AnalysisProtocolError",
|
||||
});
|
||||
expect(pages).toBe(2);
|
||||
}
|
||||
} finally {
|
||||
await producer.close();
|
||||
await rm(root, { recursive: true, force: true });
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -54,6 +54,7 @@ import {
|
||||
modernPageSchema,
|
||||
modernXrefsSchema,
|
||||
nextOffset,
|
||||
nextPageOffset,
|
||||
surveySegmentsSchema,
|
||||
type IdaDisassembly,
|
||||
type IdaFunction,
|
||||
@@ -160,11 +161,7 @@ export class IdaOperationRunner {
|
||||
if (value === undefined)
|
||||
throw new AnalysisProtocolError("IDA MCP omitted its function page.");
|
||||
result.push(...value.data.map((fn) => idaFunctionSchema.parse(fn)));
|
||||
offset = value.next_offset;
|
||||
if (offset !== null && offset <= current)
|
||||
throw new AnalysisProtocolError(
|
||||
"IDA MCP function pagination did not advance.",
|
||||
);
|
||||
offset = nextPageOffset(value.next_offset, current);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -349,7 +346,7 @@ export class IdaOperationRunner {
|
||||
const s = legacyStringSchema.parse(item);
|
||||
result.push({ address: s.address, value: s.string });
|
||||
}
|
||||
offset = page.next_offset;
|
||||
offset = nextPageOffset(page.next_offset, current);
|
||||
} else {
|
||||
const page = idaStringsPageSchema.parse(
|
||||
await this.call("find_regex", {
|
||||
@@ -370,10 +367,6 @@ export class IdaOperationRunner {
|
||||
);
|
||||
offset = nextOffset(page.cursor, current);
|
||||
}
|
||||
if (offset !== null && offset <= current)
|
||||
throw new AnalysisProtocolError(
|
||||
"IDA MCP string pagination did not advance.",
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -61,30 +61,28 @@ export const cursorSchema = z.union([
|
||||
z.object({ next: z.int().nonnegative() }),
|
||||
z.object({ done: z.literal(true) }),
|
||||
]);
|
||||
export const nextOffset = (
|
||||
cursor: z.infer<typeof cursorSchema>,
|
||||
/** Both legacy pages and modern cursors must finish or advance strictly. */
|
||||
export const nextPageOffset = (
|
||||
next: number | null,
|
||||
current: number,
|
||||
): number | null => {
|
||||
if ("done" in cursor) return null;
|
||||
if (cursor.next <= current)
|
||||
if (next !== null && next <= current)
|
||||
throw new AnalysisProtocolError(
|
||||
"IDA MCP pagination did not advance its offset.",
|
||||
);
|
||||
return cursor.next;
|
||||
return next;
|
||||
};
|
||||
export const nextOffset = (
|
||||
cursor: z.infer<typeof cursorSchema>,
|
||||
current: number,
|
||||
): number | null =>
|
||||
nextPageOffset("done" in cursor ? null : cursor.next, current);
|
||||
|
||||
export const legacyPageSchema = z.object({
|
||||
data: z.array(jsonValueSchema),
|
||||
next_offset: z.int().nonnegative().nullable(),
|
||||
});
|
||||
export const modernPageSchema = z
|
||||
.array(
|
||||
z.object({
|
||||
data: z.array(jsonValueSchema),
|
||||
next_offset: z.int().nonnegative().nullable(),
|
||||
}),
|
||||
)
|
||||
.length(1);
|
||||
export const modernPageSchema = z.array(legacyPageSchema).length(1);
|
||||
|
||||
const modernLookupRowSchema = z.object({
|
||||
query: z.string(),
|
||||
|
||||
Reference in new issue
Block a user