test(core): Swift oracle — the filius-ipad core built on Linux as the reference
tools/swift-oracle builds the unchanged Swift runtime (pinned 3b5ded0) in Docker and replays JSON scenarios; the TypeScript engine must produce the same trace events, results and end state. Five scenarios, 884 events, identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,4 +21,11 @@ Swift-Pfade relativ zu `filius-ipad/ios/FiliusPad/TopologyEditor/` (Stand
|
|||||||
| `src/runtime/trace.ts` | Engine, Paketspur-Modell | übertragen |
|
| `src/runtime/trace.ts` | Engine, Paketspur-Modell | übertragen |
|
||||||
| `src/util/equal.ts` | — | neu (Ersatz für Swifts `Equatable`) |
|
| `src/util/equal.ts` | — | neu (Ersatz für Swifts `Equatable`) |
|
||||||
| `test/engine.test.ts` | `TopologyNetworkRuntimeEngineTests.swift` (21 von 72 Tests) | übertragen; `nextInt`-Referenzwerte an OpenJDK 21 gemessen |
|
| `test/engine.test.ts` | `TopologyNetworkRuntimeEngineTests.swift` (21 von 72 Tests) | übertragen; `nextInt`-Referenzwerte an OpenJDK 21 gemessen |
|
||||||
|
| `test/oracle.test.ts`, `test/scenarios/` | — | neu: 5 Szenarien, Ergebnis mit dem Swift-Kern abgeglichen (`tools/swift-oracle`) |
|
||||||
| `test/routeTable.test.ts` | `TopologyJavaRouteTableTests.swift` (8 von 12 Tests) | übertragen; Geometrie- und Reducer-Tests folgen mit Editor und Dokument |
|
| `test/routeTable.test.ts` | `TopologyJavaRouteTableTests.swift` (8 von 12 Tests) | übertragen; Geometrie- und Reducer-Tests folgen mit Editor und Dokument |
|
||||||
|
|
||||||
|
## tools/swift-oracle
|
||||||
|
|
||||||
|
Baut den unveränderten Swift-Kern (Stand `3b5ded0`) unter Linux, nur zwei
|
||||||
|
Ausdrücke sind für den Linux-Compiler aufgeteilt (`patches/`). Die Quellen
|
||||||
|
selbst werden beim Bau geholt und nicht eingecheckt.
|
||||||
|
|||||||
+3
-2
@@ -11,9 +11,10 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"typecheck": "tsc -b",
|
"typecheck": "tsc -b && tsc -p packages/core/test",
|
||||||
"check": "npm run typecheck && npm run test",
|
"check": "npm run typecheck && npm run test",
|
||||||
"hooks": "git config core.hooksPath scripts/hooks"
|
"hooks": "git config core.hooksPath scripts/hooks",
|
||||||
|
"oracle": "tools/swift-oracle/run.sh"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.19.0",
|
"@types/node": "^20.19.0",
|
||||||
|
|||||||
@@ -0,0 +1,299 @@
|
|||||||
|
// Replays every scenario in test/scenarios with the TypeScript engine and
|
||||||
|
// compares the result with what the filius-ipad Swift engine produced for the
|
||||||
|
// same file (<name>.swift.json, written by tools/swift-oracle/run.sh).
|
||||||
|
// Every trace event must match: identity, time, node, headers, detail.
|
||||||
|
|
||||||
|
import { readdirSync, readFileSync } from 'node:fs';
|
||||||
|
import { dirname, join } from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import {
|
||||||
|
deviceConfiguration,
|
||||||
|
firewallConfiguration,
|
||||||
|
firewallRule,
|
||||||
|
interfaceKey,
|
||||||
|
NetworkRuntimeEngine,
|
||||||
|
portSnapshot,
|
||||||
|
switchConfiguration,
|
||||||
|
topologySnapshot,
|
||||||
|
type FirewallConfiguration,
|
||||||
|
type IPv4DeliveryResult,
|
||||||
|
type IcmpObservation,
|
||||||
|
type ManualRoute,
|
||||||
|
type NodeKind,
|
||||||
|
type TopologySnapshot,
|
||||||
|
type TraceEvent,
|
||||||
|
} from '../src/index.js';
|
||||||
|
|
||||||
|
const scenarioDirectory = join(dirname(fileURLToPath(import.meta.url)), 'scenarios');
|
||||||
|
|
||||||
|
interface Scenario {
|
||||||
|
seed: number;
|
||||||
|
speedPercent?: number;
|
||||||
|
nodes: { id: string; kind: NodeKind; ports: { id: string; label: string; mac?: string }[] }[];
|
||||||
|
links: { id: string; sourceNode: string; sourcePort: string; targetNode: string; targetPort: string }[];
|
||||||
|
devices?: Record<string, { ip: string; mask: string; gateway?: string; dns?: string }>;
|
||||||
|
interfaces?: { node: string; port: string; ip: string; mask: string }[];
|
||||||
|
routes?: Record<string, { destination: string; mask: string; gateway: string; interface: string }[]>;
|
||||||
|
switches?: Record<string, { ssid: string; retention?: number }>;
|
||||||
|
firewalls?: Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
active?: boolean;
|
||||||
|
defaultPolicy?: 'accept' | 'drop';
|
||||||
|
dropIcmp?: boolean;
|
||||||
|
synOnly?: boolean;
|
||||||
|
filterUdp?: boolean;
|
||||||
|
rules?: {
|
||||||
|
source?: string;
|
||||||
|
sourceMask?: string;
|
||||||
|
destination?: string;
|
||||||
|
destinationMask?: string;
|
||||||
|
port?: number;
|
||||||
|
protocol?: 'all' | 'icmp' | 'tcp' | 'udp';
|
||||||
|
action?: 'accept' | 'drop';
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
steps: {
|
||||||
|
op: string;
|
||||||
|
from?: string;
|
||||||
|
node?: string;
|
||||||
|
target?: string;
|
||||||
|
ttl?: number;
|
||||||
|
identifier?: number;
|
||||||
|
sequence?: number;
|
||||||
|
maxTtl?: number;
|
||||||
|
to?: number;
|
||||||
|
enabled?: boolean;
|
||||||
|
percent?: number;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function snapshotOf(scenario: Scenario): TopologySnapshot {
|
||||||
|
const firewalls = new Map<string, FirewallConfiguration>();
|
||||||
|
for (const [node, config] of Object.entries(scenario.firewalls ?? {})) {
|
||||||
|
firewalls.set(
|
||||||
|
node,
|
||||||
|
firewallConfiguration({
|
||||||
|
isActive: config.active ?? false,
|
||||||
|
defaultPolicy: config.defaultPolicy ?? 'drop',
|
||||||
|
dropIcmp: config.dropIcmp ?? false,
|
||||||
|
filterSynSegmentsOnly: config.synOnly ?? true,
|
||||||
|
filterUdp: config.filterUdp ?? true,
|
||||||
|
rules: (config.rules ?? []).map((rule) =>
|
||||||
|
firewallRule({
|
||||||
|
sourceIPAddress: rule.source ?? '',
|
||||||
|
sourceSubnetMask: rule.sourceMask ?? '',
|
||||||
|
destinationIPAddress: rule.destination ?? '',
|
||||||
|
destinationSubnetMask: rule.destinationMask ?? '',
|
||||||
|
...(rule.port === undefined ? {} : { port: rule.port }),
|
||||||
|
protocol: rule.protocol ?? 'tcp',
|
||||||
|
action: rule.action ?? 'drop',
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return topologySnapshot({
|
||||||
|
nodes: scenario.nodes.map((node) => ({
|
||||||
|
id: node.id,
|
||||||
|
kind: node.kind,
|
||||||
|
ports: node.ports.map((port) => portSnapshot(port.id, port.label, port.mac)),
|
||||||
|
})),
|
||||||
|
links: scenario.links.map((link) => ({
|
||||||
|
id: link.id,
|
||||||
|
sourceNodeId: link.sourceNode,
|
||||||
|
sourcePortId: link.sourcePort,
|
||||||
|
targetNodeId: link.targetNode,
|
||||||
|
targetPortId: link.targetPort,
|
||||||
|
})),
|
||||||
|
deviceConfigurations: new Map(
|
||||||
|
Object.entries(scenario.devices ?? {}).map(([node, device]) => [
|
||||||
|
node,
|
||||||
|
deviceConfiguration(device.ip, device.mask, device.gateway ?? '', device.dns ?? ''),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
interfaceConfigurations: new Map(
|
||||||
|
(scenario.interfaces ?? []).map((item) => [
|
||||||
|
interfaceKey(item.node, item.port),
|
||||||
|
{ ipAddress: item.ip, subnetMask: item.mask },
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
manualRoutesByNodeId: new Map(
|
||||||
|
Object.entries(scenario.routes ?? {}).map(([node, rows]) => [
|
||||||
|
node,
|
||||||
|
rows.map(
|
||||||
|
(row): ManualRoute => ({
|
||||||
|
destinationNetwork: row.destination,
|
||||||
|
subnetMask: row.mask,
|
||||||
|
gateway: row.gateway,
|
||||||
|
interfaceIPAddress: row.interface,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
switchConfigurationsByNodeId: new Map(
|
||||||
|
Object.entries(scenario.switches ?? {}).map(([node, config]) => [
|
||||||
|
node,
|
||||||
|
config.retention === undefined ? switchConfiguration(config.ssid) : switchConfiguration(config.ssid, config.retention),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
firewallConfigurationsByNodeId: firewalls,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const upper = (id: string) => id.toUpperCase();
|
||||||
|
|
||||||
|
function traceJson(event: TraceEvent): Record<string, unknown> {
|
||||||
|
return {
|
||||||
|
id: event.id,
|
||||||
|
time: event.timeMilliseconds,
|
||||||
|
node: upper(event.nodeId),
|
||||||
|
direction: event.direction,
|
||||||
|
layer: event.layer,
|
||||||
|
operation: event.operation,
|
||||||
|
before: event.beforeHeaders.map((field) => [field.name, field.value]),
|
||||||
|
after: event.afterHeaders.map((field) => [field.name, field.value]),
|
||||||
|
...(event.frameIdentity === undefined ? {} : { frame: event.frameIdentity }),
|
||||||
|
...(event.packetIdentity === undefined ? {} : { packet: event.packetIdentity }),
|
||||||
|
...(event.interfaceId === undefined ? {} : { interface: upper(event.interfaceId) }),
|
||||||
|
...(event.detail === undefined ? {} : { detail: event.detail }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function deliveryJson(result: IPv4DeliveryResult): Record<string, unknown> {
|
||||||
|
switch (result.type) {
|
||||||
|
case 'delivered':
|
||||||
|
return { type: 'delivered', packet: result.packetIdentity, node: upper(result.nodeId) };
|
||||||
|
case 'icmpError':
|
||||||
|
return { type: 'icmpError', packet: result.packetIdentity, node: upper(result.nodeId), kind: result.kind };
|
||||||
|
case 'dropped':
|
||||||
|
return {
|
||||||
|
type: 'dropped',
|
||||||
|
packet: result.packetIdentity,
|
||||||
|
...(result.nodeId === undefined ? {} : { node: upper(result.nodeId) }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function observationJson(item: IcmpObservation): Record<string, unknown> {
|
||||||
|
return {
|
||||||
|
kind: item.message.kind,
|
||||||
|
identifier: item.message.identifier,
|
||||||
|
sequence: item.message.sequenceNumber,
|
||||||
|
sender: item.packet.senderIPAddress,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function run(scenario: Scenario): Record<string, unknown> {
|
||||||
|
const engine = new NetworkRuntimeEngine(scenario.seed);
|
||||||
|
if (scenario.speedPercent !== undefined) engine.setSimulationSpeed(scenario.speedPercent);
|
||||||
|
engine.handle({ type: 'start', snapshot: snapshotOf(scenario), seed: scenario.seed, initialTimeMilliseconds: 0 });
|
||||||
|
|
||||||
|
const results: unknown[] = [];
|
||||||
|
for (const step of scenario.steps) {
|
||||||
|
switch (step.op) {
|
||||||
|
case 'ping':
|
||||||
|
results.push(
|
||||||
|
deliveryJson(
|
||||||
|
engine.sendIcmpEcho(step.from!, step.target!, {
|
||||||
|
timeToLive: step.ttl ?? 64,
|
||||||
|
identifier: step.identifier ?? 1,
|
||||||
|
sequenceNumber: step.sequence ?? 1,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'traceroute':
|
||||||
|
results.push(
|
||||||
|
engine
|
||||||
|
.traceIcmpEcho(step.from!, step.target!, {
|
||||||
|
maximumTimeToLive: step.maxTtl ?? 64,
|
||||||
|
identifier: step.identifier ?? 1,
|
||||||
|
})
|
||||||
|
.map(observationJson),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'resolveMac':
|
||||||
|
results.push(engine.resolveMacAddress(step.node!, step.target!) ?? null);
|
||||||
|
break;
|
||||||
|
case 'advance':
|
||||||
|
engine.handle({ type: 'advance', toMilliseconds: step.to! });
|
||||||
|
results.push(engine.state.currentTimeMilliseconds);
|
||||||
|
break;
|
||||||
|
case 'packetLoss':
|
||||||
|
engine.setGlobalPacketLossEnabled(step.enabled!);
|
||||||
|
results.push(engine.state.globalPacketLossEnabled);
|
||||||
|
break;
|
||||||
|
case 'sat':
|
||||||
|
results.push(
|
||||||
|
engine.switchSatEntries(step.node!).map((entry) => ({
|
||||||
|
mac: entry.macAddress,
|
||||||
|
port: entry.portLabel,
|
||||||
|
updated: entry.updatedAtMilliseconds,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'speed':
|
||||||
|
engine.setSimulationSpeed(step.percent!);
|
||||||
|
results.push(engine.simulationSpeed.percent);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`unknown step ${step.op}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const state = engine.state;
|
||||||
|
const mapOf = <V>(map: Map<string, V>, convert: (value: V) => unknown) =>
|
||||||
|
Object.fromEntries([...map].map(([key, value]) => [upper(key), convert(value)]));
|
||||||
|
return {
|
||||||
|
results,
|
||||||
|
traces: state.packetTraces.map(traceJson),
|
||||||
|
time: state.currentTimeMilliseconds,
|
||||||
|
pendingEvents: state.pendingEvents.length,
|
||||||
|
arp: mapOf(state.arpCachesByNodeId, (cache) =>
|
||||||
|
Object.fromEntries([...cache].map(([ip, entry]) => [ip, entry.macAddress])),
|
||||||
|
),
|
||||||
|
switchTables: mapOf(state.switchForwardingTablesByNodeId, (table) =>
|
||||||
|
Object.fromEntries([...table].map(([mac, port]) => [mac, upper(port)])),
|
||||||
|
),
|
||||||
|
icmpObservations: mapOf(state.icmpObservationsByNodeId, (items) => items.map(observationJson)),
|
||||||
|
firewallDecisions: state.firewallDecisions.map((decision) => ({
|
||||||
|
packet: decision.packetIdentity,
|
||||||
|
node: upper(decision.nodeId),
|
||||||
|
accepted: decision.accepted,
|
||||||
|
...(decision.ruleIndex === undefined ? {} : { rule: decision.ruleIndex }),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const scenarioNames = readdirSync(scenarioDirectory)
|
||||||
|
.filter((name) => name.endsWith('.scenario.json'))
|
||||||
|
.map((name) => name.replace('.scenario.json', ''))
|
||||||
|
.sort();
|
||||||
|
|
||||||
|
describe('same result as the Swift engine', () => {
|
||||||
|
it('has scenarios with Swift results', () => {
|
||||||
|
expect(scenarioNames.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const name of scenarioNames) {
|
||||||
|
it(name, () => {
|
||||||
|
const scenario = JSON.parse(readFileSync(join(scenarioDirectory, `${name}.scenario.json`), 'utf8')) as Scenario;
|
||||||
|
const expected = JSON.parse(readFileSync(join(scenarioDirectory, `${name}.swift.json`), 'utf8')) as Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
>;
|
||||||
|
const actual = run(scenario);
|
||||||
|
|
||||||
|
// Traces first and one by one: the first difference is the one that matters.
|
||||||
|
const expectedTraces = expected.traces as unknown[];
|
||||||
|
const actualTraces = actual.traces as unknown[];
|
||||||
|
for (let index = 0; index < Math.max(expectedTraces.length, actualTraces.length); index += 1) {
|
||||||
|
expect(actualTraces[index], `trace event #${index + 1}`).toEqual(expectedTraces[index]);
|
||||||
|
}
|
||||||
|
expect(actual).toEqual(expected);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
{
|
||||||
|
"seed": 18,
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000401",
|
||||||
|
"kind": "pc",
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000411",
|
||||||
|
"label": "eth0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000402",
|
||||||
|
"kind": "router",
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000421",
|
||||||
|
"label": "rt1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000422",
|
||||||
|
"label": "rt2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000403",
|
||||||
|
"kind": "pc",
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000413",
|
||||||
|
"label": "eth0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000404",
|
||||||
|
"kind": "pc",
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000414",
|
||||||
|
"label": "eth0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000405",
|
||||||
|
"kind": "networkSwitch",
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000451",
|
||||||
|
"label": "sw1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000452",
|
||||||
|
"label": "sw2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000453",
|
||||||
|
"label": "sw3"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-00000000F00A",
|
||||||
|
"sourceNode": "00000000-0000-0000-0000-000000000401",
|
||||||
|
"sourcePort": "00000000-0000-0000-0000-000000000411",
|
||||||
|
"targetNode": "00000000-0000-0000-0000-000000000405",
|
||||||
|
"targetPort": "00000000-0000-0000-0000-000000000451"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-00000000F00B",
|
||||||
|
"sourceNode": "00000000-0000-0000-0000-000000000404",
|
||||||
|
"sourcePort": "00000000-0000-0000-0000-000000000414",
|
||||||
|
"targetNode": "00000000-0000-0000-0000-000000000405",
|
||||||
|
"targetPort": "00000000-0000-0000-0000-000000000452"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-00000000F00C",
|
||||||
|
"sourceNode": "00000000-0000-0000-0000-000000000405",
|
||||||
|
"sourcePort": "00000000-0000-0000-0000-000000000453",
|
||||||
|
"targetNode": "00000000-0000-0000-0000-000000000402",
|
||||||
|
"targetPort": "00000000-0000-0000-0000-000000000421"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-00000000F00D",
|
||||||
|
"sourceNode": "00000000-0000-0000-0000-000000000402",
|
||||||
|
"sourcePort": "00000000-0000-0000-0000-000000000422",
|
||||||
|
"targetNode": "00000000-0000-0000-0000-000000000403",
|
||||||
|
"targetPort": "00000000-0000-0000-0000-000000000413"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"devices": {
|
||||||
|
"00000000-0000-0000-0000-000000000401": {
|
||||||
|
"ip": "10.1.0.2",
|
||||||
|
"mask": "255.255.255.0",
|
||||||
|
"gateway": "10.1.0.1"
|
||||||
|
},
|
||||||
|
"00000000-0000-0000-0000-000000000402": {
|
||||||
|
"ip": "10.1.0.1",
|
||||||
|
"mask": "255.255.255.0"
|
||||||
|
},
|
||||||
|
"00000000-0000-0000-0000-000000000403": {
|
||||||
|
"ip": "10.2.0.2",
|
||||||
|
"mask": "255.255.255.0",
|
||||||
|
"gateway": "10.2.0.1"
|
||||||
|
},
|
||||||
|
"00000000-0000-0000-0000-000000000404": {
|
||||||
|
"ip": "10.1.0.3",
|
||||||
|
"mask": "255.255.255.0",
|
||||||
|
"gateway": "10.1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"interfaces": [
|
||||||
|
{
|
||||||
|
"node": "00000000-0000-0000-0000-000000000402",
|
||||||
|
"port": "00000000-0000-0000-0000-000000000421",
|
||||||
|
"ip": "10.1.0.1",
|
||||||
|
"mask": "255.255.255.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"node": "00000000-0000-0000-0000-000000000402",
|
||||||
|
"port": "00000000-0000-0000-0000-000000000422",
|
||||||
|
"ip": "10.2.0.1",
|
||||||
|
"mask": "255.255.255.0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"firewalls": {
|
||||||
|
"00000000-0000-0000-0000-000000000402": {
|
||||||
|
"active": true,
|
||||||
|
"defaultPolicy": "accept",
|
||||||
|
"dropIcmp": true
|
||||||
|
},
|
||||||
|
"00000000-0000-0000-0000-000000000404": {
|
||||||
|
"active": true,
|
||||||
|
"defaultPolicy": "drop",
|
||||||
|
"dropIcmp": false
|
||||||
|
},
|
||||||
|
"00000000-0000-0000-0000-000000000401": {
|
||||||
|
"active": true,
|
||||||
|
"defaultPolicy": "drop",
|
||||||
|
"dropIcmp": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"op": "ping",
|
||||||
|
"from": "00000000-0000-0000-0000-000000000401",
|
||||||
|
"target": "10.2.0.2",
|
||||||
|
"identifier": 30,
|
||||||
|
"sequence": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "ping",
|
||||||
|
"from": "00000000-0000-0000-0000-000000000401",
|
||||||
|
"target": "10.1.0.3",
|
||||||
|
"identifier": 31,
|
||||||
|
"sequence": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "ping",
|
||||||
|
"from": "00000000-0000-0000-0000-000000000404",
|
||||||
|
"target": "10.1.0.2",
|
||||||
|
"identifier": 32,
|
||||||
|
"sequence": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "packetLoss",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "ping",
|
||||||
|
"from": "00000000-0000-0000-0000-000000000404",
|
||||||
|
"target": "10.1.0.1",
|
||||||
|
"identifier": 33,
|
||||||
|
"sequence": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "packetLoss",
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "ping",
|
||||||
|
"from": "00000000-0000-0000-0000-000000000404",
|
||||||
|
"target": "10.1.0.1",
|
||||||
|
"identifier": 34,
|
||||||
|
"sequence": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,134 @@
|
|||||||
|
{
|
||||||
|
"seed": 14,
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000201",
|
||||||
|
"kind": "pc",
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000211",
|
||||||
|
"label": "eth0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000202",
|
||||||
|
"kind": "router",
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000221",
|
||||||
|
"label": "rt1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000222",
|
||||||
|
"label": "rt2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000203",
|
||||||
|
"kind": "pc",
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000213",
|
||||||
|
"label": "eth0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-00000000F004",
|
||||||
|
"sourceNode": "00000000-0000-0000-0000-000000000201",
|
||||||
|
"sourcePort": "00000000-0000-0000-0000-000000000211",
|
||||||
|
"targetNode": "00000000-0000-0000-0000-000000000202",
|
||||||
|
"targetPort": "00000000-0000-0000-0000-000000000221"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-00000000F005",
|
||||||
|
"sourceNode": "00000000-0000-0000-0000-000000000202",
|
||||||
|
"sourcePort": "00000000-0000-0000-0000-000000000222",
|
||||||
|
"targetNode": "00000000-0000-0000-0000-000000000203",
|
||||||
|
"targetPort": "00000000-0000-0000-0000-000000000213"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"devices": {
|
||||||
|
"00000000-0000-0000-0000-000000000201": {
|
||||||
|
"ip": "10.0.0.2",
|
||||||
|
"mask": "255.255.255.0",
|
||||||
|
"gateway": "10.0.0.1"
|
||||||
|
},
|
||||||
|
"00000000-0000-0000-0000-000000000202": {
|
||||||
|
"ip": "10.0.0.1",
|
||||||
|
"mask": "255.255.255.0"
|
||||||
|
},
|
||||||
|
"00000000-0000-0000-0000-000000000203": {
|
||||||
|
"ip": "20.0.0.2",
|
||||||
|
"mask": "255.255.255.0",
|
||||||
|
"gateway": "20.0.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"interfaces": [
|
||||||
|
{
|
||||||
|
"node": "00000000-0000-0000-0000-000000000202",
|
||||||
|
"port": "00000000-0000-0000-0000-000000000221",
|
||||||
|
"ip": "10.0.0.1",
|
||||||
|
"mask": "255.255.255.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"node": "00000000-0000-0000-0000-000000000202",
|
||||||
|
"port": "00000000-0000-0000-0000-000000000222",
|
||||||
|
"ip": "20.0.0.1",
|
||||||
|
"mask": "255.255.255.0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"op": "ping",
|
||||||
|
"from": "00000000-0000-0000-0000-000000000201",
|
||||||
|
"target": "20.0.0.2",
|
||||||
|
"identifier": 10,
|
||||||
|
"sequence": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "ping",
|
||||||
|
"from": "00000000-0000-0000-0000-000000000201",
|
||||||
|
"target": "20.0.0.2",
|
||||||
|
"ttl": 1,
|
||||||
|
"identifier": 11,
|
||||||
|
"sequence": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "traceroute",
|
||||||
|
"from": "00000000-0000-0000-0000-000000000201",
|
||||||
|
"target": "20.0.0.2",
|
||||||
|
"identifier": 12
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "ping",
|
||||||
|
"from": "00000000-0000-0000-0000-000000000201",
|
||||||
|
"target": "30.0.0.2",
|
||||||
|
"identifier": 13,
|
||||||
|
"sequence": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "ping",
|
||||||
|
"from": "00000000-0000-0000-0000-000000000201",
|
||||||
|
"target": "20.0.0.99",
|
||||||
|
"identifier": 14,
|
||||||
|
"sequence": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "ping",
|
||||||
|
"from": "00000000-0000-0000-0000-000000000203",
|
||||||
|
"target": "10.0.0.1",
|
||||||
|
"identifier": 15,
|
||||||
|
"sequence": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "resolveMac",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000203",
|
||||||
|
"target": "20.0.0.1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,195 @@
|
|||||||
|
{
|
||||||
|
"seed": 3,
|
||||||
|
"speedPercent": 50,
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000301",
|
||||||
|
"kind": "pc",
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000311",
|
||||||
|
"label": "eth0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000302",
|
||||||
|
"kind": "router",
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000321",
|
||||||
|
"label": "rt1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000322",
|
||||||
|
"label": "rt2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000303",
|
||||||
|
"kind": "router",
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000331",
|
||||||
|
"label": "rt1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000332",
|
||||||
|
"label": "rt2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000304",
|
||||||
|
"kind": "pc",
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000314",
|
||||||
|
"label": "eth0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000305",
|
||||||
|
"kind": "pc",
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000315",
|
||||||
|
"label": "eth0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-00000000F006",
|
||||||
|
"sourceNode": "00000000-0000-0000-0000-000000000301",
|
||||||
|
"sourcePort": "00000000-0000-0000-0000-000000000311",
|
||||||
|
"targetNode": "00000000-0000-0000-0000-000000000302",
|
||||||
|
"targetPort": "00000000-0000-0000-0000-000000000321"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-00000000F007",
|
||||||
|
"sourceNode": "00000000-0000-0000-0000-000000000302",
|
||||||
|
"sourcePort": "00000000-0000-0000-0000-000000000322",
|
||||||
|
"targetNode": "00000000-0000-0000-0000-000000000303",
|
||||||
|
"targetPort": "00000000-0000-0000-0000-000000000331"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-00000000F008",
|
||||||
|
"sourceNode": "00000000-0000-0000-0000-000000000303",
|
||||||
|
"sourcePort": "00000000-0000-0000-0000-000000000332",
|
||||||
|
"targetNode": "00000000-0000-0000-0000-000000000304",
|
||||||
|
"targetPort": "00000000-0000-0000-0000-000000000314"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-00000000F009",
|
||||||
|
"sourceNode": "00000000-0000-0000-0000-000000000305",
|
||||||
|
"sourcePort": "00000000-0000-0000-0000-000000000315",
|
||||||
|
"targetNode": "00000000-0000-0000-0000-000000000303",
|
||||||
|
"targetPort": "00000000-0000-0000-0000-000000000332"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"devices": {
|
||||||
|
"00000000-0000-0000-0000-000000000301": {
|
||||||
|
"ip": "192.168.1.10",
|
||||||
|
"mask": "255.255.255.0",
|
||||||
|
"gateway": "192.168.1.1"
|
||||||
|
},
|
||||||
|
"00000000-0000-0000-0000-000000000302": {
|
||||||
|
"ip": "192.168.1.1",
|
||||||
|
"mask": "255.255.255.0"
|
||||||
|
},
|
||||||
|
"00000000-0000-0000-0000-000000000303": {
|
||||||
|
"ip": "10.10.10.2",
|
||||||
|
"mask": "255.255.255.0",
|
||||||
|
"gateway": "10.10.10.1"
|
||||||
|
},
|
||||||
|
"00000000-0000-0000-0000-000000000304": {
|
||||||
|
"ip": "172.16.0.10",
|
||||||
|
"mask": "255.255.0.0",
|
||||||
|
"gateway": "172.16.0.1"
|
||||||
|
},
|
||||||
|
"00000000-0000-0000-0000-000000000305": {
|
||||||
|
"ip": "172.16.0.20",
|
||||||
|
"mask": "255.255.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"interfaces": [
|
||||||
|
{
|
||||||
|
"node": "00000000-0000-0000-0000-000000000302",
|
||||||
|
"port": "00000000-0000-0000-0000-000000000321",
|
||||||
|
"ip": "192.168.1.1",
|
||||||
|
"mask": "255.255.255.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"node": "00000000-0000-0000-0000-000000000302",
|
||||||
|
"port": "00000000-0000-0000-0000-000000000322",
|
||||||
|
"ip": "10.10.10.1",
|
||||||
|
"mask": "255.255.255.252"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"node": "00000000-0000-0000-0000-000000000303",
|
||||||
|
"port": "00000000-0000-0000-0000-000000000331",
|
||||||
|
"ip": "10.10.10.2",
|
||||||
|
"mask": "255.255.255.252"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"node": "00000000-0000-0000-0000-000000000303",
|
||||||
|
"port": "00000000-0000-0000-0000-000000000332",
|
||||||
|
"ip": "172.16.0.1",
|
||||||
|
"mask": "255.255.0.0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"routes": {
|
||||||
|
"00000000-0000-0000-0000-000000000302": [
|
||||||
|
{
|
||||||
|
"destination": "172.16.0.0",
|
||||||
|
"mask": "255.255.0.0",
|
||||||
|
"gateway": "10.10.10.2",
|
||||||
|
"interface": "10.10.10.1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"op": "ping",
|
||||||
|
"from": "00000000-0000-0000-0000-000000000301",
|
||||||
|
"target": "172.16.0.10",
|
||||||
|
"identifier": 20,
|
||||||
|
"sequence": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "traceroute",
|
||||||
|
"from": "00000000-0000-0000-0000-000000000301",
|
||||||
|
"target": "172.16.0.10",
|
||||||
|
"identifier": 21
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "ping",
|
||||||
|
"from": "00000000-0000-0000-0000-000000000305",
|
||||||
|
"target": "192.168.1.10",
|
||||||
|
"identifier": 22,
|
||||||
|
"sequence": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "speed",
|
||||||
|
"percent": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "ping",
|
||||||
|
"from": "00000000-0000-0000-0000-000000000301",
|
||||||
|
"target": "172.16.0.10",
|
||||||
|
"identifier": 23,
|
||||||
|
"sequence": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "ping",
|
||||||
|
"from": "00000000-0000-0000-0000-000000000301",
|
||||||
|
"target": "10.10.10.2",
|
||||||
|
"identifier": 24,
|
||||||
|
"sequence": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,568 @@
|
|||||||
|
{
|
||||||
|
"seed": 5,
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000501",
|
||||||
|
"kind": "pc",
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000511",
|
||||||
|
"label": "eth0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000502",
|
||||||
|
"kind": "notebook",
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000512",
|
||||||
|
"label": "eth0",
|
||||||
|
"mac": "0a:1b:2c:3d:4e:5f"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000503",
|
||||||
|
"kind": "networkSwitch",
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000521",
|
||||||
|
"label": "sw1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000522",
|
||||||
|
"label": "sw2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-00000000F501",
|
||||||
|
"sourceNode": "00000000-0000-0000-0000-000000000501",
|
||||||
|
"sourcePort": "00000000-0000-0000-0000-000000000511",
|
||||||
|
"targetNode": "00000000-0000-0000-0000-000000000503",
|
||||||
|
"targetPort": "00000000-0000-0000-0000-000000000521"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-00000000F502",
|
||||||
|
"sourceNode": "00000000-0000-0000-0000-000000000503",
|
||||||
|
"sourcePort": "00000000-0000-0000-0000-000000000522",
|
||||||
|
"targetNode": "00000000-0000-0000-0000-000000000502",
|
||||||
|
"targetPort": "00000000-0000-0000-0000-000000000512"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"devices": {
|
||||||
|
"00000000-0000-0000-0000-000000000501": {
|
||||||
|
"ip": "10.5.0.1",
|
||||||
|
"mask": "255.255.255.0"
|
||||||
|
},
|
||||||
|
"00000000-0000-0000-0000-000000000502": {
|
||||||
|
"ip": "10.5.0.2",
|
||||||
|
"mask": "255.255.255.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"switches": {
|
||||||
|
"00000000-0000-0000-0000-000000000503": {
|
||||||
|
"ssid": "age",
|
||||||
|
"retention": 1000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"op": "ping",
|
||||||
|
"from": "00000000-0000-0000-0000-000000000501",
|
||||||
|
"target": "10.5.0.2",
|
||||||
|
"identifier": 50,
|
||||||
|
"sequence": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1005
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1010
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1015
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1020
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1025
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1030
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1035
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1040
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1045
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1050
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1055
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1060
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1065
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1070
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1075
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1080
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1085
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1090
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1095
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1105
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1110
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1115
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1130
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1135
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1140
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1145
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1155
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1160
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1165
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1170
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1175
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1180
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1185
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1190
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1195
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1205
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1210
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1215
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1220
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1225
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1230
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1235
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1240
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1245
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1250
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1255
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1260
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1265
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1270
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1275
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1280
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1285
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1290
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 1295
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "sat",
|
||||||
|
"node": "00000000-0000-0000-0000-000000000503"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "ping",
|
||||||
|
"from": "00000000-0000-0000-0000-000000000502",
|
||||||
|
"target": "10.5.0.1",
|
||||||
|
"identifier": 51,
|
||||||
|
"sequence": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,155 @@
|
|||||||
|
{
|
||||||
|
"seed": 14,
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000101",
|
||||||
|
"kind": "pc",
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000111",
|
||||||
|
"label": "eth0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000102",
|
||||||
|
"kind": "pc",
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000112",
|
||||||
|
"label": "eth0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000103",
|
||||||
|
"kind": "pc",
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000113",
|
||||||
|
"label": "eth0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000104",
|
||||||
|
"kind": "networkSwitch",
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000121",
|
||||||
|
"label": "sw1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000122",
|
||||||
|
"label": "sw2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000123",
|
||||||
|
"label": "sw3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000124",
|
||||||
|
"label": "sw4"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-00000000F001",
|
||||||
|
"sourceNode": "00000000-0000-0000-0000-000000000101",
|
||||||
|
"sourcePort": "00000000-0000-0000-0000-000000000111",
|
||||||
|
"targetNode": "00000000-0000-0000-0000-000000000104",
|
||||||
|
"targetPort": "00000000-0000-0000-0000-000000000121"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-00000000F002",
|
||||||
|
"sourceNode": "00000000-0000-0000-0000-000000000102",
|
||||||
|
"sourcePort": "00000000-0000-0000-0000-000000000112",
|
||||||
|
"targetNode": "00000000-0000-0000-0000-000000000104",
|
||||||
|
"targetPort": "00000000-0000-0000-0000-000000000122"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-00000000F003",
|
||||||
|
"sourceNode": "00000000-0000-0000-0000-000000000103",
|
||||||
|
"sourcePort": "00000000-0000-0000-0000-000000000113",
|
||||||
|
"targetNode": "00000000-0000-0000-0000-000000000104",
|
||||||
|
"targetPort": "00000000-0000-0000-0000-000000000123"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"devices": {
|
||||||
|
"00000000-0000-0000-0000-000000000101": {
|
||||||
|
"ip": "192.168.0.10",
|
||||||
|
"mask": "255.255.255.0"
|
||||||
|
},
|
||||||
|
"00000000-0000-0000-0000-000000000102": {
|
||||||
|
"ip": "192.168.0.20",
|
||||||
|
"mask": "255.255.255.0"
|
||||||
|
},
|
||||||
|
"00000000-0000-0000-0000-000000000103": {
|
||||||
|
"ip": "192.168.0.30",
|
||||||
|
"mask": "255.255.255.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"switches": {
|
||||||
|
"00000000-0000-0000-0000-000000000104": {
|
||||||
|
"ssid": "lab",
|
||||||
|
"retention": 5000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"op": "ping",
|
||||||
|
"from": "00000000-0000-0000-0000-000000000101",
|
||||||
|
"target": "192.168.0.20",
|
||||||
|
"identifier": 1,
|
||||||
|
"sequence": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "ping",
|
||||||
|
"from": "00000000-0000-0000-0000-000000000101",
|
||||||
|
"target": "192.168.0.20",
|
||||||
|
"identifier": 1,
|
||||||
|
"sequence": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "ping",
|
||||||
|
"from": "00000000-0000-0000-0000-000000000103",
|
||||||
|
"target": "192.168.0.10",
|
||||||
|
"identifier": 7,
|
||||||
|
"sequence": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "ping",
|
||||||
|
"from": "00000000-0000-0000-0000-000000000102",
|
||||||
|
"target": "192.168.0.99",
|
||||||
|
"identifier": 9,
|
||||||
|
"sequence": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "ping",
|
||||||
|
"from": "00000000-0000-0000-0000-000000000101",
|
||||||
|
"target": "192.168.0.10",
|
||||||
|
"identifier": 2,
|
||||||
|
"sequence": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "ping",
|
||||||
|
"from": "00000000-0000-0000-0000-000000000101",
|
||||||
|
"target": "127.0.0.1",
|
||||||
|
"identifier": 3,
|
||||||
|
"sequence": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "advance",
|
||||||
|
"to": 20000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "ping",
|
||||||
|
"from": "00000000-0000-0000-0000-000000000102",
|
||||||
|
"target": "192.168.0.30",
|
||||||
|
"identifier": 4,
|
||||||
|
"sequence": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"noEmit": true,
|
||||||
|
"composite": false,
|
||||||
|
"declaration": false,
|
||||||
|
"types": ["node"]
|
||||||
|
},
|
||||||
|
"include": ["."]
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../tsconfig.base.json",
|
"extends": "../../tsconfig.base.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"rootDir": ".",
|
"rootDir": "src",
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"types": []
|
"types": []
|
||||||
},
|
},
|
||||||
"include": ["src", "test"]
|
"include": ["src"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
.upstream/
|
||||||
|
.build/
|
||||||
|
Sources/FiliusCore/
|
||||||
|
Package.resolved
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// swift-tools-version:5.9
|
||||||
|
// The filius-ipad core (runtime, model, persistence, reports) built on Linux as
|
||||||
|
// a reference for the TypeScript port. Sources/FiliusCore is filled by
|
||||||
|
// fetch-upstream.sh from a pinned commit; nothing of it is checked in here.
|
||||||
|
import PackageDescription
|
||||||
|
|
||||||
|
let package = Package(
|
||||||
|
name: "FiliusOracle",
|
||||||
|
dependencies: [.package(url: "https://github.com/apple/swift-crypto.git", from: "3.0.0")],
|
||||||
|
targets: [
|
||||||
|
// Apple-only modules the core imports, reduced to what it uses.
|
||||||
|
.target(name: "CoreGraphics"),
|
||||||
|
.target(name: "CryptoKit", dependencies: [.product(name: "Crypto", package: "swift-crypto")]),
|
||||||
|
.systemLibrary(name: "CZlib"),
|
||||||
|
.target(name: "Compression", dependencies: ["CZlib"]),
|
||||||
|
.target(
|
||||||
|
name: "FiliusCore",
|
||||||
|
dependencies: ["CoreGraphics", "CryptoKit", "Compression"],
|
||||||
|
swiftSettings: [.unsafeFlags(["-enable-testing"])]
|
||||||
|
),
|
||||||
|
.executableTarget(name: "Oracle", dependencies: ["FiliusCore"]),
|
||||||
|
]
|
||||||
|
)
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# Swift-Orakel
|
||||||
|
|
||||||
|
Der Swift-Kern von `filius-ipad` (Laufzeit, Modell, `.fls`, Berichte — alles
|
||||||
|
ohne Oberfläche) lässt sich unter Linux bauen. Dieses Werkzeug nutzt das als
|
||||||
|
**Maßstab für die Übertragung**: Ein Szenario wird einmal als JSON
|
||||||
|
beschrieben, in Swift abgespielt, und die TypeScript-Übertragung muss
|
||||||
|
dieselbe Ausgabe liefern — jedes einzelne Spurereignis, jede Antwort, den
|
||||||
|
Endzustand.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run oracle # = tools/swift-oracle/run.sh (braucht Docker)
|
||||||
|
npm test # vergleicht mit den geschriebenen *.swift.json
|
||||||
|
```
|
||||||
|
|
||||||
|
* Szenarien: `packages/core/test/scenarios/<name>.scenario.json`
|
||||||
|
* Ergebnis aus Swift: `<name>.swift.json` daneben — **wird eingecheckt**, damit
|
||||||
|
die Tests ohne Docker und ohne Swift laufen.
|
||||||
|
* Vergleich: `packages/core/test/oracle.test.ts`. Er meldet das **erste**
|
||||||
|
abweichende Spurereignis mit Nummer.
|
||||||
|
|
||||||
|
## Wie es gebaut wird
|
||||||
|
|
||||||
|
`fetch-upstream.sh` holt `filius-ipad` am festen Stand `3b5ded0` und kopiert
|
||||||
|
die oberflächenfreien Quellen nach `Sources/FiliusCore/` (nicht eingecheckt).
|
||||||
|
Apple-Module, die es unter Linux nicht gibt, sind auf das Nötige verkleinert:
|
||||||
|
|
||||||
|
| Modul | Ersatz |
|
||||||
|
| --- | --- |
|
||||||
|
| `CoreGraphics` | `Foundation` (hat `CGPoint`/`CGFloat` unter Linux) |
|
||||||
|
| `CryptoKit` | `swift-crypto` (gleiche Schnittstelle) |
|
||||||
|
| `Compression` | eine Funktion, `compression_decode_buffer` mit rohem DEFLATE über zlib |
|
||||||
|
| `Network` | entfällt: `TopologyRemoteLinkLANController.swift` wird nicht mitgebaut |
|
||||||
|
|
||||||
|
`patches/linux-type-checker.patch` teilt zwei lange Ausdrücke auf, an denen der
|
||||||
|
Linux-Compiler die Typprüfung abbricht. Das Verhalten ändert sich dadurch nicht.
|
||||||
|
|
||||||
|
## Neue Schrittarten
|
||||||
|
|
||||||
|
Eine Schrittart gibt es zweimal — in `Sources/Oracle/main.swift` und in
|
||||||
|
`packages/core/test/oracle.test.ts`. Beide müssen dasselbe JSON schreiben.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
module CZlib [system] {
|
||||||
|
header "shim.h"
|
||||||
|
link "z"
|
||||||
|
export *
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
#include <zlib.h>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// Linux stand-in for the one call filius-ipad makes into Apple's Compression
|
||||||
|
// framework: COMPRESSION_ZLIB is raw DEFLATE (RFC 1951), decoded here by zlib.
|
||||||
|
import CZlib
|
||||||
|
|
||||||
|
public struct compression_algorithm: Equatable { let rawValue: Int32 }
|
||||||
|
public let COMPRESSION_ZLIB = compression_algorithm(rawValue: 0x205)
|
||||||
|
|
||||||
|
public func compression_decode_buffer(
|
||||||
|
_ dst: UnsafeMutablePointer<UInt8>, _ dstSize: Int,
|
||||||
|
_ src: UnsafePointer<UInt8>, _ srcSize: Int,
|
||||||
|
_ scratch: UnsafeMutableRawPointer?, _ algorithm: compression_algorithm
|
||||||
|
) -> Int {
|
||||||
|
var stream = z_stream()
|
||||||
|
guard inflateInit2_(&stream, -15, ZLIB_VERSION, Int32(MemoryLayout<z_stream>.size)) == Z_OK else { return 0 }
|
||||||
|
defer { inflateEnd(&stream) }
|
||||||
|
stream.next_in = UnsafeMutablePointer(mutating: src)
|
||||||
|
stream.avail_in = uInt(srcSize)
|
||||||
|
stream.next_out = dst
|
||||||
|
stream.avail_out = uInt(dstSize)
|
||||||
|
let status = inflate(&stream, Z_FINISH)
|
||||||
|
guard status == Z_STREAM_END || status == Z_OK || status == Z_BUF_ERROR else { return 0 }
|
||||||
|
return Int(stream.total_out)
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
@_exported import Foundation
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
@_exported import Crypto
|
||||||
@@ -0,0 +1,273 @@
|
|||||||
|
// Replays scenario files in the filius-ipad runtime engine and writes what
|
||||||
|
// happened: every trace event, the result of every step, and the end state.
|
||||||
|
// The TypeScript port replays the same files (packages/core/test/oracle.test.ts)
|
||||||
|
// and must produce the same output.
|
||||||
|
//
|
||||||
|
// Usage: Oracle <directory> — reads <name>.scenario.json, writes <name>.swift.json
|
||||||
|
|
||||||
|
@testable import FiliusCore
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
// MARK: - Scenario format (shared with packages/core/test/oracle/scenario.ts)
|
||||||
|
|
||||||
|
struct ScenarioPort: Decodable { let id: String; let label: String; let mac: String? }
|
||||||
|
struct ScenarioNode: Decodable { let id: String; let kind: String; let ports: [ScenarioPort] }
|
||||||
|
struct ScenarioLink: Decodable {
|
||||||
|
let id: String
|
||||||
|
let sourceNode: String; let sourcePort: String
|
||||||
|
let targetNode: String; let targetPort: String
|
||||||
|
}
|
||||||
|
struct ScenarioDevice: Decodable { let ip: String; let mask: String; let gateway: String?; let dns: String? }
|
||||||
|
struct ScenarioInterface: Decodable { let node: String; let port: String; let ip: String; let mask: String }
|
||||||
|
struct ScenarioRoute: Decodable { let destination: String; let mask: String; let gateway: String; let interface: String }
|
||||||
|
struct ScenarioSwitch: Decodable { let ssid: String; let retention: UInt64? }
|
||||||
|
struct ScenarioFirewallRule: Decodable {
|
||||||
|
let source: String?; let sourceMask: String?
|
||||||
|
let destination: String?; let destinationMask: String?
|
||||||
|
let port: Int?; let `protocol`: String?; let action: String?
|
||||||
|
}
|
||||||
|
struct ScenarioFirewall: Decodable {
|
||||||
|
let active: Bool?; let defaultPolicy: String?; let dropIcmp: Bool?
|
||||||
|
let synOnly: Bool?; let filterUdp: Bool?; let rules: [ScenarioFirewallRule]?
|
||||||
|
}
|
||||||
|
struct ScenarioStep: Decodable {
|
||||||
|
let op: String
|
||||||
|
let from: String?; let node: String?; let target: String?
|
||||||
|
let ttl: UInt8?; let identifier: UInt16?; let sequence: UInt16?; let maxTtl: UInt8?
|
||||||
|
let to: UInt64?; let enabled: Bool?; let percent: Int?
|
||||||
|
}
|
||||||
|
struct Scenario: Decodable {
|
||||||
|
let seed: UInt64
|
||||||
|
let speedPercent: Int?
|
||||||
|
let nodes: [ScenarioNode]
|
||||||
|
let links: [ScenarioLink]
|
||||||
|
let devices: [String: ScenarioDevice]?
|
||||||
|
let interfaces: [ScenarioInterface]?
|
||||||
|
let routes: [String: [ScenarioRoute]]?
|
||||||
|
let switches: [String: ScenarioSwitch]?
|
||||||
|
let firewalls: [String: ScenarioFirewall]?
|
||||||
|
let steps: [ScenarioStep]
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Building the snapshot
|
||||||
|
|
||||||
|
func uuid(_ value: String) -> UUID {
|
||||||
|
guard let id = UUID(uuidString: value) else { fatalError("not a UUID: \(value)") }
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
func firewallAction(_ value: String?) -> TopologyFirewallAction {
|
||||||
|
value == "accept" ? .accept : .drop
|
||||||
|
}
|
||||||
|
|
||||||
|
func firewallProtocol(_ value: String?) -> TopologyFirewallProtocol {
|
||||||
|
switch value {
|
||||||
|
case "all": return .all
|
||||||
|
case "icmp": return .icmp
|
||||||
|
case "udp": return .udp
|
||||||
|
default: return .tcp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func snapshot(of scenario: Scenario) -> TopologyNetworkRuntimeTopologySnapshot {
|
||||||
|
let nodes = scenario.nodes.map { node -> TopologyNetworkRuntimeNodeSnapshot in
|
||||||
|
guard let kind = TopologyNodeKind(rawValue: node.kind) else { fatalError("unknown kind \(node.kind)") }
|
||||||
|
return TopologyNetworkRuntimeNodeSnapshot(
|
||||||
|
id: uuid(node.id),
|
||||||
|
kind: kind,
|
||||||
|
ports: node.ports.map { TopologyNetworkRuntimePortSnapshot(id: uuid($0.id), label: $0.label, macAddress: $0.mac) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
let links = scenario.links.map {
|
||||||
|
TopologyNetworkRuntimeLinkSnapshot(
|
||||||
|
id: uuid($0.id),
|
||||||
|
sourceNodeID: uuid($0.sourceNode), sourcePortID: uuid($0.sourcePort),
|
||||||
|
targetNodeID: uuid($0.targetNode), targetPortID: uuid($0.targetPort)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
var devices: [UUID: TopologyRuntimeDeviceConfiguration] = [:]
|
||||||
|
for (node, device) in scenario.devices ?? [:] {
|
||||||
|
devices[uuid(node)] = TopologyRuntimeDeviceConfiguration(
|
||||||
|
ipAddress: device.ip, subnetMask: device.mask,
|
||||||
|
defaultGateway: device.gateway ?? "", dnsServer: device.dns ?? ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
var interfaces: [TopologyRuntimeInterfaceKey: TopologyRuntimeInterfaceConfiguration] = [:]
|
||||||
|
for item in scenario.interfaces ?? [] {
|
||||||
|
interfaces[TopologyRuntimeInterfaceKey(nodeID: uuid(item.node), portID: uuid(item.port))] =
|
||||||
|
TopologyRuntimeInterfaceConfiguration(ipAddress: item.ip, subnetMask: item.mask)
|
||||||
|
}
|
||||||
|
var routes: [UUID: [TopologyRuntimeManualRoute]] = [:]
|
||||||
|
for (node, rows) in scenario.routes ?? [:] {
|
||||||
|
routes[uuid(node)] = rows.map {
|
||||||
|
TopologyRuntimeManualRoute(
|
||||||
|
destinationNetwork: $0.destination, subnetMask: $0.mask,
|
||||||
|
gateway: $0.gateway, interfaceIPAddress: $0.interface
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var switches: [UUID: TopologySwitchConfiguration] = [:]
|
||||||
|
for (node, config) in scenario.switches ?? [:] {
|
||||||
|
switches[uuid(node)] = TopologySwitchConfiguration(
|
||||||
|
ssid: config.ssid,
|
||||||
|
retentionTimeMilliseconds: config.retention ?? TopologySwitchConfiguration.defaultRetentionTimeMilliseconds
|
||||||
|
)
|
||||||
|
}
|
||||||
|
var firewalls: [UUID: TopologyFirewallConfiguration] = [:]
|
||||||
|
for (node, config) in scenario.firewalls ?? [:] {
|
||||||
|
firewalls[uuid(node)] = TopologyFirewallConfiguration(
|
||||||
|
isActive: config.active ?? false,
|
||||||
|
defaultPolicy: firewallAction(config.defaultPolicy),
|
||||||
|
dropICMP: config.dropIcmp ?? false,
|
||||||
|
filterSYNSegmentsOnly: config.synOnly ?? true,
|
||||||
|
filterUDP: config.filterUdp ?? true,
|
||||||
|
rules: (config.rules ?? []).map {
|
||||||
|
TopologyFirewallRule(
|
||||||
|
sourceIPAddress: $0.source ?? "", sourceSubnetMask: $0.sourceMask ?? "",
|
||||||
|
destinationIPAddress: $0.destination ?? "", destinationSubnetMask: $0.destinationMask ?? "",
|
||||||
|
port: $0.port ?? TopologyFirewallRule.allPorts,
|
||||||
|
protocolType: firewallProtocol($0.protocol),
|
||||||
|
action: firewallAction($0.action)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return TopologyNetworkRuntimeTopologySnapshot(
|
||||||
|
nodes: nodes, links: links,
|
||||||
|
deviceConfigurations: devices, interfaceConfigurations: interfaces,
|
||||||
|
manualRoutesByNodeID: routes,
|
||||||
|
firewallConfigurationsByNodeID: firewalls,
|
||||||
|
switchConfigurationsByNodeID: switches
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Output
|
||||||
|
|
||||||
|
typealias JSON = [String: Any]
|
||||||
|
|
||||||
|
func headers(_ fields: [TopologyPacketHeaderField]) -> [[String]] {
|
||||||
|
fields.map { [$0.name, $0.value] }
|
||||||
|
}
|
||||||
|
|
||||||
|
func trace(_ event: TopologyPacketTraceEvent) -> JSON {
|
||||||
|
var result: JSON = [
|
||||||
|
"id": event.id,
|
||||||
|
"time": event.timeMilliseconds,
|
||||||
|
"node": event.nodeID.uuidString,
|
||||||
|
"direction": event.direction.rawValue,
|
||||||
|
"layer": event.layer.rawValue,
|
||||||
|
"operation": event.operation.rawValue,
|
||||||
|
"before": headers(event.beforeHeaders),
|
||||||
|
"after": headers(event.afterHeaders),
|
||||||
|
]
|
||||||
|
if let frame = event.frameIdentity { result["frame"] = frame }
|
||||||
|
if let packet = event.packetIdentity { result["packet"] = packet }
|
||||||
|
if let interface = event.interfaceID { result["interface"] = interface.uuidString }
|
||||||
|
if let detail = event.detail { result["detail"] = detail }
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func delivery(_ result: TopologyIPv4DeliveryResult) -> JSON {
|
||||||
|
switch result {
|
||||||
|
case let .delivered(packet, node): return ["type": "delivered", "packet": packet, "node": node.uuidString]
|
||||||
|
case let .icmpError(packet, node, kind):
|
||||||
|
return ["type": "icmpError", "packet": packet, "node": node.uuidString, "kind": kind.rawValue]
|
||||||
|
case let .dropped(packet, node):
|
||||||
|
var json: JSON = ["type": "dropped", "packet": packet]
|
||||||
|
if let node { json["node"] = node.uuidString }
|
||||||
|
return json
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func observation(_ item: TopologyRuntimeICMPObservation) -> JSON {
|
||||||
|
[
|
||||||
|
"kind": item.message.kind.rawValue,
|
||||||
|
"identifier": item.message.identifier,
|
||||||
|
"sequence": item.message.sequenceNumber,
|
||||||
|
"sender": item.packet.senderIPAddress,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(_ scenario: Scenario) -> JSON {
|
||||||
|
var engine = TopologyNetworkRuntimeEngine(seed: scenario.seed)
|
||||||
|
if let percent = scenario.speedPercent { engine.setSimulationSpeed(percent: percent) }
|
||||||
|
engine.handle(.start(snapshot: snapshot(of: scenario), seed: scenario.seed, initialTimeMilliseconds: 0))
|
||||||
|
|
||||||
|
var results: [Any] = []
|
||||||
|
for step in scenario.steps {
|
||||||
|
switch step.op {
|
||||||
|
case "ping":
|
||||||
|
results.append(delivery(engine.sendICMPEcho(
|
||||||
|
fromNodeID: uuid(step.from!), targetIPAddress: step.target!,
|
||||||
|
timeToLive: step.ttl ?? 64, identifier: step.identifier ?? 1, sequenceNumber: step.sequence ?? 1
|
||||||
|
)))
|
||||||
|
case "traceroute":
|
||||||
|
results.append(engine.traceICMPEcho(
|
||||||
|
fromNodeID: uuid(step.from!), targetIPAddress: step.target!,
|
||||||
|
maximumTimeToLive: step.maxTtl ?? 64, identifier: step.identifier ?? 1
|
||||||
|
).map(observation))
|
||||||
|
case "resolveMac":
|
||||||
|
results.append(engine.resolveMACAddress(nodeID: uuid(step.node!), targetIPAddress: step.target!) ?? NSNull())
|
||||||
|
case "advance":
|
||||||
|
engine.handle(.advance(toMilliseconds: step.to!))
|
||||||
|
results.append(engine.state.currentTimeMilliseconds)
|
||||||
|
case "packetLoss":
|
||||||
|
engine.setGlobalPacketLossEnabled(step.enabled!)
|
||||||
|
results.append(engine.state.globalPacketLossEnabled)
|
||||||
|
case "sat":
|
||||||
|
results.append(engine.switchSATEntries(nodeID: uuid(step.node!)).map {
|
||||||
|
["mac": $0.macAddress, "port": $0.portLabel, "updated": $0.updatedAtMilliseconds] as JSON
|
||||||
|
})
|
||||||
|
case "speed":
|
||||||
|
engine.setSimulationSpeed(percent: step.percent!)
|
||||||
|
results.append(engine.simulationSpeed.percent)
|
||||||
|
default:
|
||||||
|
fatalError("unknown step \(step.op)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let state = engine.state
|
||||||
|
var arp: JSON = [:]
|
||||||
|
for (node, cache) in state.arpCachesByNodeID {
|
||||||
|
arp[node.uuidString] = cache.mapValues { $0.macAddress }
|
||||||
|
}
|
||||||
|
var switchTables: JSON = [:]
|
||||||
|
for (node, table) in state.switchForwardingTablesByNodeID {
|
||||||
|
switchTables[node.uuidString] = table.mapValues { $0.uuidString }
|
||||||
|
}
|
||||||
|
var observations: JSON = [:]
|
||||||
|
for (node, items) in state.icmpObservationsByNodeID {
|
||||||
|
observations[node.uuidString] = items.map(observation)
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
"results": results,
|
||||||
|
"traces": state.packetTraces.map(trace),
|
||||||
|
"time": state.currentTimeMilliseconds,
|
||||||
|
"pendingEvents": state.pendingEvents.count,
|
||||||
|
"arp": arp,
|
||||||
|
"switchTables": switchTables,
|
||||||
|
"icmpObservations": observations,
|
||||||
|
"firewallDecisions": state.firewallDecisions.map { decision -> JSON in
|
||||||
|
var json: JSON = [
|
||||||
|
"packet": decision.packetIdentity, "node": decision.nodeID.uuidString, "accepted": decision.accepted,
|
||||||
|
]
|
||||||
|
if let rule = decision.ruleIndex { json["rule"] = rule }
|
||||||
|
return json
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Main
|
||||||
|
|
||||||
|
let directory = URL(fileURLWithPath: CommandLine.arguments.dropFirst().first ?? ".")
|
||||||
|
let files = try FileManager.default.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil)
|
||||||
|
.filter { $0.lastPathComponent.hasSuffix(".scenario.json") }
|
||||||
|
.sorted { $0.lastPathComponent < $1.lastPathComponent }
|
||||||
|
for file in files {
|
||||||
|
let scenario = try JSONDecoder().decode(Scenario.self, from: Data(contentsOf: file))
|
||||||
|
let output = run(scenario)
|
||||||
|
let data = try JSONSerialization.data(withJSONObject: output, options: [.prettyPrinted, .sortedKeys])
|
||||||
|
let name = file.lastPathComponent.replacingOccurrences(of: ".scenario.json", with: ".swift.json")
|
||||||
|
try (data + Data("\n".utf8)).write(to: directory.appendingPathComponent(name))
|
||||||
|
print("\(name): \((output["traces"] as? [Any])?.count ?? 0) trace events")
|
||||||
|
}
|
||||||
Executable
+26
@@ -0,0 +1,26 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Copies the UI-free Swift sources of filius-ipad, at the commit the port
|
||||||
|
# follows, into Sources/FiliusCore and applies the Linux patches.
|
||||||
|
set -eu
|
||||||
|
here="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
commit=3b5ded0
|
||||||
|
upstream="$here/.upstream"
|
||||||
|
if [ ! -d "$upstream/.git" ]; then
|
||||||
|
git clone -q https://github.com/filius-project/filius-ipad "$upstream"
|
||||||
|
fi
|
||||||
|
git -C "$upstream" fetch -q origin
|
||||||
|
git -C "$upstream" checkout -q "$commit"
|
||||||
|
|
||||||
|
core="$here/Sources/FiliusCore"
|
||||||
|
rm -rf "$core"
|
||||||
|
mkdir -p "$core"
|
||||||
|
editor="$upstream/ios/FiliusPad/TopologyEditor"
|
||||||
|
cp "$editor"/State/*.swift "$editor"/State/Reports/*.swift "$editor"/Model/*.swift \
|
||||||
|
"$editor"/Persistence/*.swift "$upstream/ios/FiliusPad/Localization/FiliusLocalization.swift" "$core/"
|
||||||
|
# Apple's Network framework has no Linux counterpart; the LAN transport of the
|
||||||
|
# remote link is not needed to replay a simulation.
|
||||||
|
rm "$core/TopologyRemoteLinkLANController.swift"
|
||||||
|
for patch in "$here"/patches/*.patch; do
|
||||||
|
(cd "$core" && patch -p1 --forward --quiet < "$patch")
|
||||||
|
done
|
||||||
|
echo "filius-ipad $commit → $core ($(ls "$core" | wc -l) files)"
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
diff --git a/TopologyPacketCaptureTextExport.swift b/TopologyPacketCaptureTextExport.swift
|
||||||
|
index fcdb43a..c9ed472 100644
|
||||||
|
--- a/TopologyPacketCaptureTextExport.swift
|
||||||
|
+++ b/TopologyPacketCaptureTextExport.swift
|
||||||
|
@@ -245,26 +245,26 @@ enum TopologyPacketCaptureTextExportFormatter {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func renderRecord(_ record: TopologyPacketCaptureExportRecord) -> String {
|
||||||
|
- [
|
||||||
|
- String(record.number),
|
||||||
|
- String(record.timeMilliseconds),
|
||||||
|
- String(record.traceID),
|
||||||
|
- record.frameID.map(String.init) ?? "",
|
||||||
|
- record.packetID.map(String.init) ?? "",
|
||||||
|
- normalizedUUID(record.nodeID),
|
||||||
|
- record.nodeName,
|
||||||
|
- record.interfaceID.map(normalizedUUID) ?? "",
|
||||||
|
- record.interfaceName ?? "",
|
||||||
|
- record.direction,
|
||||||
|
- record.layer,
|
||||||
|
- record.operation,
|
||||||
|
- record.source,
|
||||||
|
- record.destination,
|
||||||
|
- record.protocolName,
|
||||||
|
- record.beforeHeaders,
|
||||||
|
- record.afterHeaders,
|
||||||
|
- record.detail,
|
||||||
|
- ].map(escapeTSVField).joined(separator: "\t")
|
||||||
|
+ var fields: [String] = []
|
||||||
|
+ fields.append(String(record.number))
|
||||||
|
+ fields.append(String(record.timeMilliseconds))
|
||||||
|
+ fields.append(String(record.traceID))
|
||||||
|
+ fields.append(record.frameID.map { String($0) } ?? "")
|
||||||
|
+ fields.append(record.packetID.map { String($0) } ?? "")
|
||||||
|
+ fields.append(normalizedUUID(record.nodeID))
|
||||||
|
+ fields.append(record.nodeName)
|
||||||
|
+ fields.append(record.interfaceID.map { normalizedUUID($0) } ?? "")
|
||||||
|
+ fields.append(record.interfaceName ?? "")
|
||||||
|
+ fields.append(record.direction)
|
||||||
|
+ fields.append(record.layer)
|
||||||
|
+ fields.append(record.operation)
|
||||||
|
+ fields.append(record.source)
|
||||||
|
+ fields.append(record.destination)
|
||||||
|
+ fields.append(record.protocolName)
|
||||||
|
+ fields.append(record.beforeHeaders)
|
||||||
|
+ fields.append(record.afterHeaders)
|
||||||
|
+ fields.append(record.detail)
|
||||||
|
+ return fields.map(escapeTSVField).joined(separator: "\t")
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func traceIsOrderedBefore(
|
||||||
|
diff --git a/TopologyRuntimeSimpleClientEcho.swift b/TopologyRuntimeSimpleClientEcho.swift
|
||||||
|
index e72694f..5b9baf3 100644
|
||||||
|
--- a/TopologyRuntimeSimpleClientEcho.swift
|
||||||
|
+++ b/TopologyRuntimeSimpleClientEcho.swift
|
||||||
|
@@ -252,8 +252,11 @@ struct TopologyRuntimeWebBrowserHistoryEntry: Equatable {
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate var retainedByteCount: Int {
|
||||||
|
- address.utf8.count + resolvedIPAddress.utf8.count + (contentType?.utf8.count ?? 0)
|
||||||
|
- + body.utf8.count + bodyData.count + (errorMessage?.utf8.count ?? 0)
|
||||||
|
+ let head: Int = address.utf8.count + resolvedIPAddress.utf8.count
|
||||||
|
+ let type: Int = contentType?.utf8.count ?? 0
|
||||||
|
+ let error: Int = errorMessage?.utf8.count ?? 0
|
||||||
|
+ let tail: Int = body.utf8.count + bodyData.count
|
||||||
|
+ return head + type + tail + error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Executable
+10
@@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Replays every scenario under packages/core/test/scenarios in the Swift core
|
||||||
|
# and writes the reference result next to it (<name>.swift.json).
|
||||||
|
# Needs Docker; the TypeScript tests only read the written files.
|
||||||
|
set -eu
|
||||||
|
here="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
root="$(cd "$here/../.." && pwd)"
|
||||||
|
[ -d "$here/Sources/FiliusCore" ] || "$here/fetch-upstream.sh"
|
||||||
|
docker run --rm -v "$root:/work" -w /work/tools/swift-oracle swift:6.1-jammy \
|
||||||
|
sh -c 'swift build -q 2>&1 | grep -v "Sendable\|^ *|\|^ *\`" ; .build/debug/Oracle /work/packages/core/test/scenarios'
|
||||||
Reference in New Issue
Block a user