feat(core): packet capture per card after Java's Lauscher, measured card by card

The engine keeps Java's packet capture (capture.ts): per network card
every frame it sent (when it leaves on the cable) or received (also one
for another card), once, with time and number; switches log nothing;
frames the packet loss dropped are kept apart; the capture outlives a
stop and a new start, as in Java, until clearCapture. Per port instead
of per MAC, at most 10,000 frames a card.

expectSameCaptures compares each card's capture with Java's list of that
card in the ARP, WLAN, DNS, TCP, NAT, RIP and single-client DHCP runs.
It found that ScenarioNetwork ignored a switch's learning time (Java ran
300 s, the core the scenarios' 5 s); the oracle sets it now, and the TCP
and DHCP runs are recorded again. A core test found that a new start
numbers frames from 1 again and the capture took them as seen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 08:53:37 +00:00
co-authored by Claude Opus 5.5
parent 65ccd9ddd7
commit 088b62469f
24 changed files with 796 additions and 344 deletions
+6 -2
View File
@@ -16,7 +16,7 @@ import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { describe, expect, it } from 'vitest';
import { stableMacAddress } from '../src/index.js';
import { GAP_TOLERANCE_MILLISECONDS, javaFrames, watchedEngine, type JavaFrame, type SeenFrame } from './javaRun.js';
import { expectSameCaptures, GAP_TOLERANCE_MILLISECONDS, javaFrames, watchedEngine, type JavaFrame, type SeenFrame } from './javaRun.js';
import { type Scenario } from './scenario.js';
const scenarioDirectory = join(dirname(fileURLToPath(import.meta.url)), 'scenarios');
@@ -38,7 +38,7 @@ function runEngine(scenario: Scenario, milliseconds: number) {
[device.ipAddress, device.subnetMask, device.defaultGateway, device.dnsServer],
]),
);
return { frames: frames(), devices };
return { engine, frames: frames(), devices };
}
/** Consecutive frames without the other clients' own: the text, and the time since the one before. */
@@ -86,6 +86,10 @@ describe('DHCP as Java FILIUS runs it', () => {
);
}
}
// Each card's capture, with one client: of two that start together, Java's
// log loses the DISCOVER of the one that sends second, and which sends
// first varies (javaRun.ts).
if (clients.length === 1) expectSameCaptures(engine.engine, scenario, java.frames, { text: (text) => text.replace(LINK_LOCAL, '169.254.1.x') });
const linkLocal = (devices: Record<string, string[]>) =>
Object.fromEntries(Object.entries(devices).map(([node, device]) => [node, device.map((value) => value.replace(LINK_LOCAL, '169.254.1.x'))]));
expect(linkLocal(engine.devices)).toEqual(linkLocal(java.devices));