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:
@@ -20,7 +20,7 @@ import { fileURLToPath } from 'node:url';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { TCP_FLAG, type Id, type NetworkRuntimeEngine, type TcpSegment } from '../src/index.js';
|
||||
import { loseFirst } from './fixtures.js';
|
||||
import { GAP_TOLERANCE_MILLISECONDS, gaps, javaFrames, watchedEngine, type JavaFrame, type SeenFrame } from './javaRun.js';
|
||||
import { expectSameCaptures, GAP_TOLERANCE_MILLISECONDS, gaps, javaFrames, watchedEngine, withClientPortText, type JavaFrame, type SeenFrame } from './javaRun.js';
|
||||
import type { Scenario } from './scenario.js';
|
||||
|
||||
const scenarioDirectory = join(dirname(fileURLToPath(import.meta.url)), 'scenarios');
|
||||
@@ -241,6 +241,17 @@ const JAVA_UNTIL: Record<string, (java: JavaRun) => number> = {
|
||||
lostSynAck: (java) => java.states.client!.find(([, state]) => state === 'SYN_SENT')![0] + JAVA_TIMEOUT_MILLISECONDS - GAP_TOLERANCE_MILLISECONDS,
|
||||
};
|
||||
|
||||
/**
|
||||
* Where each card's capture is compared only up to a time (Java's), beyond
|
||||
* JAVA_UNTIL. closedPort: JAVA_BUGS adds the client's two later SYNs to its
|
||||
* own card, not where else they show — the switch has forgotten the server
|
||||
* by then and floods them to every card. Compared up to the second SYN.
|
||||
*/
|
||||
const CAPTURE_UNTIL: Record<string, (java: JavaRun) => number> = {
|
||||
...JAVA_UNTIL,
|
||||
closedPort: (java) => java.states.client!.find(([, state]) => state === 'SYN_SENT')![0] + JAVA_TIMEOUT_MILLISECONDS - GAP_TOLERANCE_MILLISECONDS,
|
||||
};
|
||||
|
||||
describe('TCP as Java FILIUS runs it', () => {
|
||||
it('corrects Java only where it errs', () => {
|
||||
for (const [name, correct] of Object.entries(JAVA_BUGS)) {
|
||||
@@ -250,7 +261,7 @@ describe('TCP as Java FILIUS runs it', () => {
|
||||
});
|
||||
|
||||
it('cuts only where Java goes on with its error', () => {
|
||||
for (const [name, until] of Object.entries(JAVA_UNTIL)) {
|
||||
for (const [name, until] of Object.entries(CAPTURE_UNTIL)) {
|
||||
const java = JSON.parse(readFileSync(join(scenarioDirectory, `tcp-java-${name}.java.json`), 'utf8')) as JavaRun;
|
||||
const cut = until(java);
|
||||
const later = [...Object.values(java.frames).flat().map((frame) => frame.time), ...Object.values(java.states).flat().map(([time]) => time)];
|
||||
@@ -288,13 +299,17 @@ describe('TCP as Java FILIUS runs it', () => {
|
||||
);
|
||||
}
|
||||
|
||||
expectSameCaptures(engine, scenario, java.frames, { text: withClientPortText, until: CAPTURE_UNTIL[name]?.(measured) });
|
||||
|
||||
const shift = (states: [number, string][], syn: number): [number, string][] => before(states.map(([time, state]) => [time - syn, state]));
|
||||
// From SYN_SENT: Java's first CLOSED is when the socket was made.
|
||||
const javaClient = java.states.client!.filter(([, state], index) => !(index === 0 && state === 'CLOSED'));
|
||||
expectSameStates('client', shift(engineStates(engine, story.client, false), engineSyn), shift(javaClient, javaSyn));
|
||||
if (java.states.server !== undefined) {
|
||||
// A listening server from its first change: Java's first LISTEN is when it began to listen.
|
||||
const javaServer = story.serverListening === true ? java.states.server.slice(1) : java.states.server;
|
||||
// A listening server from its first change: Java's first LISTEN is when it began to listen (its
|
||||
// poll may have seen the socket CLOSED before).
|
||||
const listening = java.states.server.findIndex(([, state]) => state === 'LISTEN');
|
||||
const javaServer = story.serverListening === true ? java.states.server.slice(listening + 1) : java.states.server;
|
||||
expectSameStates('server', shift(engineStates(engine, SERVER, story.serverListening !== true), engineSyn), shift(javaServer, javaSyn));
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user