Files
mdg-filius/tools/java-oracle/README.md
T
julian.kiedaischandClaude Opus 5.5 67fb608412 test(java-oracle): ArpRun records ARP as Java FILIUS runs it
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>
2026-09-30 17:01:39 +00:00

15 KiB
Raw Blame History

Java-Orakel

Das Orakel misst Javas echtes Verhalten, Fehler eingeschlossen. Der TypeScript-Leser folgt Java, übernimmt aber keine Java-Fehler (Datenverlust, Absturz, eigene Ausgabe nicht wieder lesbar, Tippfehler, Verstoß gegen den Standard); jede solche Abweichung steht in den Tests namentlich mit Grund (JAVA_BUGS). Was Java selbst schreibt — der Korpus — wird exakt wie in Java gelesen.

Java-Filius selbst liest den Korpus (corpus/) und schreibt, was es gefunden hat, nach corpus/corpus.java.json. Java ist der Schiedsrichter für das Dateiformat — der Swift-Leser verliert in 10 von 23 Dateien Geräte (Plan §13, M2).

npm run oracle:java     # = tools/java-oracle/run.sh

Braucht JDK 17+, Maven und xvfb-run. Filius öffnet beim Laden sein Hauptfenster (Information.reset → JMainFrame), deshalb läuft es an einem virtuellen Bildschirm statt „headless".

  • Gelesen wird so, wie Filius eine Datei öffnet: SzenarioVerwaltung.laden entpackt das Archiv, kopiert die eigenen Anwendungen (projekt/anwendungen) dorthin, wo Filius' Klassenlader sucht, und lädt dann die Konfiguration — Javas Weg, kein Nachbau (seit 30.09.2026, M8). --network lädt nur die Konfiguration über die private Methode netzwerkLaden (per Reflection), wie vorher. Der Unterschied am Korpus: In anwendung_komplett.fls und multicast_server.fls sind dann die eigenen Anwendungen Accumulator und Broadcast installiert. Filius' Klassenlader ist ein Singleton: Eine einmal geladene eigene Klasse bleibt für die übrigen Dateien desselben Laufs geladen. Am Korpus stört das nicht, denn keine Datei nutzt eine Klasse, die sie nicht mitbringt.
  • Stand: gitlab.com/filius1/filius @ 6640354 (2.14.0), wird beim ersten Lauf nach .upstream/ geholt und gebaut.
  • Gewürfeltes: Was nicht in der Datei steht, würfelt Filius zum Teil (MAC-Adressen, die SSID eines Switches). Jede Datei wird deshalb zweimal geladen; jeder Wert, der sich zwischen beiden Läufen unterscheidet, erscheint als null. Zwei Läufe des Werkzeugs sind byte-gleich. (Bis 29.09.2026 suchte es die MAC im Dateitext und hielt dabei acht kleingeschriebene für gewürfelt.)
  • Bisher ausgelesen: Geräte (Klasse, Name, Beschriftung, Position, Schnittstellen samt Funk-Eigenschaft wireless, bei Rechner/Notebook useIPAsName/useMACAsName), unter system SSID, Lernzeit des Switches, RIP, manuelle Routen, die Firewall von Router und Heimrouter, DHCP-Client und -Server, die statische NAT-Tabelle des Heimrouters, die Verbindung eines Modems und das Dateisystem (Inhalte als Länge und SHA-256, damit die Referenz klein bleibt; bei Router und Heimrouter stehen vorn die Dateien, die Javas Konstruktor aus eigenen Vorlagen erzeugt, dahinter die aus der Datei) und die installierten Programme eines Rechners mit ihren Einstellungen (die persönliche Firewall samt Regeln; je Programm onItsSystem: ob es am Betriebssystem seines Knotens hängt) (Klassen, die Filius nicht mitbringt — eigene Anwendungen im Archiv unter projekt/anwendungen — fehlen, weil das Werkzeug nur konfiguration.xml lädt); E-Mail: Konten des Servers aus der Bean-Liste und aus /mailserver/konten.txt (kontenLaden), Ordner und Adressbuch des Clients und sein Konto aus /konten.txt (laden); ein Empfänger, den eine ältere Datei als Zeichenkette hält, samt dem, was Javas AddressEntry(String) daraus macht; Kabel (Enden als Gerät/Schnittstelle), Doku-Elemente.

Getter-Standardwerte

XMLEncoder teilt gleiche Integer/Boolean-Objekte. Ein Wert, der einmal als Ergebnis eines Getters geschrieben wurde (<void id="Integer0" property="iconTextGap"/>), taucht später nur noch als Verweis auf — etwa als x-Koordinate eines Geräts. Daran scheitert der Swift-Leser (Plan §13, M2).

GetterDefaults erzeugt die betroffenen Klassen wie XMLDecoder (ohne Argumente) und ruft den Getter auf; das Ergebnis steht in packages/fls/src/javaDefaults.json. Gemessen, nicht geraten: Filius' Knopf setzt verticalTextPosition auf 3 (BOTTOM), nicht auf Swings 0 (CENTER).

Dasselbe gilt für statische Konstanten: Die erste 0 einer Datei kann als <object class="javax.swing.SwingConstants" field="CENTER"/> stehen, und jede spätere 0 — etwa die Rot-, Grün- und Blauwerte einer schwarzen Doku-Farbe — verweist darauf. Schreibweise im Aufruf: klasse::FELD, Ergebnis "SwingConstants.CENTER": 0.

IP.ipCheck

Java-Filius schickt jede IP-Adresse und Maske beim Laden durch IP.ipCheck. IpCheckCases ruft es für jede Zeile von packages/fls/test/ip-check.cases.txt auf (ASCII, Nicht-ASCII als \uXXXX) und schreibt ip-check.java.json. Gemessen: leere Abschnitte zählen nicht (10..0.0.1), Vorzeichen und führende Nullen gehen, ebenso Ziffern jeder Schrift in der BMP (Thai, Devanagari, Vollbreite) — aber kein Leerzeichen und kein 𝟏 (Ersatzpaar).

/dns/hosts

Javas DNS-Server liest seine Einträge bei jeder Anfrage aus der Textdatei /dns/hosts. DnsHostsCases legt für jede Zeile von packages/fls/test/dns-hosts.cases.txt (eine ganze Datei je Zeile, ASCII, Zeilenumbruch als \u000A) einen Rechner mit dieser Datei an und schreibt, was DNSServer.holeResourceRecords() liefert, nach dns-hosts.java.json. Gemessen: Felder trennt nur ein einzelnes Leerzeichen (Tab und NBSP nicht), Zeilen nur LF; ein Name, den Javas Muster ablehnt (1und1.de, müller.de), wird leer, der Eintrag bleibt trotzdem; Typen außer A/MX/NS (auch a, CNAME, eine Kommentarzeile) gelten als gültig; eine TTL, die kein int ist, wird still 3600.

Was Java aus unseren Dateien lädt

run.sh lädt auch die Dateien unter packages/fls/test/written/, die topologyWriter.test.ts mit UPDATE_WRITTEN=1 schreibt, und unter packages/fls/test/exported/, die exportProject.test.ts mit UPDATE_EXPORTED=1 schreibt (beide nicht eingecheckt), mit derselben Lademethode wie den Korpus, und schreibt written.java.json bzw. exported.java.json daneben, samt SHA-256 jeder Datei (--sha256), damit der Test merkt, wenn der Befund nicht mehr zur Ausgabe passt. Je Kabel steht auch panel: die Geräte, zwischen denen die Oberfläche es zeichnet (JCablePanel.ziel1/ziel2) — im Korpus bei allen 183 Kabeln die Geräte, die es verbindet.

Wie Java Base64 schreibt

Base64Cases ruft Filius' eigenes Base64.encodeBytes (iHarder 2.x) auf Byte-Folgen von 19 Längen rund um die Zeilenlänge auf (packages/fls/test/base64.java.json). Befund: nach je 76 Zeichen ein \n, auch nach der letzten vollen Zeile, dann der aufgefüllte Rest ohne Umbruch. So stehen Bilder in den Dateien, auch die, die Javas Konstruktoren selbst anlegen.

Was Java aus unserem XML macht

BeansDump liest jede *.xml unter packages/fls/test/beans/ mit XMLDecoder und beschreibt, was dabei herauskommt (Beans über ihre Eigenschaften mit Lese- und Schreibmethode, Listen, Maps, Arrays, Rectangle, Color; ein zweites Mal gesehenes Objekt als Verweis) — beans.java.json. Fehler, die der Decoder sonst nur ins Log schreibt, sammelt ein ExceptionListener. Zeichenketten stehen als ASCII mit \uXXXX, weil ein einsames Surrogat in UTF-8 sonst zu „?“ würde.

E-Mail-Kontodateien

EmailFileCases server|client legt für jede Zeile von packages/fls/test/email-server.cases.txt bzw. email-client.cases.txt einen Rechner mit dieser Datei an (/mailserver/konten.txt bzw. /konten.txt) und schreibt, welche Konten EmailServer.kontenLaden() bzw. EmailAnwendung.laden() daraus machen. Gemessen: Eine einzige Serverzeile mit weniger als fünf Feldern lässt Java die ganze Datei verwerfen (NoSuchElementException — die Simulation startet nicht); leere Felder überspringt Java, statt sie zu zählen; eine Nachricht mit leerem Text geht verloren, weil split leere Felder am Ende abschneidet. Beim Client gewinnt die letzte Zeile, auch eine kaputte, die das Konto leert.

DHCP zur Laufzeit

DhcpRun baut das Netz eines Kern-Szenarios (packages/core/test/scenarios/*.scenario.json: Knoten, Kabel, Geräte, DHCP-Server und -Clients; die Schritte bleiben unbeachtet) in Java-Filius nach und startet es wie der Aktionsmodus (starten() jeder Systemsoftware). Nach der angegebenen Echtzeit schreibt es nach <name>.java.json, was jede Netzwerkkarte gesehen hat (Filius' eigener Mitschnitt, der Lauscher: Zeit, MACs, ARP-Operation und -Adressen, IP/UDP-Adressen und -Ports samt DHCP-Text), und die Adressen jedes Rechners am Ende. Die MAC eines Ports ist dieselbe wie im Kern (mac, sonst stableGeneratedMacAddress).

  • Switches und DHCP-Server starten zuerst: Javas Client wartet auf jeden aktiven Server, findet sie aber über die Knotenliste der Oberfläche, die das Werkzeug nicht hat.
  • GUIContainer.getGUIContainer() wird vor dem Start einmal aufgerufen. Im echten Filius steht das Fenster schon; ohne das legen zwei Clients den Singleton gleichzeitig an (nicht synchronisiert), und einer von ihnen sendet nie etwas (gemessen am Szenario dhcp-lan).
  • Die Zeiten schwanken von Lauf zu Lauf um einige zehn Millisekunden (echte Threads), die Link-Local-Adresse würfelt Java mit Math.random. Die Reihenfolge der Nachrichten eines Clients und ihr Inhalt schwanken nicht.

Gemessen (30.09.2026), was Swift anders macht: Je Versuch sendet Java genau ein DISCOVER bzw. REQUEST und wartet dann bis zu einer RTT (2,5 s) auf die Antwort; eine unpassende Antwort (etwa das Angebot des zweiten Servers) liest es und verwirft sie. Vor dem REQUEST prüft es die angebotene Adresse per ARP und wartet dabei eine RTT; antwortet ein anderer Rechner, lehnt es mit DHCPDECLINE ab. Javas Server übergeht das DECLINE und hält die Adresse weiter als Angebot fest (4 RTT). Nach dem fünften Fehlversuch (Zeitablauf) nimmt der Client eine Link-Local-Adresse 169.254.1.x/16: ARP-Probe, eine RTT warten, dann ein ARP-Reply an alle.

Die RTT ist in Java nicht fest: Verbindung.holeRTT = 50 Kabel × Verzögerung je Kabel × RTT-Faktor (Startoption, 1). Beim Anlegen des Fensters setzt das Hauptmenü den Regler auf 90 %, also 55 ms je Kabel und 2750 ms RTT — so laufen die DhcpRun-Referenzen; gemessen sind die Wartezeiten 2745 bis 2751 ms. Außerdem befördert ein Java-Kabel einen Frame nach dem anderen (SimplexVerbindung schläft die Verzögerung mit gesperrtem Puffer): Sendet eine Karte zwei Frames direkt nacheinander, kommt der zweite eine Kabellänge später.

Filius' Mitschnitt selbst ist dabei nicht ganz zuverlässig: Starten zwei Clients zugleich, fehlt auf der Karte des zweiten dessen eigenes DISCOVER (vier von vier Läufen von dhcp-lan; der Server empfängt es).

TCP zur Laufzeit

TcpRun spielt auf dem Netz von tcp-lan (Server 192.168.0.10, Clients .20 und .30) einen festen Ablauf von Socket-Aufrufen ab, wie Filius' Anwendungen sie machen, den Server in einem eigenen Thread. Es schreibt nach tcp-java-<ablauf>.java.json die Frames jeder Karte (mit TCP-Flags, Sequenz- und Bestätigungsnummer), wann jeder Aufruf begann, zurückkam oder scheiterte, und jeden Zustand jedes Sockets (alle 2 ms abgefragt). Netzbau und Mitschnitt teilen DhcpRun und TcpRun in ScenarioNetwork.

  • exchange: Verbindung zu Port 80, GET /, Antwort 3000 × x (drei Segmente), der Client schließt, der Server eine Sekunde nach CLOSE_WAIT.
  • closedPort: Verbindung zu Port 81, auf dem niemand lauscht.
  • lostData: verbunden, dann gehen alle Frames verloren (Verbindung.setDrop, wie der Paketverlust des Kerns), während der Client sendet; der Verlust endet, wenn senden scheitert.

Gemessen (30.09.2026, 90 %, RTT 2750 ms), was Swift anders macht:

  • Jeder Versuch wartet 3 RTT (8250 ms): Daten gehen dreimal hinaus (1720, 9970, 18220 ms), senden scheitert 8250 ms nach dem dritten.
  • Scheitert senden, schließt Java ordentlich: FIN, ACK, FIN, ACK. Swift setzt den Socket ohne FIN auf CLOSED.
  • TIME_WAIT dauert 3 RTT (8250 ms; oder bis zum nächsten Segment), nicht eine RTT.
  • Die Gegenseite schließt von selbst: Kommt ein FIN, geht Javas Socket-Thread aus listen in close und sendet sein FIN sofort (LAST_ACK rund 60 ms nach dem FIN), nicht erst, wenn die Anwendung schliessen aufruft.
  • Der Client sendet nur ein SYN: Nach dem ersten Zeitablauf setzt connectClientMode closeSocket, und die Schleife über drei Versuche endet (verbinden scheitert nach 3 RTT). Der Server-Socket wiederholt sein SYN+ACK nie.
  • Startnummern aus einem Zähler über alle TCP-Sockets (1000000, 2000000, …), wie im Kern; der freie Port ist zufällig.

DNS zur Laufzeit

DnsRun ruft auf dem Netz von dns-lan den Resolver eines Clients auf, wie Filius' Programme es tun, und schreibt nach dns-java-<ablauf>.java.json die Frames jeder Karte und das Ergebnis des Aufrufs. ScenarioNetwork richtet dazu die dnsServers des Szenarios ein: installiert, Einträge über hinzuRecord in /dns/hosts (Zeilen name TYP ziel, TTL 3600), eingeschaltet. Server 192.168.0.10 kennt web.filius.de, den Mailserver von filius.de und verweist schule.de an 192.168.0.11, der www.schule.de kennt.

  • address, mail, referral, unknown: A, MX (samt A des Mailservers), Verweis per NS, unbekannter Name.
  • noServer: der DNS-Server des Clients (192.168.0.99) fehlt.

Gemessen (30.09.2026), Javas Format, das Swift durch ein eigenes ersetzt: Jede Nachricht ist Text, ID=… QR=… RCODE=… QDCOUNT=… ANCOUNT=… NSCOUNT=… ARCOUNT=… und dann je Zeile eine Frage name. TYP IN oder ein Eintrag name. TYP ttl ziel. Die ID würfelt Java (Math.random). Die Antwort wiederholt die Frage nicht (QDCOUNT=0), RCODE ist immer 0, auch für einen unbekannten Namen (leere Antwort), und ein Verweis steht mit NS und A des Nameservers im Antwortteil. Der Resolver folgt dem Verweis selbst (iterativ) und wartet je Anfrage 5 RTT (13750 ms bei 90 %); ohne Server fragt ARP zweimal im Abstand einer RTT, dann scheitert der Aufruf.

ARP zur Laufzeit

ArpRun sendet auf dem Netz von arp-routed (10.0.0.2 über einen Switch am Router 10.0.0.1, 20.0.0.2 am Router 20.0.0.1; ScenarioNetwork baut dazu Router mit setzeAnzahlAnschluesse und den interfaces des Szenarios) an Adressen, die keine Karte hat, und schreibt nach arp-java-<ablauf>.java.json die Frames und die Aufrufe. Abläufe: pingLan (Ping an 10.0.0.9, ICMP.sendProbe), pingRouted (Ping an 20.0.0.9), udpTwice (zwei Datagramme an 10.0.0.9), udpBehind (eines an 10.0.0.9, dann eines an 20.0.0.2).

Gemessen (30.09.2026), was der Kern (nach Swift) anders macht:

  • ARP fragt bis zu zweimal und wartet je eine RTT (holeARPTabellenEintrag(ziel, 2, …)); dann „Host unreachable" (ICMP 3/1). Einen Negativ-Cache gibt es nicht: das nächste Paket fragt wieder zweimal.
  • Wer wartet, ist ein Thread: beim Ping der Aufrufer (5,5 s, dann ICMP 3/1 vom eigenen Rechner, Absender 127.0.0.1), beim Weiterleiten der IP-Thread des Routers, bei UDP und TCP der Transport-Thread je Protokoll und Knoten. senden kehrt sofort zurück, aber der Transport-Thread hält seine Warteschlange gesperrt, solange er wartet: das nächste senden blockiert bis dahin (5,5 s), und ein Datagramm an einen erreichbaren Rechner geht erst danach.
  • Ein Ping wartet eine RTT auf die Antwort; das „Host unreachable" des Routers (nach dessen 5,5 s) kommt zu spät.