mirror of
https://github.com/morluto/rea.git
synced 2026-11-06 14:56:32 +00:00
* fix(ci): remove unused exports that fail deep static checks Knip on main reports three analysis-view hub re-exports, two type re-exports, an unused artifact-format guard and an alias constant. Drop the dead re-exports and guard, and give the debugger control-reply budget its own literal: it is a separate budget from the caller command-output default, as its comment already states. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(config): read the Rizin control budget from its own constant The Rizin control-reply admission limit still took its value from the caller command-output default, while GDB used DEBUGGER_CONTROL_REPLY_BYTES. Read the dedicated constant in both so the control budget has one owner. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
253 lines
8.8 KiB
TypeScript
253 lines
8.8 KiB
TypeScript
import {
|
|
DEBUGGER_CONTROL_REPLY_BYTES,
|
|
DEFAULT_DEBUGGER_COMMAND_DEADLINE_MS,
|
|
DEFAULT_DEBUGGER_MAX_COMMAND_OUTPUT_BYTES,
|
|
DEFAULT_DEBUGGER_MAX_PENDING_COMMANDS,
|
|
DEFAULT_DEBUGGER_MAX_SESSIONS,
|
|
MAXIMUM_DEBUGGER_COMMAND_DEADLINE_MS,
|
|
MAXIMUM_DEBUGGER_MAX_COMMAND_OUTPUT_BYTES,
|
|
MAXIMUM_DEBUGGER_MAX_PENDING_COMMANDS,
|
|
MAXIMUM_DEBUGGER_MAX_SESSIONS,
|
|
MINIMUM_DEBUGGER_COMMAND_DEADLINE_MS,
|
|
MINIMUM_DEBUGGER_MAX_COMMAND_OUTPUT_BYTES,
|
|
MINIMUM_DEBUGGER_MAX_PENDING_COMMANDS,
|
|
MINIMUM_DEBUGGER_MAX_SESSIONS,
|
|
} from "../domain/analysisResourceLimits.js";
|
|
import { ConfigurationError } from "../domain/configurationErrors.js";
|
|
import { err, ok, type Result } from "../domain/result.js";
|
|
import { positiveDecimalIntegerSchema } from "./positiveDecimalInteger.js";
|
|
|
|
/**
|
|
* Launch caps shared by GDB and Rizin. Each setting is declared here once.
|
|
* Startup validation spreads {@link debuggerSessionLimitEnvironmentShape};
|
|
* it does not restate the name, bounds, or schema.
|
|
*/
|
|
export const DEBUGGER_COMMAND_DEADLINE_SETTING =
|
|
"REA_DEBUGGER_COMMAND_DEADLINE_MS";
|
|
export const DEBUGGER_MAX_SESSIONS_SETTING = "REA_DEBUGGER_MAX_SESSIONS";
|
|
export const DEBUGGER_MAX_PENDING_COMMANDS_SETTING =
|
|
"REA_DEBUGGER_MAX_PENDING_COMMANDS";
|
|
export const DEBUGGER_MAX_COMMAND_OUTPUT_BYTES_SETTING =
|
|
"REA_DEBUGGER_MAX_COMMAND_OUTPUT_BYTES";
|
|
|
|
/** Effective debugger admission and where its value came from. */
|
|
export interface DebuggerAdmissionLimit {
|
|
readonly setting: string;
|
|
readonly value: number;
|
|
readonly source: "default" | "setting" | "runtime";
|
|
readonly defaultValue: number;
|
|
readonly minimum: number;
|
|
readonly maximum: number;
|
|
readonly unit: "ms" | "bytes" | "sessions" | "commands";
|
|
}
|
|
|
|
const limit = (
|
|
setting: string,
|
|
value: number,
|
|
minimum: number,
|
|
maximum: number,
|
|
unit: DebuggerAdmissionLimit["unit"],
|
|
): DebuggerAdmissionLimit => ({
|
|
setting,
|
|
value,
|
|
source: "default",
|
|
defaultValue: value,
|
|
minimum,
|
|
maximum,
|
|
unit,
|
|
});
|
|
|
|
export const DEFAULT_DEBUGGER_COMMAND_DEADLINE: DebuggerAdmissionLimit = limit(
|
|
DEBUGGER_COMMAND_DEADLINE_SETTING,
|
|
DEFAULT_DEBUGGER_COMMAND_DEADLINE_MS,
|
|
MINIMUM_DEBUGGER_COMMAND_DEADLINE_MS,
|
|
MAXIMUM_DEBUGGER_COMMAND_DEADLINE_MS,
|
|
"ms",
|
|
);
|
|
export const DEFAULT_DEBUGGER_MAX_SESSIONS_LIMIT: DebuggerAdmissionLimit =
|
|
limit(
|
|
DEBUGGER_MAX_SESSIONS_SETTING,
|
|
DEFAULT_DEBUGGER_MAX_SESSIONS,
|
|
MINIMUM_DEBUGGER_MAX_SESSIONS,
|
|
MAXIMUM_DEBUGGER_MAX_SESSIONS,
|
|
"sessions",
|
|
);
|
|
export const DEFAULT_DEBUGGER_MAX_PENDING_COMMANDS_LIMIT: DebuggerAdmissionLimit =
|
|
limit(
|
|
DEBUGGER_MAX_PENDING_COMMANDS_SETTING,
|
|
DEFAULT_DEBUGGER_MAX_PENDING_COMMANDS,
|
|
MINIMUM_DEBUGGER_MAX_PENDING_COMMANDS,
|
|
MAXIMUM_DEBUGGER_MAX_PENDING_COMMANDS,
|
|
"commands",
|
|
);
|
|
export const DEFAULT_DEBUGGER_MAX_COMMAND_OUTPUT_BYTES_LIMIT: DebuggerAdmissionLimit =
|
|
limit(
|
|
DEBUGGER_MAX_COMMAND_OUTPUT_BYTES_SETTING,
|
|
DEFAULT_DEBUGGER_MAX_COMMAND_OUTPUT_BYTES,
|
|
MINIMUM_DEBUGGER_MAX_COMMAND_OUTPUT_BYTES,
|
|
MAXIMUM_DEBUGGER_MAX_COMMAND_OUTPUT_BYTES,
|
|
"bytes",
|
|
);
|
|
|
|
const constraintFor = (admission: DebuggerAdmissionLimit): string =>
|
|
`${admission.setting} must be a positive decimal integer from ${String(admission.minimum)} to ${String(admission.maximum)} ${admission.unit}; the default is ${String(admission.defaultValue)} ${admission.unit}.${
|
|
admission.unit === "ms"
|
|
? " Node schedules a delay above that maximum as 1 ms, so a larger value is refused."
|
|
: ""
|
|
}`;
|
|
|
|
export const debuggerCommandDeadlineConstraint = constraintFor(
|
|
DEFAULT_DEBUGGER_COMMAND_DEADLINE,
|
|
);
|
|
export const debuggerMaxSessionsConstraint = constraintFor(
|
|
DEFAULT_DEBUGGER_MAX_SESSIONS_LIMIT,
|
|
);
|
|
export const debuggerMaxPendingCommandsConstraint = constraintFor(
|
|
DEFAULT_DEBUGGER_MAX_PENDING_COMMANDS_LIMIT,
|
|
);
|
|
export const debuggerMaxCommandOutputBytesConstraint = constraintFor(
|
|
DEFAULT_DEBUGGER_MAX_COMMAND_OUTPUT_BYTES_LIMIT,
|
|
);
|
|
|
|
export const debuggerCommandDeadlineMsSchema = positiveDecimalIntegerSchema(
|
|
MINIMUM_DEBUGGER_COMMAND_DEADLINE_MS,
|
|
MAXIMUM_DEBUGGER_COMMAND_DEADLINE_MS,
|
|
debuggerCommandDeadlineConstraint,
|
|
);
|
|
export const debuggerMaxSessionsSchema = positiveDecimalIntegerSchema(
|
|
MINIMUM_DEBUGGER_MAX_SESSIONS,
|
|
MAXIMUM_DEBUGGER_MAX_SESSIONS,
|
|
debuggerMaxSessionsConstraint,
|
|
);
|
|
export const debuggerMaxPendingCommandsSchema = positiveDecimalIntegerSchema(
|
|
MINIMUM_DEBUGGER_MAX_PENDING_COMMANDS,
|
|
MAXIMUM_DEBUGGER_MAX_PENDING_COMMANDS,
|
|
debuggerMaxPendingCommandsConstraint,
|
|
);
|
|
export const debuggerMaxCommandOutputBytesSchema = positiveDecimalIntegerSchema(
|
|
MINIMUM_DEBUGGER_MAX_COMMAND_OUTPUT_BYTES,
|
|
MAXIMUM_DEBUGGER_MAX_COMMAND_OUTPUT_BYTES,
|
|
debuggerMaxCommandOutputBytesConstraint,
|
|
);
|
|
|
|
/** Schemas startup validation spreads. The setting names are not restated. */
|
|
export const debuggerSessionLimitEnvironmentShape = {
|
|
[DEBUGGER_COMMAND_DEADLINE_SETTING]:
|
|
debuggerCommandDeadlineMsSchema.optional(),
|
|
[DEBUGGER_MAX_SESSIONS_SETTING]: debuggerMaxSessionsSchema.optional(),
|
|
[DEBUGGER_MAX_PENDING_COMMANDS_SETTING]:
|
|
debuggerMaxPendingCommandsSchema.optional(),
|
|
[DEBUGGER_MAX_COMMAND_OUTPUT_BYTES_SETTING]:
|
|
debuggerMaxCommandOutputBytesSchema.optional(),
|
|
};
|
|
|
|
export interface DebuggerSessionLimits {
|
|
readonly commandDeadline: DebuggerAdmissionLimit;
|
|
readonly maxSessions: DebuggerAdmissionLimit;
|
|
readonly maxPendingCommands: DebuggerAdmissionLimit;
|
|
readonly maxCommandOutputBytes: DebuggerAdmissionLimit;
|
|
}
|
|
|
|
const parseOne = (
|
|
admission: DebuggerAdmissionLimit,
|
|
schema: ReturnType<typeof positiveDecimalIntegerSchema>,
|
|
constraint: string,
|
|
value: string | undefined,
|
|
): Result<DebuggerAdmissionLimit, ConfigurationError> => {
|
|
if (value === undefined) return ok(admission);
|
|
const parsed = schema.safeParse(value);
|
|
return parsed.success
|
|
? ok({ ...admission, value: parsed.data, source: "setting" })
|
|
: err(
|
|
new ConfigurationError(constraint, {
|
|
settings: [{ setting: admission.setting, constraint }],
|
|
}),
|
|
);
|
|
};
|
|
|
|
/** Resolve every shared debugger cap from one environment. Invalid values are refused. */
|
|
export const parseDebuggerSessionLimits = (
|
|
environment: Readonly<Record<string, string | undefined>>,
|
|
): Result<DebuggerSessionLimits, ConfigurationError> => {
|
|
const commandDeadline = parseOne(
|
|
DEFAULT_DEBUGGER_COMMAND_DEADLINE,
|
|
debuggerCommandDeadlineMsSchema,
|
|
debuggerCommandDeadlineConstraint,
|
|
environment[DEBUGGER_COMMAND_DEADLINE_SETTING],
|
|
);
|
|
const maxSessions = parseOne(
|
|
DEFAULT_DEBUGGER_MAX_SESSIONS_LIMIT,
|
|
debuggerMaxSessionsSchema,
|
|
debuggerMaxSessionsConstraint,
|
|
environment[DEBUGGER_MAX_SESSIONS_SETTING],
|
|
);
|
|
const maxPendingCommands = parseOne(
|
|
DEFAULT_DEBUGGER_MAX_PENDING_COMMANDS_LIMIT,
|
|
debuggerMaxPendingCommandsSchema,
|
|
debuggerMaxPendingCommandsConstraint,
|
|
environment[DEBUGGER_MAX_PENDING_COMMANDS_SETTING],
|
|
);
|
|
const maxCommandOutputBytes = parseOne(
|
|
DEFAULT_DEBUGGER_MAX_COMMAND_OUTPUT_BYTES_LIMIT,
|
|
debuggerMaxCommandOutputBytesSchema,
|
|
debuggerMaxCommandOutputBytesConstraint,
|
|
environment[DEBUGGER_MAX_COMMAND_OUTPUT_BYTES_SETTING],
|
|
);
|
|
const settings = [
|
|
commandDeadline,
|
|
maxSessions,
|
|
maxPendingCommands,
|
|
maxCommandOutputBytes,
|
|
].flatMap((result) => (result.ok ? [] : result.error.settings));
|
|
if (
|
|
settings.length > 0 ||
|
|
!commandDeadline.ok ||
|
|
!maxSessions.ok ||
|
|
!maxPendingCommands.ok ||
|
|
!maxCommandOutputBytes.ok
|
|
)
|
|
return err(
|
|
new ConfigurationError("Invalid REA environment configuration", {
|
|
settings,
|
|
}),
|
|
);
|
|
return ok({
|
|
commandDeadline: commandDeadline.value,
|
|
maxSessions: maxSessions.value,
|
|
maxPendingCommands: maxPendingCommands.value,
|
|
maxCommandOutputBytes: maxCommandOutputBytes.value,
|
|
});
|
|
};
|
|
|
|
/** Refuse an invalid launch cap instead of falling back to the default. */
|
|
export const resolveDebuggerSessionLimits = (
|
|
environment: Readonly<Record<string, string | undefined>>,
|
|
): DebuggerSessionLimits => {
|
|
const parsed = parseDebuggerSessionLimits(environment);
|
|
if (!parsed.ok) throw parsed.error;
|
|
return parsed.value;
|
|
};
|
|
|
|
/**
|
|
* Fixed budget for Rizin cleanup readback and GDB startup or detach MI.
|
|
* This is not `REA_DEBUGGER_MAX_COMMAND_OUTPUT_BYTES`. A caller cap below
|
|
* this budget must not truncate those required replies, and recovery for
|
|
* this budget is not an environment setting.
|
|
*/
|
|
export const debuggerControlReplyOutput: DebuggerAdmissionLimit = {
|
|
setting: "debugger control-reply budget",
|
|
value: DEBUGGER_CONTROL_REPLY_BYTES,
|
|
source: "default",
|
|
defaultValue: DEBUGGER_CONTROL_REPLY_BYTES,
|
|
minimum: DEBUGGER_CONTROL_REPLY_BYTES,
|
|
maximum: DEBUGGER_CONTROL_REPLY_BYTES,
|
|
unit: "bytes",
|
|
};
|
|
|
|
/** Test and caller seams replace the launch value and record that source. */
|
|
export const applyDebuggerAdmissionOverride = (
|
|
admission: DebuggerAdmissionLimit,
|
|
value: number | undefined,
|
|
): DebuggerAdmissionLimit =>
|
|
value === undefined ? admission : { ...admission, value, source: "runtime" };
|