Files
mdg-filius/packages/core/test/fixtures.ts
T
julian.kiedaischandClaude Opus 5.5 7e0348636c feat(core): SYN+ACK and FIN go once, as in Java, measured with Java FILIUS
TcpRun loses exactly one frame (loseFirst: the card's output buffer drops
it; the sender's packet log still shows it, as with setDrop) in three new
stories: lostSynAck, lostFin, lostLastFin.

Measured: Java never repeats a SYN+ACK or a FIN. The server gives a
handshake up after one timeout (3 RTT) and closes with a FIN from SYN_RCVD
(its boundary check sometimes lets it wait twice; it means once). After its
FIN a socket waits at most five times, each wait ended by a segment or the
timeout; then it is gone. The core does the same (TCP_CLOSING_WAITS, with
TIME_WAIT in the loop) and shows CLOSED where Java leaves the state
standing (a named Java bug).

One departure: a repeated SYN in SYN_RCVD gets the SYN+ACK once more and
the wait starts anew; Java closes there, but its client never repeats the
SYN. lostSynAck is compared up to the second SYN (JAVA_UNTIL); the rest is
an engine test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 18:08:18 +00:00

87 lines
3.3 KiB
TypeScript

// Test fixtures, ported from the private helpers of
// filius-ipad ios/FiliusPadTests/TopologyNetworkRuntimeEngineTests.swift.
import {
deviceConfiguration,
type EthernetFrame,
type TcpSegment,
NetworkRuntimeEngine,
portSnapshot,
topologySnapshot,
type DeviceConfiguration,
type Id,
type LinkSnapshot,
type NodeSnapshot,
type TopologySnapshotInput,
} from '../src/index.js';
let linkCounter = 0;
/** Swift uses a fresh `UUID()` per link; a counter keeps runs reproducible. */
export function link(sourceNodeId: Id, sourcePortId: Id, targetNodeId: Id, targetPortId: Id): LinkSnapshot {
linkCounter += 1;
return {
id: `ffffffff-0000-0000-0000-${linkCounter.toString(16).padStart(12, '0')}`,
sourceNodeId,
sourcePortId,
targetNodeId,
targetPortId,
};
}
export function pc(nodeId: Id, portId: Id): NodeSnapshot {
return { id: nodeId, kind: 'pc', ports: [portSnapshot(portId, 'eth0')] };
}
export function device(ipAddress: string, options: { mask?: string; gateway?: string } = {}): DeviceConfiguration {
return deviceConfiguration(ipAddress, options.mask ?? '255.255.255.0', options.gateway ?? '');
}
export function startedEngine(
input: TopologySnapshotInput,
seed: number = 14,
): NetworkRuntimeEngine {
const engine = new NetworkRuntimeEngine(seed);
engine.handle({ type: 'start', snapshot: topologySnapshot(input), seed, initialTimeMilliseconds: 0 });
return engine;
}
/** `00000000-0000-0000-0000-` followed by twelve hex digits. */
export function uuid(suffix: number): Id {
return `00000000-0000-0000-0000-${suffix.toString(16).toUpperCase().padStart(12, '0')}`;
}
const FRAME_EVENTS = new Set(['ethernetLinkDeparture', 'ethernetLinkDelivery', 'remoteLinkDelivery', 'arpAnswered']);
/**
* Advances until every frame in flight has arrived — what Swift's send did
* by itself; the engine's send returns at once, as Java's.
*/
export function letFramesArrive(engine: NetworkRuntimeEngine): void {
const inFlight = () =>
engine.state.pendingEvents.filter((event) => FRAME_EVENTS.has(event.kind.kind)).map((event) => event.deadlineMilliseconds);
for (let deadlines = inFlight(); deadlines.length > 0; deadlines = inFlight()) {
engine.handle({ type: 'advance', toMilliseconds: Math.min(...deadlines) });
}
}
/**
* The first frame the node sends whose TCP segment matches is lost, as with
* TcpRun's loseFirst: it leaves (the sender's card shows it) and never arrives.
*/
export function loseFirst(engine: NetworkRuntimeEngine, nodeId: Id, which: (segment: TcpSegment) => boolean): void {
type Depart = (fromNodeId: Id, portId: Id, endpoint: unknown, frame: EthernetFrame) => void;
const target = engine as unknown as { departEthernetFrame: Depart };
const depart = target.departEthernetFrame.bind(engine);
let lost = false;
target.departEthernetFrame = (fromNodeId, portId, endpoint, frame) => {
depart(fromNodeId, portId, endpoint, frame);
const payload = frame.payload.kind === 'ipv4' ? frame.payload.packet.payload : undefined;
if (lost || fromNodeId !== nodeId || payload?.kind !== 'tcp' || !which(payload.segment)) return;
lost = true;
engine.state.pendingEvents = engine.state.pendingEvents.filter(
(event) => !(event.kind.kind === 'ethernetLinkDelivery' && event.kind.frame.identity === frame.identity),
);
};
}