feat(core): the packet capture's table after Java's Lauscher, measured against Java's own tables
captureRows.ts builds Java's rows per frame (Lauscher.datenVorbereiten): Ethernet; ARP, ICMP or IP; TCP or UDP; the data with Java's protocol recognition; with addresses and ports or ports only. The comment stays structured; captureCommentText says it with given words and numbers. ScenarioNetwork writes Java's finished tables per card (Lauscher.getDaten, German locale); all runtime runs are recorded again, NatRun has ttlOut. expectSameCaptures compares both views for every frame. Fixed where the engine differed: an ARP request's target MAC is the broadcast address, an ICMP error's identifier and sequence number are 0 (RFC 792), and host unreachable from a router carries the packet as forwarded. Not taken: Java's lower-case broadcast address in a reply to 0.0.0.0, and the typo 'Exeeded'. RIP keeps its recordings: Java's staggered router start now gives a different startup on this machine. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -285,6 +285,31 @@ const scenarioNames = readdirSync(scenarioDirectory)
|
||||
*/
|
||||
const SWIFT_ERRORS: Record<string, (expected: Record<string, unknown>) => Record<string, unknown>> = {};
|
||||
|
||||
const ECHO_KINDS = new Set(['echoRequest', 'echoReply']);
|
||||
|
||||
/**
|
||||
* Java instead of Swift in every scenario: an ICMP error's identifier and
|
||||
* sequence number are 0 (RFC 792: unused; Java's sendeICMP), where Swift
|
||||
* copies them from the echo request it is about. Measured with Java FILIUS
|
||||
* (the capture tables of arpJava and natJava).
|
||||
*/
|
||||
function withJavaIcmpErrors(expected: Record<string, unknown>): Record<string, unknown> {
|
||||
type Header = [string, string];
|
||||
const headers = (fields: Header[]): Header[] => {
|
||||
const kind = fields.find(([name]) => name === 'kind')?.[1];
|
||||
if (kind === undefined || ECHO_KINDS.has(kind)) return fields;
|
||||
return fields.map(([name, value]) => [name, name === 'identifier' || name === 'sequence' ? '0' : value]);
|
||||
};
|
||||
const traces = (expected.traces as { before: Header[]; after: Header[] }[]).map((trace) => ({ ...trace, before: headers(trace.before), after: headers(trace.after) }));
|
||||
const observation = (item: { kind?: string }) => (item.kind === undefined || ECHO_KINDS.has(item.kind) ? item : { ...item, identifier: 0, sequence: 0 });
|
||||
const observations = Object.fromEntries(
|
||||
Object.entries(expected.icmpObservations as Record<string, { kind: string }[]>).map(([node, items]) => [node, items.map(observation)]),
|
||||
);
|
||||
// A traceroute's result is its observations (other results are lists too: a switch's table).
|
||||
const results = (expected.results as unknown[]).map((result) => (Array.isArray(result) ? (result as { kind?: string }[]).map(observation) : result));
|
||||
return { ...expected, traces, icmpObservations: observations, results };
|
||||
}
|
||||
|
||||
/**
|
||||
* Scenarios where the engine follows Java instead of Swift; the Java
|
||||
* comparisons check that behaviour (dhcpJava.test.ts, tcpJava.test.ts,
|
||||
@@ -319,6 +344,14 @@ describe('same result as the Swift engine', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('corrects ICMP errors where Swift has them', () => {
|
||||
const corrected = scenarioNames.filter((name) => {
|
||||
const expected = JSON.parse(readFileSync(join(scenarioDirectory, `${name}.swift.json`), 'utf8')) as Record<string, unknown>;
|
||||
return JSON.stringify(withJavaIcmpErrors(expected)) !== JSON.stringify(expected);
|
||||
});
|
||||
expect(corrected.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('has scenarios with Swift results', () => {
|
||||
expect(scenarioNames.length).toBeGreaterThan(0);
|
||||
});
|
||||
@@ -334,7 +367,7 @@ describe('same result as the Swift engine', () => {
|
||||
for (const [name, { trace, reason }] of Object.entries(SWIFT_UNTIL_TRACE)) {
|
||||
it(`${name} matches Swift until trace #${trace}, then follows Java: ${reason}`, () => {
|
||||
const scenario = JSON.parse(readFileSync(join(scenarioDirectory, `${name}.scenario.json`), 'utf8')) as Scenario;
|
||||
const swift = JSON.parse(readFileSync(join(scenarioDirectory, `${name}.swift.json`), 'utf8')) as Record<string, unknown>;
|
||||
const swift = withJavaIcmpErrors(JSON.parse(readFileSync(join(scenarioDirectory, `${name}.swift.json`), 'utf8')) as Record<string, unknown>);
|
||||
const actual = run(scenario);
|
||||
const expectedTraces = swift.traces as unknown[];
|
||||
const actualTraces = actual.traces as unknown[];
|
||||
@@ -349,7 +382,7 @@ describe('same result as the Swift engine', () => {
|
||||
it(name, () => {
|
||||
const scenario = JSON.parse(readFileSync(join(scenarioDirectory, `${name}.scenario.json`), 'utf8')) as Scenario;
|
||||
const swift = JSON.parse(readFileSync(join(scenarioDirectory, `${name}.swift.json`), 'utf8')) as Record<string, unknown>;
|
||||
const expected = SWIFT_ERRORS[name]?.(swift) ?? swift;
|
||||
const expected = withJavaIcmpErrors(SWIFT_ERRORS[name]?.(swift) ?? swift);
|
||||
const actual = run(scenario);
|
||||
|
||||
// Traces first and one by one: the first difference is the one that matters.
|
||||
|
||||
Reference in New Issue
Block a user