mirror of
https://github.com/morluto/rea.git
synced 2026-10-19 04:19:54 -05:00
* test(http3): add real MITM capture verification lab * test(http3): register the optional verification command * fix(http3): preserve the latest verification attempt owner * test(cli): admit the dyld slice fixture on x64 hosts * test(http3): verify exact producer TLS connection facts * test(http3): verify readable capture fields against observed bytes * fix(comparison): recheck historical calls-only changes (#2223) * fix(comparison): preserve incomplete function call evidence Keep missing, unknown, partial and unresolved call relationships unknown in function comparisons and reconstruction claims. Aggregate IDA caller and callee coverage without overwriting uncertainty. Validate with 144 focused tests, typecheck/lint, docs checks and test discovery. Real providers were not verified; the full headless IDA session suite requires unavailable Windows native controls. * fix(comparison): recheck historical calls-only changes --------- Co-authored-by: morluto <76467478+morluto@users.noreply.github.com> * fix(android): make DEX descriptor pattern portable * fix(ci): isolate Linux Hopper process fixture inventory * fix(http3): retain MCP startup diagnostics --------- Co-authored-by: morluto <76467478+morluto@users.noreply.github.com> Co-authored-by: Lucien <164857476+dakjdakd@users.noreply.github.com>
244 lines
8.5 KiB
JavaScript
244 lines
8.5 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { createHash } from "node:crypto";
|
|
import { isUtf8 } from "node:buffer";
|
|
const digest = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
// The native decoder displays strict UTF-8 and retains invalid bytes as null text.
|
|
const textOf = (expected) => {
|
|
const value = Buffer.from(expected.base64, "base64");
|
|
return isUtf8(value) ? value.toString("utf8") : null;
|
|
};
|
|
const field = (record, pointer) =>
|
|
record.binary_fields.find((item) => item.pointer === pointer);
|
|
const bytes = (record, pointer) => {
|
|
const value = field(record, pointer);
|
|
assert.equal(
|
|
value?.state,
|
|
"retained",
|
|
`Missing retained bytes at ${pointer}`,
|
|
);
|
|
const decoded = Buffer.from(value.content_base64, "base64");
|
|
assert.equal(value.bytes, decoded.length);
|
|
assert.equal(value.sha256, digest(decoded));
|
|
return decoded;
|
|
};
|
|
const binaryEqual = (record, pointer, expected) => {
|
|
const decoded = bytes(record, pointer);
|
|
assert.deepEqual(decoded, Buffer.from(expected.base64, "base64"), pointer);
|
|
assert.equal(field(record, pointer).sha256, expected.sha256);
|
|
assert.equal(field(record, pointer).bytes, expected.bytes);
|
|
};
|
|
const caseOf = (record) =>
|
|
record.reported.request.headers.find(
|
|
([name]) => name === "x-lab-case",
|
|
)?.[1] ?? "curl";
|
|
const byCase = (value, name) => {
|
|
const selected = value.records.filter((record) => caseOf(record) === name);
|
|
assert.equal(
|
|
selected.length,
|
|
1,
|
|
`Expected exactly one producer record for ${name}`,
|
|
);
|
|
return selected[0];
|
|
};
|
|
const connectionH3 = (record, side) => {
|
|
const conn = record.reported[side];
|
|
assert.equal(conn.transport_protocol, "udp");
|
|
assert.equal(bytes(record, `/${side}/alpn`).toString(), "h3");
|
|
assert.equal(conn.alpn, "h3", `${side}.alpn readable value`);
|
|
assert.equal(typeof conn.timestamp_tls_setup, "number");
|
|
};
|
|
|
|
function assertHeaders(record, side, headers) {
|
|
const regular = headers.filter(
|
|
([name]) => !Buffer.from(name.base64, "base64").toString().startsWith(":"),
|
|
);
|
|
assert.equal(record.reported[side].headers.length, regular.length);
|
|
for (const [index, pair] of regular.entries()) {
|
|
binaryEqual(record, `/${side}/headers/${index}/0`, pair[0]);
|
|
binaryEqual(record, `/${side}/headers/${index}/1`, pair[1]);
|
|
assert.deepEqual(
|
|
record.reported[side].headers[index],
|
|
pair.map(textOf),
|
|
`${side}.headers[${index}] readable values`,
|
|
);
|
|
}
|
|
}
|
|
|
|
export function assertEndpointHeaders(actual, expected) {
|
|
// The proxy rebuilds pseudo fields from its HTTP state. Compare their values
|
|
// by name while retaining the exact order and duplicates of regular headers.
|
|
const partition = (headers) => {
|
|
const pseudo = new Map();
|
|
const regular = [];
|
|
for (const pair of headers) {
|
|
const name = Buffer.from(pair[0].base64, "base64").toString();
|
|
if (name.startsWith(":")) {
|
|
assert.equal(pseudo.has(name), false);
|
|
pseudo.set(name, pair);
|
|
} else regular.push(pair);
|
|
}
|
|
return { pseudo, regular };
|
|
};
|
|
assert.deepEqual(partition(actual), partition(expected));
|
|
}
|
|
|
|
export function requireHttp3(record) {
|
|
for (const side of ["request", "response"])
|
|
assert.equal(record.reported[side].http_version, "HTTP/3");
|
|
connectionH3(record, "client_conn");
|
|
connectionH3(record, "server_conn");
|
|
}
|
|
|
|
export function verifyNormalRecords(normal, manifest, finalProducer) {
|
|
for (const record of normal.records) {
|
|
const name = caseOf(record);
|
|
const producer = finalProducer.find(
|
|
(event) => event.id === record.reported.id,
|
|
);
|
|
assert.ok(producer, `No actual producer observation for ${name}`);
|
|
assert.equal(record.reported.error, null);
|
|
assert.equal(producer.error, null);
|
|
assert.equal(record.reported.response.status_code, 200);
|
|
requireHttp3(record);
|
|
for (const side of ["request", "response"]) {
|
|
assert.equal(
|
|
record.reported[side].http_version,
|
|
producer[side].http_version,
|
|
);
|
|
binaryEqual(record, `/${side}/content`, producer[side].content);
|
|
assertHeaders(
|
|
record,
|
|
side,
|
|
manifest[side === "request" ? "requests" : "responses"][name].headers,
|
|
);
|
|
}
|
|
// Native UUIDs must be retained as UUIDs; the producer exports no stream mapping.
|
|
for (const side of ["client_conn", "server_conn"])
|
|
for (const key of ["id", "tls_version", "timestamp_tls_setup", "error"])
|
|
assert.equal(
|
|
record.reported[side][key],
|
|
producer[side][key],
|
|
`${side}.${key} differs from producer observation ${producer.id}`,
|
|
);
|
|
binaryEqual(record, "/request/content", manifest.requests[name].body);
|
|
binaryEqual(record, "/response/content", manifest.responses[name].body);
|
|
const pseudo = {
|
|
":method": "method",
|
|
":scheme": "scheme",
|
|
":authority": "authority",
|
|
":path": "path",
|
|
};
|
|
for (const [headerName, headerValue] of manifest.requests[name].headers) {
|
|
const decodedName = Buffer.from(headerName.base64, "base64").toString();
|
|
if (pseudo[decodedName]) {
|
|
binaryEqual(record, `/request/${pseudo[decodedName]}`, headerValue);
|
|
assert.equal(
|
|
record.reported.request[pseudo[decodedName]],
|
|
textOf(headerValue),
|
|
`request.${pseudo[decodedName]} readable value`,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export { bytes, caseOf, byCase };
|
|
|
|
export function verifyConcurrency(normal, manifest) {
|
|
const parallelNames = ["parallel-0", "parallel-1", "parallel-2"];
|
|
const sent = manifest.events.filter(
|
|
(event) =>
|
|
event.event === "request-sent" && parallelNames.includes(event.case),
|
|
);
|
|
const received = manifest.events.filter(
|
|
(event) =>
|
|
event.event === "request-received" && parallelNames.includes(event.case),
|
|
);
|
|
const responses = manifest.events.filter(
|
|
(event) =>
|
|
event.event === "response-sent" && parallelNames.includes(event.case),
|
|
);
|
|
assert.equal(new Set(sent.map((event) => event.connection)).size, 1);
|
|
assert.equal(new Set(received.map((event) => event.connection)).size, 1);
|
|
assert.equal(new Set(sent.map((event) => event.stream_id)).size, 3);
|
|
assert.equal(new Set(received.map((event) => event.stream_id)).size, 3);
|
|
assert.ok(
|
|
Math.max(...received.map((event) => event.monotonic)) <
|
|
responses[0].monotonic,
|
|
);
|
|
assert.deepEqual(
|
|
responses.map((event) => event.case),
|
|
received.map((event) => event.case).reverse(),
|
|
);
|
|
for (const side of ["client_conn", "server_conn"])
|
|
assert.equal(
|
|
new Set(
|
|
parallelNames.map((name) => byCase(normal, name).reported[side].id),
|
|
).size,
|
|
1,
|
|
);
|
|
const normalConnections = new Set(
|
|
[...sent, ...received].map((event) => event.connection),
|
|
);
|
|
for (const connection of normalConnections) {
|
|
const handshakes = manifest.events.filter(
|
|
(event) => event.event === "handshake" && event.connection === connection,
|
|
);
|
|
assert.equal(handshakes.length, 1);
|
|
assert.equal(handshakes[0].alpn, "h3");
|
|
assert.equal(handshakes[0].quic_version, 1);
|
|
}
|
|
assert.equal(
|
|
new Set(normal.records.map((record) => record.reported.id)).size,
|
|
4,
|
|
);
|
|
assert.equal(sent.length, 3);
|
|
const receivedByClient = manifest.events.filter(
|
|
(event) =>
|
|
event.event === "response-received" &&
|
|
sent.some(
|
|
(request) =>
|
|
request.connection === event.connection &&
|
|
request.stream_id === event.stream_id,
|
|
),
|
|
);
|
|
assert.equal(receivedByClient.length, 3);
|
|
for (const request of sent) {
|
|
const response = receivedByClient.find(
|
|
(event) =>
|
|
event.connection === request.connection &&
|
|
event.stream_id === request.stream_id,
|
|
);
|
|
assert.ok(
|
|
response,
|
|
"Each sent request needs an independently observed client response",
|
|
);
|
|
assert.deepEqual(response.body, manifest.responses[request.case].body);
|
|
assert.deepEqual(request.body, manifest.requests[request.case].body);
|
|
assertEndpointHeaders(
|
|
request.headers,
|
|
manifest.requests[request.case].headers,
|
|
);
|
|
assertEndpointHeaders(
|
|
response.headers,
|
|
manifest.responses[request.case].headers,
|
|
);
|
|
}
|
|
return {
|
|
sent: sent.map((event) => event.case),
|
|
completed_at_origin: received.map((event) => event.case),
|
|
dispatched_at_origin: responses.map((event) => event.case),
|
|
completed_at_client: receivedByClient.map(
|
|
(event) =>
|
|
sent.find(
|
|
(request) =>
|
|
request.connection === event.connection &&
|
|
request.stream_id === event.stream_id,
|
|
).case,
|
|
),
|
|
saved_in_capture: normal.records
|
|
.filter((record) => parallelNames.includes(caseOf(record)))
|
|
.map(caseOf),
|
|
};
|
|
}
|