The core translates before the IP thread, as Java's NAT threads sit
between card and Ethernet: out, the WAN address and the entry's port (ICMP
keeps its identifier); in, by remote end, port and protocol, else by the
port forwarding, which then makes a dynamic entry; a ping at the WAN
address is answered by the router, packets from outside for the LAN are
dropped, both sides pass the router's firewall, TTL 1 going out gets time
exceeded. Dynamic entries expire at Java's check each second, 300 s times
the delay factor old (55 minutes at 90 %), refreshed only by what comes in.
The home router forwards as a router does.
Measured and not taken: when a LAN endpoint sends to a second remote end,
Java gives it the entry's WAN port but no entry, and the answer stays at
the router; the core adds an entry for each new remote end on that port.
The oracle now records every card of a node (the second card of routers
was missing); the ARP and RIP runs are recorded anew and still match.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ScenarioNetwork builds home routers (their first card the WAN port) and
their port forwarding (portForwarding, through GatewayFirmware.setStaticNAT).
NatRun plays six stories on nat-home's network and writes the frames, the
calls and the router's NAT table: a ping, a UDP exchange, the same socket
to a second server (whose answer never reaches the LAN: Java keeps one
entry per LAN endpoint, for the first remote only), a TCP connection out,
a forwarded one in, and pings at the WAN address and past it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RipRun runs rip-chain's three routers in Java FILIUS, each router's random
set by reflection to the core's ripSeed, and writes the frames, every
change of each RIP table and the calls. converge: the tables fill, a ping
at 45 s. expire: R3 falls silent at 10 s; R2 marks its network 16 hops only
at its own next beacon, R1 passes it on at once, the ping gets ICMP 3/0.
Both match: per sending card the same messages at the same times, every
table through the same states. The first 5 s are compared by the state the
tables reach, since Java's routers start one after the other.
Measured on the JDK: Java's pause divides nextFloat() by 3 in float; the
core does too now (a millisecond off in one draw of about 10,000 before).
Scenarios name their RIP routers in the new field rip.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Where the Swift template departs from Java's filius.software.rip, Java's
code rules: a learned route gets 16 hops at the router's next beacon after
it expired (RIPTable.check), not when it expires; messages count from any
source port; each router draws its pauses from a random of its own (Java:
an unseeded Random per router), which the core seeds from the scenario and
the router's id (ripSeed), so that the Java oracle can give it the same.
A route of hop count 0 to each own network, the first beacon after a
second, then every 25.2 to 35.2 s; a message per interface without the
routes through it; a new route or a longer one from the same router sends
at once; forwarding by the table alone, fewest hops before longest mask.
Messages are read with Java's parser (javaIpCheck, javaParseInt, moved from
fls to core for it).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The resolver runs on the events now (a query, its dnsQueryTimeout, its
answer at the socket); the blocking resolveDns waits for it, and a
recursive server uses it: its own records, else the zone's name server
from its records, else its own DNS server, else a referral
(DNSServer.answerWithRemoteData).
DnsRun plays five stories on the new dns-recursive network. Measured and
not taken: a server that is its zone's name server asks itself, which asks
itself again, without end (the client times out, then every level floods
its own DNS server); Java guards only the step through its own DNS server.
The core asks itself in neither step. That story is compared up to the
client's query (JAVA_UNTIL).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every packet that comes in goes through its node's IP thread (UDP, TCP) or
ICMP thread (ICMP), as Java's IPThread and ICMPThread: deliver, answer an
echo request or forward, one after the other; while one waits for ARP, the
next wait behind it. A packet a node sends to itself is delivered at once,
as Java's IP.senden does. The transport send threads and these are one
mechanism now (handToProtocolThread).
ArpRun has three stories through the router (routedUdpBehind,
routedPingBesideUdp, routedPingBehindPing); the oracle writes ICMP frames
with type, code, addresses and sequence number. All seven ArpRun stories
match Java FILIUS.
Measured and not taken: Java's ARP wakes one waiter with notify, not the
one whose answer came; the core wakes all that wait for the address.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
A send hands its frame to the card and returns, as in Java; the frames
travel while the clock advances. Swift's send ran the network until every
frame had arrived; the option swiftSends keeps that for the Swift
comparison. Waiting calls (TCP connect, acknowledgement) now run event by
event until the answer instead of to the timeout.
UDP and TCP send through a thread per node and protocol (Java's
TransportProtokoll): one segment after the other; while IP waits for ARP,
the next send blocks and a segment from an event queues behind it. Senders
that wait take their turns in the order they came (a fair lock; Java's
leaves the order to chance).
All four ArpRun stories now match Java FILIUS, including when each call
returns. Tests let frames arrive explicitly with letFramesArrive.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An ARP lookup asks, waits a round trip, asks once more and gives up after
another round trip; then host unreachable, observed at the sender itself or
sent back by the router (for ICMP only for an echo request). A waiting
sender wakes after what else happens at that time. A ping then waits a
round trip for its answer, counted from when it left. Swift asks twice at
once while events run and lets the packet wait without end when the
address exists anywhere in the network; the option swiftArp keeps that for
the Swift comparison, and three scenarios are compared with Swift until the
trace where ARP fails or a ping goes unanswered.
arpJava.test.ts compares ArpRun: both pings match; the two UDP stories wait
for Java's transport thread (next step). DNS without a server now matches
Java too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sends to addresses no card has on the new arp-routed network (ping on the
LAN and behind a router, two UDP datagrams, one behind a failing one):
frames and calls. ScenarioNetwork builds routers and their interfaces.
Measured: ARP asks twice, a round trip each, then host unreachable; the
thread that waits is the ping's caller, the router's IP thread, or the
node's transport thread per protocol, which blocks the next send.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DNS follows Java's DNSServer and Resolver, not Swift's typed format:
Java's text messages (dns.ts), a server on port 53 answering from its
records (no question in the answer, RCODE 0, a referral with NS and A in
the answer section), an iterative resolver that waits five round trips per
query and remembers only the servers it was referred to. Recursion throws
NotPortedError until the next step. The snapshot carries the DNS servers
switched on. receiveUdp returns as soon as a datagram is there, as Java's
empfangen does.
dnsJava.test.ts compares DnsRun's five lookups; four match frame by frame,
the missing server waits for Java's ARP (named as not yet like Java).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A resolver's lookup on the new dns-lan network (A, MX, a referral by NS,
an unknown name, a missing server): every frame and the call's result.
ScenarioNetwork installs a scenario's DNS servers. The Swift comparison
reads only scenarios with a Swift result.
Measured: Java's text messages (ID=... QR=... header, question and record
lines), no question in the answer, RCODE 0 always, a referral in the answer
section, an iterative resolver waiting 5 RTT per query.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Java's SimplexVerbindung sleeps a cable's delay with the port's buffer
locked: frames on one cable leave one after the other, and a frame shows
in the packet log when it leaves. The engine now queues a frame in the
output buffer until the one before has arrived (ethernetLinkDeparture).
Swift sends a cable's frames at once; the option swiftCables keeps that for
the Swift comparison, which goes on checking everything else trace by
trace.
With the queue, the Java comparisons match within 120 ms (was 200); a
state of the TCP stories within 250 ms, where the engine's calls block
one after the other.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TCP follows Java's TCPSocket where it differs from Swift: each attempt and
TIME_WAIT wait three round trips that follow the speed (8250 ms at 90 %),
counted from when the segment is handed down, before ARP; the next segment
ends TIME_WAIT; the other end closes by itself on FIN; data no one
acknowledges closes with FIN; the accepted socket has the listener's
initial sequence number, and the listener takes the next.
Not taken from Java: three SYNs, not one (Java's retry loop ends after
the first timeout), named in tcpJava.test.ts as JAVA_BUGS.
tcpJava.test.ts compares TcpRun's three stories frame by frame and state
by state; the Swift comparison only checks that the TCP scenarios differ.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three stories of socket calls on tcp-lan's network (exchange, closedPort,
lostData): every frame with TCP flags and numbers, when each call started,
returned or failed, and each socket's states. Network building and the
packet log move from DhcpRun to ScenarioNetwork.
Measured: 3 RTT per attempt (8250 ms at 90 %), an orderly close after a
failed send, TIME_WAIT of 3 RTT, the passive side closing by itself on FIN,
one SYN only (Java's retry loop ends after the first timeout).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client follows Java's DHCPClient instead of Swift: one DISCOVER or
REQUEST per attempt, an answer that does not fit is read and dropped, the
ARP check waits a round trip (an answer for the address ends it at once,
then DECLINE), a timeout repeats the state's message, and the fifth turns
to a link-local address 169.254.1.x/16 (ARP probe, ARP reply to all); no
old address is given back. DHCP round trip times follow the speed as in
Java (50 cables, 2750 ms at 90 %; Swift: fixed 2500 ms).
Not taken from Java: the client gives up after five occupied link-local
addresses (Java searches on without end), and an ARP packet for another
address does not end the check.
dhcpJava.test.ts compares the four DHCP scenarios with DhcpRun's Java
runs; the Swift comparison only checks that they differ.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Builds a core scenario's network in Java FILIUS, starts it as the action
mode does and writes every frame each card saw (the Lauscher) and the
addresses at the end. References for the four DHCP scenarios: one
DISCOVER/REQUEST per attempt, an ARP check that waits one RTT, DECLINE on a
conflict, link-local 169.254.1.x after the fifth failure.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
engine.ts ports Swift's "DHCP server and automatic client": an active
server listens on port 67 from the start and offers a static assignment
before its range, round from the address after the last one offered;
offers last 10 s, dynamic leases a day, and a server whose offer another
server beat blacklists that address for a while. A client starts as an
event: DISCOVER, OFFER, a check by ARP, DECLINE, REQUEST, ACK or NAK,
2.5 s per wait, and after ten failures its old address back. The snapshot
carries the DHCP settings of clients and servers.
Four scenarios match the Swift engine. One Swift error is named in the
oracle test: a client run directly is advanced a second time, to FINISH,
by the server's answer within its own DISCOVER, and the outer run then
writes back its stale copy of the status (Swift's value types); the
address and every trace agree, only the reported status is wrong in
Swift. Swift's three DHCP engine tests are ported, with tests for
stopping and the message format. 15 mutation probes, 14 caught; the other
concerns the home router, which runs only once NAT is ported.
Two departures from Java remain, to be fixed next: from the event queue
the client's ARP check does not wait, so an address conflict is never
found, and each answer that wakes the client makes it send its state's
message again (a second DISCOVER and REQUEST, the second answered with
NAK). Java waits a round trip and sends once per attempt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
engine.ts ports Swift's "Java-compatible TCP sockets" in one piece, since
half a state machine could not be checked: server and client sockets (a
free port from Java's random numbers on their own seed), the three-way
handshake with its result (connected, invalid socket, unreachable, timed
out), segments of 1460 bytes, messages up to the push, a timeout of
3 × 2.5 s with at most three attempts, closing through every Java state
including TIME_WAIT (2.5 s), sequence numbers modulo 2^32.
Three scenarios match the Swift engine trace for trace: a LAN with data
over three segments, closing from both ends, a closed port and an
unreachable host; two networks through a router with a firewall rule,
data lost until the connection closes, and an accepted socket that does
not hold the port of its closed listener; CLOSING, reached by a lost FIN.
The engine delivers a FIN within the same close, so both ends close at
once only with packet loss; an empty message does not arrive in Swift (it
does not advance the sequence number), nor here.
Swift's four TCP engine tests are ported, with tests for stopping, a
foreign SYN+ACK at a listener and the flag names. 17 mutation probes, 16
caught; the other (delivered, not waited, not connected gives unreachable)
cannot be reached through Swift's engine, which never sends a reset and
always acknowledges correctly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
engine.ts ports the UDP part of Swift's "UDP sockets and Router RIP": a
socket bound to a given port or to a free one drawn from 49152–65534 with
Java's random numbers on Swift's seed; sending, also as a limited broadcast
on every interface (or the bound one) with TTL 1; delivery into the queue
of every socket on the destination port, which then answers to the last
sender; receiving, with a timeout that advances the virtual clock; closing.
Stopping forgets sockets and queues. RIP stays not ported; Swift's DHCP
handling of a datagram has nothing to do until DHCP is in the snapshot.
The oracle gets the steps bindUdp, sendUdp, receiveUdp and closeSocket on
both sides and reports the sockets. Two new scenarios — a LAN, and two
networks through a router with a UDP firewall rule and a broadcast that
does not cross it — match the Swift engine trace for trace, the random
ports included. Swift's four UDP engine tests are ported, with a test for
stopping. 11 mutation probes were all caught (one only after the stop test
was added).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
exportProject.ts, step X4a: each installed program of a PC or notebook as
the FILIUS application of its class, keyed by that class name as Java keys
them, with its settings: servers switched on as the model has them, a web
server's port and virtual hosts, the DNS server's recursion on port 53, the
echo server's port, the generic client's target (none as null, as its
constructor leaves it), the peer count, the personal firewall. E-mail
contents are the next step.
The model lost a web server's virtual hosts switch (useVHost, on for 3 web
servers in the corpus); Swift sets it off on import. The core now has
WebServerConfiguration.virtualHosts (Swift's name), the import carries
Java's value, and the Swift comparison names Swift's error.
Checked by Java: every corpus file, imported, exported and written, loads
with the programs of the original, with no new departure. In the synthetic
model the PC has every program with settings away from the defaults, the
notebook programs without settings (Java's defaults, 3 peers), where Java
makes /peer2peer itself. 12 mutation probes, each loaded by Java, were all
caught.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The e-mail client and server of every host come into the model (core:
EmailClientConfiguration, EmailServerConfiguration, EmailMessage), from
Java's view:
- the server's accounts are those of /mailserver/konten.txt, as Java reads
them at every simulation start; without the file those of the bean list,
which Java would drop;
- the client's account is that of /konten.txt; without it the first of an
older file's kontoListe, which FILIUS 2.14 no longer reads;
- messages are numbered per program in folder order, as Swift does; the
sent folder's are sent; a date is taken only when the file gives one.
Departures from Swift: accounts and messages stay where Swift drops what
fails its checks; first and last name stay apart; a port that is no number
is the default, with a warning. On the corpus the model matches Swift's on
all 13 files; the one Swift error is named (it numbers a mailbox's messages
as their received time and writes that into the konten.txt it rewrites).
The Swift dump now carries the e-mail state, and the file system
comparison leaves out only Swift's hidden state files (and the directories
it makes for them) instead of all of /mail, /mailserver and /peer2peer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Java's DNS server reads its records from the text file /dns/hosts on every
query. dnsHosts.ts ports that reading (DNSServer.leseRecordListe,
ResourceRecord, Dateisystem.holeDatei) and keeps in the model the records
Java answers: type exactly A, MX or NS, and a name Java accepted. The file
stays in the file system as it is.
Both oracles read 76 hand-picked hosts files (dns-hosts.cases.txt): the
Java oracle through DNSServer.holeResourceRecords (DnsHostsCases), the
Swift oracle through TopologyDNSHostsFileCodec (--dns-hosts). The Java view
matches Java on every case; the model matches Swift except for 33 named
cases in twelve reasons: Java splits at single spaces and LF only, has no
comments or "IN", turns a TTL that is no int into 3600, keeps duplicates,
and empties names with a leading digit. The Swift dump now carries each
server's records; the corpus comparison covers them on 16 files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
importProject carries a host's installed programs into the model with
the settings Swift keeps (web server port, Gnutella peer limit, DNS
recursion and forwarder, personal firewall) and two it does not (the echo
server's port, the generic client's target), plus Java's "switched on".
The Swift oracle writes programs and settings; they match on the 13 files
Swift reads completely.
Intended differences, named in the test:
- every FILIUS program is kept; Swift drops command prompt, text
editor, file explorer, image viewer, echo server and generic client;
- a web server's files stay in /webserver, its document root; Swift
moves them to /www. No program state goes into a host's files.
- multicast_server.fls: maxTeilnehmerZahl refers to a shared Integer
(a button's icon text gap, 4); Swift falls back to 3.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
importProject turns a PC's or notebook's Java file tree into Swift's
virtual file system, as Swift's registerVirtualFileSystem does: images
by type or extension from Base64, text by type, unknown types as binary
data or text, unsafe names and collisions left out with a warning, and
the whole import rejected over the project quota. NSString.pathExtension
is measured in the oracle, which now writes every imported file system.
On the 13 files Swift reads completely every file matches, images
compared by hash. Intended: a host whose file has no file system stays
empty, as in Java, where Swift adds the demo files of a new device.
Pending the applications step, the comparison reads our /webserver as
Swift's /www and leaves out the e-mail and Gnutella state files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The core gains Swift's virtual file system (paths, case-insensitive
lookup that keeps the spelling, quotas, atomic changes, the defaults of
a new device) and the string behaviour the ported code relies on without
saying so. Both are measured in the Swift oracle: 11 file system cases
(the three Swift tests among them) step by step, and trimming, lower
case, equality, ordering and Base64 case by case — whitespace and control
characters over all of Unicode.
Found by measuring:
- Swift trims NEL and ZERO WIDTH SPACE, not the BOM; JS trim() does the
opposite. The .fls import now trims with swiftTrim.
- lowercased() maps scalar by scalar: a word-final sigma stays σ.
- Foundation decodes "====" and "Y===" as empty data.
- Foundation on Linux counts 24,735 plane-14 scalars as control
characters, a table artefact; the port keeps Unicode Cc+Cf.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
importProject carries the DHCP client switch and server settings (hosts
and home routers), a home router's port forwarding rows and a modem's
pairing into the model; the core gains the types, ported from Swift.
The Swift oracle writes them too, and they match on the 13 files Swift
reads completely. Configuration text is now trimmed throughout, as Swift
trims every value it reads (routes and firewall rules included).
Outside those files Swift switches every DHCP server of the Internet
examples off: their "aktiv" refers to a shared Boolean it cannot follow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
importProject carries the system software settings into the model the
core runs: switch SSID and learning time (an SSID Java would roll comes
from the node id, as in Swift), a host's wireless network, manual routes,
RIP and the firewall of every router and home router. The Swift oracle
writes these settings too; they match on the 13 files Swift reads
completely.
A router's default gateway — its first interface's, which Java routes to
(Weiterleitungstabelle.holeTabelle) — is imported into the router's
device configuration. Swift's model drops it; in internetwork_komplett
the way back from 141.99.5.10 depends on it. A test pings across three
routers in the core, with and without it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
importProject builds what the simulation core runs from Java's view of a
file: nodes with kinds and ports, links between port ids, device and
interface IP configurations, documentation items with Swift's derived
ids. The core gains the model types it needs (node, link, graph,
documentation item), ported from filius-ipad.
Checked against the Swift import, which the Swift oracle now writes in
full (corpus.swift.json, "state"), on the 13 files Swift reads
completely. Two differences are intended and named in the test:
- a device the file leaves unnamed has Java's default name
("Switch / WLAN"), not Swift's;
- multicast_server.fls: useIPAsName refers to a shared Boolean that
Swift cannot follow; Java shows the IP address, and so do we.
All 23 files import with as many devices and cables as Java has, and an
imported example pings across its router in the core.
Port counts of switches and routers follow Java's lists instead of
Swift's guess from references.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
First slice of the TypeScript port of the filius-ipad runtime engine,
with 29 tests ported from the Swift suite. Parts not ported yet throw
NotPortedError instead of guessing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>