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:
2026-09-29 19:33:14 +00:00
co-authored by Claude Opus 5.5
parent 696334aa5a
commit 5c16cf18b4
27 changed files with 27394 additions and 4 deletions
@@ -0,0 +1,5 @@
module CZlib [system] {
header "shim.h"
link "z"
export *
}
+1
View File
@@ -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")
}