feat(editor): F4.5b the device sheet for routers and home routers

Measured first: SheetRun now types into FILIUS' router and home router
panels, records five forwarding tables (holeTabelle) and steps in the
panel's table. Java turns a card address that passes the pattern but not
IP.ipCheck (".0.0.1") into null and the router panel throws; an empty
gateway is marked red yet taken; a refused table cell is stored as null;
a home router's DHCP client leaves its DHCP server running. None of the
nulls is adopted. The core's routeTableRows matches all five tables.

- doc: card addresses (the first card mirrored in the device's settings),
  router gateway, RIP, manual routes (a new row once all four cells pass,
  a cell, removal); the sheet reads cards with what their cable joins and
  the whole table
- editor: Java's tabs (general, each card titled by its address, the
  forwarding table greyed out under RIP; general, LAN, WAN on a home
  router); the open card's cable drawn active in Java's green

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 11:51:03 +00:00
co-authored by Claude Opus 5.5
parent 9b7ec2fc72
commit eff9433673
22 changed files with 2201 additions and 119 deletions
+190 -20
View File
@@ -1,5 +1,6 @@
// Types into FILIUS' own settings panels (JHostKonfiguration,
// JSwitchKonfiguration, JModemKonfiguration) and lets them take the text as
// JSwitchKonfiguration, JModemKonfiguration, JVermittlungsrechnerKonfiguration,
// JGatewayConfiguration) and lets them take the text as
// they do when a field loses the focus (aenderungenAnnehmen), then writes,
// for each case, whether the panel marked the text red while typing, what the
// device holds afterwards and what the field shows then (updateAttribute):
@@ -8,25 +9,38 @@
// Java-style unicode escapes (backslash, u, four hex digits) for anything
// else, one case a line:
//
// <host|switch|modem> <field> <text> the text may be empty
// <host|switch|modem|router|homeRouter> <field> <text> the text may be empty
//
// Fields: host name, ip, mask, gateway, dns; switch name, ssid, retention;
// modem name, port. After the cases, a few flows of the check boxes
// ("flows"): a host's name while it shows its address or MAC address, and
// DHCP switched on while its own DHCP server runs.
// modem name, port; router name, ip, mask (its first interface), gateway;
// homeRouter name, gateway, wanIp, wanMask, lanIp, lanMask. A field without
// a check method of its own is marked as its key listener marks it (the
// pattern). After the cases, a few flows ("flows"): a host's name while it
// shows its address or MAC address, DHCP switched on while the device's own
// DHCP server runs; then a router's forwarding table as Java shows it
// (Weiterleitungstabelle.holeTabelle, "tables") and its rows edited in the
// panel's table (JWeiterleitungsTabelle, "edits").
//
// Usage: java -cp filius.jar:lib/*:classes SheetRun <cases txt> <output json>
import filius.gui.GUIContainer;
import filius.gui.netzwerksicht.JKonfiguration;
import filius.gui.netzwerksicht.JWeiterleitungsTabelle;
import filius.hardware.NetzwerkInterface;
import filius.hardware.knoten.Gateway;
import filius.hardware.knoten.Host;
import filius.hardware.knoten.Modem;
import filius.hardware.knoten.Rechner;
import filius.hardware.knoten.Switch;
import filius.hardware.knoten.Vermittlungsrechner;
import filius.rahmenprogramm.EingabenUeberpruefung;
import filius.software.system.Betriebssystem;
import filius.software.system.GatewayFirmware;
import filius.software.system.InternetKnotenBetriebssystem;
import filius.software.system.ModemFirmware;
import filius.software.system.SwitchFirmware;
import filius.software.system.VermittlungsrechnerBetriebssystem;
import filius.software.vermittlungsschicht.Weiterleitungstabelle;
import java.io.FileOutputStream;
import java.lang.reflect.Field;
@@ -35,17 +49,22 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import javax.swing.JCheckBox;
import javax.swing.JTextField;
public class SheetRun {
/** A private field of a panel; "name#i" is the i-th of an array. */
static Object get(Object panel, String name) throws Exception {
Field field = panel.getClass().getDeclaredField(name);
String[] parts = name.split("#");
Field field = panel.getClass().getDeclaredField(parts[0]);
field.setAccessible(true);
return field.get(panel);
Object value = field.get(panel);
return parts.length == 1 ? value : ((Object[]) value)[Integer.parseInt(parts[1])];
}
static void call(Object panel, String name) throws Exception {
@@ -54,19 +73,33 @@ public class SheetRun {
method.invoke(panel);
}
/** The panel's field and its check (the method its key listener calls), per device and field. */
/** The panel's field, its check (the method its key listener calls) or else the pattern its key listener checks, per device and field. */
static final Map<String, String[]> FIELDS = new LinkedHashMap<>();
static {
FIELDS.put("host name", new String[] { "name", null });
FIELDS.put("host ip", new String[] { "ipAdresse", "checkIpAddress" });
FIELDS.put("host mask", new String[] { "netzmaske", "checkNetmask" });
FIELDS.put("host gateway", new String[] { "gateway", "checkGatewayAddress" });
FIELDS.put("host dns", new String[] { "dns", "checkDnsAddress" });
FIELDS.put("switch name", new String[] { "name", null });
FIELDS.put("switch ssid", new String[] { "ssid", "checkSSID" });
FIELDS.put("switch retention", new String[] { "retentionTime", "checkRetentionTime" });
FIELDS.put("modem name", new String[] { "name", null });
FIELDS.put("modem port", new String[] { "tfPort", null });
FIELDS.put("host name", new String[] { "name", null, null });
FIELDS.put("host ip", new String[] { "ipAdresse", "checkIpAddress", null });
FIELDS.put("host mask", new String[] { "netzmaske", "checkNetmask", null });
FIELDS.put("host gateway", new String[] { "gateway", "checkGatewayAddress", null });
FIELDS.put("host dns", new String[] { "dns", "checkDnsAddress", null });
FIELDS.put("switch name", new String[] { "name", null, null });
FIELDS.put("switch ssid", new String[] { "ssid", "checkSSID", null });
FIELDS.put("switch retention", new String[] { "retentionTime", "checkRetentionTime", null });
FIELDS.put("modem name", new String[] { "name", null, null });
FIELDS.put("modem port", new String[] { "tfPort", null, null });
FIELDS.put("router name", new String[] { "name", null, null });
FIELDS.put("router ip", new String[] { "ipAdressen#0", null, "ip" });
FIELDS.put("router mask", new String[] { "netzmasken#0", null, "mask" });
FIELDS.put("router gateway", new String[] { "gateway", null, "ip" });
FIELDS.put("homeRouter name", new String[] { "name", null, null });
FIELDS.put("homeRouter gateway", new String[] { "gatewayName", null, "ip" });
FIELDS.put("homeRouter wanIp", new String[] { "ipAddressWANPort", null, "ip" });
FIELDS.put("homeRouter wanMask", new String[] { "netmaskWANPort", null, "mask" });
FIELDS.put("homeRouter lanIp", new String[] { "ipAddressLANPort", null, "ip" });
FIELDS.put("homeRouter lanMask", new String[] { "netmaskLANPort", null, "mask" });
}
static Pattern pattern(String name) {
return name.equals("ip") ? EingabenUeberpruefung.musterIpAdresse : EingabenUeberpruefung.musterSubNetz;
}
static String stored(Object device, String key) {
@@ -88,6 +121,25 @@ public class SheetRun {
case "switch ssid": return firmware.getSSID();
case "switch retention": return Long.toString(firmware.getRetentionTime());
}
} else if (device instanceof Vermittlungsrechner) {
Vermittlungsrechner router = (Vermittlungsrechner) device;
NetzwerkInterface first = router.getNetzwerkInterfaces().get(0);
switch (key) {
case "router name": return router.getName();
case "router ip": return first.getIp();
case "router mask": return first.getSubnetzMaske();
case "router gateway": return ((InternetKnotenBetriebssystem) router.getSystemSoftware()).getStandardGateway();
}
} else if (device instanceof Gateway) {
Gateway home = (Gateway) device;
switch (key) {
case "homeRouter name": return home.getName();
case "homeRouter gateway": return ((InternetKnotenBetriebssystem) home.getSystemSoftware()).getStandardGateway();
case "homeRouter wanIp": return home.holeWANInterface().getIp();
case "homeRouter wanMask": return home.holeWANInterface().getSubnetzMaske();
case "homeRouter lanIp": return home.holeLANInterface().getIp();
case "homeRouter lanMask": return home.holeLANInterface().getSubnetzMaske();
}
} else if (device instanceof Modem) {
Modem modem = (Modem) device;
switch (key) {
@@ -114,10 +166,82 @@ public class SheetRun {
return sw;
}
case "modem": return new Modem();
case "router": {
Vermittlungsrechner router = new Vermittlungsrechner();
router.setzeAnzahlAnschluesse(2);
((InternetKnotenBetriebssystem) router.getSystemSoftware()).setStandardGateway("192.168.0.254");
return router;
}
case "homeRouter": {
Gateway home = new Gateway();
((InternetKnotenBetriebssystem) home.getSystemSoftware()).setStandardGateway("192.168.0.254");
return home;
}
}
throw new IllegalArgumentException(kind);
}
/** A router's forwarding table (Weiterleitungstabelle.holeTabelle) with its manual flags. */
static List<Object> table(String[] interfaces, String gateway, String[][] manual) {
Vermittlungsrechner router = new Vermittlungsrechner();
router.setzeAnzahlAnschluesse(interfaces.length / 2);
for (int i = 0; i < interfaces.length / 2; i++) {
router.getNetzwerkInterfaces().get(i).setIp(interfaces[2 * i]);
router.getNetzwerkInterfaces().get(i).setSubnetzMaske(interfaces[2 * i + 1]);
}
VermittlungsrechnerBetriebssystem os = (VermittlungsrechnerBetriebssystem) router.getSystemSoftware();
os.setStandardGateway(gateway);
Weiterleitungstabelle table = os.getWeiterleitungstabelle();
for (String[] row : manual) table.addManuellenEintrag(row[0], row[1], row[2], row[3]);
List<Object> rows = new ArrayList<>();
List<String[]> entries = table.holeTabelle();
List<Boolean> flags = table.holeManuelleEintraegeFlags();
for (int i = 0; i < entries.size(); i++) {
Map<String, Object> row = new LinkedHashMap<>();
row.put("cells", Arrays.asList(entries.get(i)));
row.put("manual", flags.get(i));
rows.add(row);
}
return rows;
}
/**
* Steps in the panel's table of a router with one manual row, only the
* manual rows shown: {"<column>", text} sets a cell of the first row,
* {"N<column>", text} of the last row, {"new"} adds a row, {"remove"}
* removes the first; each step taken as the table takes it. The manual
* rows afterwards (and the rows shown, when asked).
*/
static Map<String, Object> edit(String[][] steps, boolean shown) throws Exception {
Vermittlungsrechner router = new Vermittlungsrechner();
router.setzeAnzahlAnschluesse(2);
VermittlungsrechnerBetriebssystem os = (VermittlungsrechnerBetriebssystem) router.getSystemSoftware();
os.getWeiterleitungstabelle().addManuellenEintrag("172.16.0.0", "255.255.0.0", "192.168.0.2", "192.168.0.10");
JKonfiguration panel = JKonfiguration.getInstance(router);
JWeiterleitungsTabelle table = (JWeiterleitungsTabelle) get(panel, "weiterleitungstabelle");
table.setzeAlleEintraegeAnzeigen(false);
table.updateAttribute();
for (String[] step : steps) {
if (step[0].equals("new")) {
table.neuerEintrag();
} else if (step[0].equals("remove")) {
table.setRowSelectionInterval(0, 0);
table.markiertenEintragLoeschen();
} else {
boolean last = step[0].startsWith("N");
int column = Integer.parseInt(last ? step[0].substring(1) : step[0]);
table.setValueAt(step[1], last ? table.getRowCount() - 1 : 0, column);
table.aenderungenAnnehmen();
}
}
Map<String, Object> result = new LinkedHashMap<>();
List<Object> rows = new ArrayList<>();
for (String[] row : os.getWeiterleitungstabelle().getManuelleTabelle()) rows.add(Arrays.asList(row));
result.put("manual", rows);
if (shown) result.put("rowsShown", table.getRowCount());
return result;
}
public static void main(String[] args) throws Exception {
// FILIUS' window keeps the JVM alive: end it on any failure too.
try {
@@ -148,9 +272,19 @@ public class SheetRun {
if (names[1] != null) {
call(panel, names[1]);
marked = field.getForeground().equals(EingabenUeberpruefung.farbeFalsch);
} else if (names[2] != null) {
marked = !panel.ueberpruefen(pattern(names[2]), field);
}
// A panel that throws is part of the measurement ("error").
String error = null;
try {
call(panel, "aenderungenAnnehmen");
panel.updateAttribute();
} catch (java.lang.reflect.InvocationTargetException thrown) {
error = thrown.getCause().getClass().getSimpleName();
} catch (RuntimeException thrown) {
error = thrown.getClass().getSimpleName();
}
call(panel, "aenderungenAnnehmen");
panel.updateAttribute();
Map<String, Object> result = new LinkedHashMap<>();
result.put("device", words[0]);
result.put("field", words[1]);
@@ -159,6 +293,7 @@ public class SheetRun {
result.put("marked", marked);
result.put("stored", stored(device, key));
result.put("shown", field.getText());
if (error != null) result.put("error", error);
cases.add(result);
}
@@ -187,10 +322,45 @@ public class SheetRun {
flows.put("dhcpClientStopsServer", !os.getDHCPServer().isAktiv());
flows.put("dhcpClient", os.isDHCPKonfiguration());
}
// The same on a home router (its WAN card).
{
Gateway home = new Gateway();
GatewayFirmware firmware = (GatewayFirmware) home.getSystemSoftware();
firmware.getDHCPServer().setAktiv(true);
JKonfiguration panel = JKonfiguration.getInstance(home);
((JCheckBox) get(panel, "dhcp")).setSelected(true);
call(panel, "aenderungenAnnehmen");
flows.put("homeRouterDhcpClientStopsServer", !firmware.getDHCPServer().isAktiv());
flows.put("homeRouterDhcpClient", firmware.isDHCPKonfiguration());
}
// Forwarding tables: two interfaces, a gateway, manual rows.
Map<String, Object> tables = new LinkedHashMap<>();
String[][] manual = { { "172.16.0.0", "255.255.0.0", "192.168.1.2", "192.168.1.1" }, { "010.020.0.0", "255.255.0.0", "10.0.0.2", "10.0.0.1" } };
tables.put("gatewayInSecondNetwork", table(new String[] { "10.0.0.1", "255.0.0.0", "192.168.1.1", "255.255.255.0" }, "192.168.1.254", manual));
tables.put("gatewayInNoNetwork", table(new String[] { "10.0.0.1", "255.0.0.0", "192.168.1.1", "255.255.255.0" }, "172.30.0.1", manual));
tables.put("gatewayInBothNetworks", table(new String[] { "10.0.0.1", "255.0.0.0", "10.1.0.1", "255.255.0.0" }, "10.1.0.254", new String[0][]));
tables.put("noGateway", table(new String[] { "10.0.0.1", "255.0.0.0", "192.168.1.1", "255.255.255.0" }, "", manual));
tables.put("threeInterfaces", table(new String[] { "10.0.0.1", "255.0.0.0", "192.168.1.1", "255.255.255.0", "172.16.5.1", "255.255.0.0" }, "10.0.0.254", new String[0][]));
// The panel's table: a manual row edited, a new row, a row removed (only the manual rows shown).
Map<String, Object> edits = new LinkedHashMap<>();
edits.put("invalidCell", edit(new String[][] { { 0 + "", "abc" } }, false));
edits.put("emptyCell", edit(new String[][] { { 0 + "", "" } }, false));
edits.put("leadingZeros", edit(new String[][] { { 0 + "", "010.0.0.0" } }, false));
edits.put("dottedTwice", edit(new String[][] { { 0 + "", "10..0.0" } }, false));
edits.put("maskCell", edit(new String[][] { { 1 + "", "255.0.255.0" } }, false));
edits.put("newRowThreeCells", edit(new String[][] { { "new" }, { "N0", "10.9.0.0" }, { "N1", "255.255.0.0" }, { "N2", "10.0.0.9" } }, false));
edits.put("newRowFourCells", edit(new String[][] { { "new" }, { "N0", "10.9.0.0" }, { "N1", "255.255.0.0" }, { "N2", "10.0.0.9" }, { "N3", "10.0.0.1" } }, false));
edits.put("newRowInvalidCell", edit(new String[][] { { "new" }, { "N0", "10.9.0.0" }, { "N1", "255.255.0.0" }, { "N2", "10.0.0.9" }, { "N3", "10..0.0.1" } }, false));
edits.put("newRowTwice", edit(new String[][] { { "new" }, { "new" } }, true));
edits.put("removeRow", edit(new String[][] { { "remove" } }, false));
Map<String, Object> output = new LinkedHashMap<>();
output.put("cases", cases);
output.put("flows", flows);
output.put("tables", tables);
output.put("edits", edits);
try (FileOutputStream out = new FileOutputStream(args[1])) {
out.write((FlsOracle.Json.write(output, "") + "\n").getBytes(StandardCharsets.UTF_8));
}