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:
@@ -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 { expectSameCaptures, GAP_TOLERANCE_MILLISECONDS, javaFrames, watchedEngine, type JavaFrame, type SeenFrame } from './javaRun.js';
|
||||
import { expectSameCaptures, GAP_TOLERANCE_MILLISECONDS, javaFrames, watchedEngine, type JavaFrame, type SeenFrame, type JavaTables } from './javaRun.js';
|
||||
import type { Scenario } from './scenario.js';
|
||||
|
||||
const scenarioDirectory = join(dirname(fileURLToPath(import.meta.url)), 'scenarios');
|
||||
@@ -49,7 +49,7 @@ function bySender(frames: readonly SeenFrame[]): Map<string, SeenFrame[]> {
|
||||
return result;
|
||||
}
|
||||
|
||||
interface JavaRun {
|
||||
interface JavaRun extends JavaTables {
|
||||
frames: Record<string, JavaFrame[]>;
|
||||
events: [number, string][];
|
||||
nat: string[];
|
||||
@@ -60,13 +60,17 @@ type Call = (label: string, run: () => string) => void;
|
||||
const text = (value: string) => new TextEncoder().encode(value);
|
||||
const decode = (value: Uint8Array | undefined) => (value === undefined ? 'null' : new TextDecoder().decode(value));
|
||||
|
||||
const ICMP_CODES: Record<string, string> = { timeExceeded: '11/0', destinationNetworkUnreachable: '3/0', destinationHostUnreachable: '3/1' };
|
||||
|
||||
/** A ping in Java's words: the answer's type and code and its sender, or null. */
|
||||
function ping(engine: NetworkRuntimeEngine, from: Id, target: string, sequenceNumber: number): string {
|
||||
const result: IPv4DeliveryResult = engine.sendIcmpEcho(from, target, { sequenceNumber });
|
||||
if (result.type === 'icmpError') return `ICMP ${result.kind}`;
|
||||
const reply = (engine.state.icmpObservationsByNodeId.get(from) ?? []).find(
|
||||
(observation) => observation.message.kind === 'echoReply' && observation.message.sequenceNumber === sequenceNumber,
|
||||
);
|
||||
function ping(engine: NetworkRuntimeEngine, from: Id, target: string, sequenceNumber: number, timeToLive = 64): string {
|
||||
const result: IPv4DeliveryResult = engine.sendIcmpEcho(from, target, { sequenceNumber, timeToLive });
|
||||
const observations = engine.state.icmpObservationsByNodeId.get(from) ?? [];
|
||||
if (result.type === 'icmpError') {
|
||||
const error = observations.findLast((observation) => observation.message.kind === result.kind);
|
||||
return error === undefined ? `ICMP ${result.kind}` : `ICMP ${ICMP_CODES[result.kind]} from ${error.packet.senderIPAddress}`;
|
||||
}
|
||||
const reply = observations.find((observation) => observation.message.kind === 'echoReply' && observation.message.sequenceNumber === sequenceNumber);
|
||||
return reply === undefined ? 'null' : `ICMP 0/0 from ${reply.packet.senderIPAddress}`;
|
||||
}
|
||||
|
||||
@@ -131,6 +135,7 @@ const STORIES: Record<string, (engine: NetworkRuntimeEngine, call: Call) => void
|
||||
},
|
||||
tcpOut: (engine, call) => tcpExchange(engine, call, SERVER, LAN_PC, '42.0.0.20', 80),
|
||||
forward: (engine, call) => tcpExchange(engine, call, LAN_PC, SERVER, '42.0.0.10', 8080),
|
||||
ttlOut: (engine, call) => call('sendProbe 42.0.0.20', () => ping(engine, LAN_PC, '42.0.0.20', 1, 1)),
|
||||
pingWan: (engine, call) => {
|
||||
call('sendProbe 42.0.0.10', () => ping(engine, SERVER, '42.0.0.10', 1));
|
||||
call('sendProbe 192.168.0.10', () => ping(engine, SERVER, '192.168.0.10', 2));
|
||||
@@ -259,6 +264,7 @@ describe('NAT as Java FILIUS runs it', () => {
|
||||
// A card's own list keeps its order.
|
||||
expectSameCaptures(engine, scenario, java.frames, {
|
||||
normalized: withPlaceholders,
|
||||
tables: measured,
|
||||
tolerance: TCP_STORIES.has(name) ? TCP_TOLERANCE_MILLISECONDS : GAP_TOLERANCE_MILLISECONDS,
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user