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:
@@ -22,7 +22,7 @@ import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { Id, IPv4DeliveryResult, NetworkRuntimeEngine } 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');
|
||||
@@ -144,6 +144,8 @@ const STORIES: Record<string, (engine: NetworkRuntimeEngine, call: Call) => void
|
||||
* end; the second server gets the datagram from the entry's WAN port, but its
|
||||
* answer finds no entry and stays at the router. The engine adds an entry for
|
||||
* the second remote end: its answer goes on to the PC, a cable later.
|
||||
* (`answer` is the frame on the home router's WAN card, the first list it
|
||||
* shows in.)
|
||||
*/
|
||||
const JAVA_BUGS: Record<string, (java: JavaRun) => JavaRun> = {
|
||||
udpTwoRemotes: (java) => {
|
||||
@@ -162,7 +164,8 @@ const JAVA_BUGS: Record<string, (java: JavaRun) => JavaRun> = {
|
||||
const wanPort = java.nat[0]!.match(/42\.0\.0\.20:(\d+)/)![1];
|
||||
const received = java.events.find(([, event]) => event === 'other server received two')!;
|
||||
return {
|
||||
frames: { ...java.frames, [LAN_PC]: [...java.frames[LAN_PC]!, onLan] },
|
||||
// The home router's LAN card sends it as it arrives on the WAN card; the PC's receives it a cable later.
|
||||
frames: { ...java.frames, [`${GATEWAY}#1`]: [...java.frames[`${GATEWAY}#1`]!, { ...onLan, time: answer.time + 1 }], [LAN_PC]: [...java.frames[LAN_PC]!, onLan] },
|
||||
events: java.events.map(([time, event], index) =>
|
||||
index === java.events.length - 1 && event === 'empfangen returned null' ? [received[0] + 168, 'empfangen returned pong two'] : [time, event],
|
||||
),
|
||||
@@ -171,8 +174,8 @@ const JAVA_BUGS: Record<string, (java: JavaRun) => JavaRun> = {
|
||||
},
|
||||
};
|
||||
|
||||
/** Free ports as "port", initial sequence numbers by their order, ties sorted. */
|
||||
function normalized(frames: readonly SeenFrame[]): SeenFrame[] {
|
||||
/** Free ports as "port", initial sequence numbers by their order. */
|
||||
function withPlaceholders(frames: readonly SeenFrame[]): SeenFrame[] {
|
||||
const bases = new Map<number, number>();
|
||||
const sequence = (value: string) => {
|
||||
const number = Number(value);
|
||||
@@ -180,10 +183,15 @@ function normalized(frames: readonly SeenFrame[]): SeenFrame[] {
|
||||
if (!bases.has(base)) bases.set(base, bases.size + 1);
|
||||
return `isn${bases.get(base)}+${number % 1_000_000}`;
|
||||
};
|
||||
const texts = frames.map((frame) => ({
|
||||
return frames.map((frame) => ({
|
||||
time: frame.time,
|
||||
text: withPorts(frame.text).replace(/(seq|ack)=(\d+)/g, (whole, name: string, value: string) => (value === '0' ? whole : `${name}=${sequence(value)}`)),
|
||||
}));
|
||||
}
|
||||
|
||||
/** With placeholders, ties sorted: over all cards, Java's log writes frames that show together in any order. */
|
||||
function normalized(frames: readonly SeenFrame[]): SeenFrame[] {
|
||||
const texts = withPlaceholders(frames);
|
||||
const result: SeenFrame[] = [];
|
||||
for (let start = 0; start < texts.length; ) {
|
||||
let end = start + 1;
|
||||
@@ -248,6 +256,12 @@ describe('NAT as Java FILIUS runs it', () => {
|
||||
}
|
||||
}
|
||||
|
||||
// A card's own list keeps its order.
|
||||
expectSameCaptures(engine, scenario, java.frames, {
|
||||
normalized: withPlaceholders,
|
||||
tolerance: TCP_STORIES.has(name) ? TCP_TOLERANCE_MILLISECONDS : GAP_TOLERANCE_MILLISECONDS,
|
||||
});
|
||||
|
||||
// The client's calls: Java's server threads write events of their own, which the engine's story has not.
|
||||
const javaCalls = java.events.filter(([, event]) => !event.startsWith('server') && !event.startsWith('other server'));
|
||||
expect(events.map(([, event]) => event)).toEqual(javaCalls.map(([, event]) => event));
|
||||
|
||||
Reference in New Issue
Block a user