// 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), ); }; }