feat(fls): system software settings as Java loads them

readSystemSoftware adds to each node what the topology needs from its
system software: a switch's SSID and learning time, a host's wireless
SSID, a router's RIP switch, the manual routes and the firewall of a
router or home router. All 23 corpus files match Java.

- A router's firewall is one object its constructor hangs in two places;
  the corpus writes it under the web server's "konfig" plug-in. The
  places are merged.
- The decoder keeps entries a constructor made in a map, reached by
  get(key) (installed applications), like the elements of a list.
- The Java oracle loads every file twice and writes values that differ
  as null: what FILIUS rolls (MACs, a switch's SSID) without text search.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-29 20:56:10 +00:00
co-authored by Claude Opus 5.5
parent da2ec1dae2
commit 9a389810f9
10 changed files with 1851 additions and 228 deletions
+77 -10
View File
@@ -15,6 +15,14 @@ import filius.hardware.knoten.InternetKnoten;
import filius.hardware.knoten.Knoten;
import filius.hardware.knoten.LokalerKnoten;
import filius.rahmenprogramm.SzenarioVerwaltung;
import filius.software.firewall.Firewall;
import filius.software.firewall.FirewallRule;
import filius.software.system.Betriebssystem;
import filius.software.system.GatewayFirmware;
import filius.software.system.InternetKnotenBetriebssystem;
import filius.software.system.SwitchFirmware;
import filius.software.system.SystemSoftware;
import filius.software.system.VermittlungsrechnerBetriebssystem;
import java.awt.Color;
import java.awt.Font;
@@ -27,12 +35,12 @@ 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.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
@@ -73,8 +81,17 @@ public class FlsOracle {
System.exit(0);
}
/**
* Loads the file twice. What FILIUS does not find in a file it rolls at
* random (MAC addresses, a switch's SSID) — such values differ between
* the two loads and are written as null, so that two runs are byte-equal.
*/
@SuppressWarnings("unchecked")
static Map<String, Object> read(Path file) throws Exception {
return (Map<String, Object>) stable(load(file), load(file));
}
static Map<String, Object> load(Path file) throws Exception {
Path xml = Files.createTempFile("konfiguration", ".xml");
try {
extractConfiguration(file, xml);
@@ -85,12 +102,27 @@ public class FlsOracle {
List.class, List.class);
load.setAccessible(true);
boolean success = (Boolean) load.invoke(null, xml.toString(), nodes, cables, docs);
return describe(success, nodes, cables, docs, Files.readString(xml, StandardCharsets.UTF_8));
return describe(success, nodes, cables, docs);
} finally {
Files.deleteIfExists(xml);
}
}
/** The two descriptions with every value that differs between them replaced by null. */
static Object stable(Object first, Object second) {
if (first instanceof Map<?, ?> left && second instanceof Map<?, ?> right && left.keySet().equals(right.keySet())) {
Map<String, Object> result = new LinkedHashMap<>();
for (Object key : left.keySet()) result.put(key.toString(), stable(left.get(key), right.get(key)));
return result;
}
if (first instanceof List<?> left && second instanceof List<?> right && left.size() == right.size()) {
List<Object> result = new ArrayList<>();
for (int index = 0; index < left.size(); index++) result.add(stable(left.get(index), right.get(index)));
return result;
}
return java.util.Objects.equals(first, second) ? first : null;
}
static void extractConfiguration(Path archive, Path target) throws Exception {
try (ZipInputStream zip = new ZipInputStream(Files.newInputStream(archive))) {
for (ZipEntry entry; (entry = zip.getNextEntry()) != null;) {
@@ -104,7 +136,7 @@ public class FlsOracle {
}
static Map<String, Object> describe(boolean success, List<GUIKnotenItem> nodes, List<GUIKabelItem> cables,
List<GUIDocuItem> docs, String xmlText) {
List<GUIDocuItem> docs) {
Map<Port, int[]> portOwners = new IdentityHashMap<>();
List<Object> nodeList = new ArrayList<>();
for (int nodeIndex = 0; nodeIndex < nodes.size(); nodeIndex++) {
@@ -128,13 +160,8 @@ public class FlsOracle {
Map<String, Object> port = new LinkedHashMap<>();
port.put("ip", nic.getIp());
port.put("mask", nic.getSubnetzMaske());
// Without a MAC address in the file, FILIUS rolls a random one when it
// creates the interface; only a MAC that stands in the file is stable.
String mac = nic.getMac();
// getMac() upper-cases; the file may not.
boolean macInFile = mac != null && Pattern
.compile(">" + Pattern.quote(mac) + "<", Pattern.CASE_INSENSITIVE).matcher(xmlText).find();
port.put("mac", macInFile ? mac : null);
// Random unless the file has one; see read().
port.put("mac", nic.getMac());
port.put("gateway", nic.getGateway());
port.put("dns", nic.getDns());
ports.add(port);
@@ -148,6 +175,7 @@ public class FlsOracle {
}
}
json.put("ports", ports);
json.put("system", systemSoftware(node));
nodeList.add(json);
}
@@ -189,6 +217,45 @@ public class FlsOracle {
return result;
}
/** The settings of a node's system software the topology layer reads. */
static Map<String, Object> systemSoftware(Knoten node) {
Map<String, Object> json = new LinkedHashMap<>();
SystemSoftware software = node.getSystemSoftware();
if (software instanceof SwitchFirmware firmware) {
json.put("ssid", firmware.getSSID());
json.put("retentionTime", firmware.getRetentionTime());
}
if (software instanceof Betriebssystem system) json.put("ssid", system.getSsid());
if (software instanceof VermittlungsrechnerBetriebssystem system) json.put("ripEnabled", system.isRipEnabled());
if (software instanceof InternetKnotenBetriebssystem system) {
List<Object> routes = new ArrayList<>();
if (system.getWeiterleitungstabelle() != null && system.getWeiterleitungstabelle().getManuelleTabelle() != null) {
for (String[] route : system.getWeiterleitungstabelle().getManuelleTabelle()) {
routes.add(route == null ? null : Arrays.asList(route));
}
}
json.put("manualRoutes", routes);
}
Firewall firewall = software instanceof VermittlungsrechnerBetriebssystem system ? system.holeFirewall()
: software instanceof GatewayFirmware firmware ? firmware.holeFirewall() : null;
if (firewall != null) {
Map<String, Object> settings = new LinkedHashMap<>();
settings.put("activated", firewall.isActivated());
settings.put("defaultPolicy", (int) firewall.getDefaultPolicy());
settings.put("dropICMP", firewall.getDropICMP());
settings.put("filterSYNSegmentsOnly", firewall.getFilterSYNSegmentsOnly());
settings.put("filterUdp", firewall.getFilterUdp());
List<Object> rules = new ArrayList<>();
for (FirewallRule rule : firewall.getRuleset()) {
rules.add(Arrays.asList(rule.srcIP, rule.srcMask, rule.destIP, rule.destMask, rule.port,
(int) rule.protocol, (int) rule.action));
}
settings.put("rules", rules);
json.put("firewall", settings);
}
return json;
}
/** Just enough JSON: maps, lists, strings, numbers, booleans, null. */
static final class Json {
static String write(Object value, String indent) {