feat(fls): write the system software of a .fls, loaded by Java as the original

topologyWriter.ts now sets the system software each device's constructor
makes, through its setters: a switch's SSID and learning time, a host's
SSID, RIP, manual routes, DHCP client and server with static assignments,
static NAT, the modem, and a router's or home router's firewall. The
firewall is reached as XMLEncoder reaches it, through the web server's
`konfig` plug-in: the constructor hangs the same object there and among
the applications, where its key differs (Firewall on a router, NatGateway
on a home router). A switch's SSID the file lacked stays unwritten, so Java
rolls one as it did. Files and applications are later layers.

javaBeansWriter.ts: a call can carry statements on its result (a map's
value changed through get(key)), checked by Java's XMLDecoder in a new
case; a short() helper for Java's short values.

Checked by Java against Java: every corpus file, read and written again,
loads with the same system software (without files and applications) as
the original. The synthetic topology adds what the corpus lacks: a router
firewall switched on with rules of every protocol (one with a null
address), every flag away from its default, a modem with its own port and
target, and a PC's SSID, routes and DHCP server. 16 mutation probes, each
rewritten and loaded by Java, were all caught.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-30 08:18:52 +00:00
co-authored by Claude Opus 5.5
parent 314f696515
commit facad5843e
8 changed files with 749 additions and 227 deletions
+116 -3
View File
@@ -1,5 +1,6 @@
// Writes `projekt/konfiguration.xml` from Java's view of a project — the
// counterpart of topology.ts. The four values Java's netzwerkLaden reads: the
// counterpart of topology.ts and, for the system software, of
// systemSoftware.ts. The four values Java's netzwerkLaden reads: the
// version line, the list of GUIKnotenItem, of GUIKabelItem and of
// GUIDocuItem. What a node's constructor makes (its interfaces, a switch's
// ports) is reached through getters and changed there, as XMLEncoder does;
@@ -7,28 +8,42 @@
// objects of the nodes, so the same cable joins the same ports when Java
// loads the file.
//
// The system software is the one the node's constructor makes; its settings
// are set through getters. A router's or home router's firewall is reached
// the way XMLEncoder reaches it: through the web server's `konfig` plug-in,
// which holds the same object (its key among the installed applications
// differs: Firewall on a router, NatGateway on a home router). Files and
// applications are later layers.
//
// Checked by Java: every corpus file, read and written again, loads in Java
// FILIUS to the same topology (topologyWriter.test.ts, tools/java-oracle).
// FILIUS to the same topology and system software (topologyWriter.test.ts,
// tools/java-oracle).
import {
encodeJavaBeans,
int,
long,
object,
reference,
type BeanObject,
type BeanReference,
type BeanStatement,
type BeanValue,
short,
} from './javaBeansWriter.js';
import type { FlsFirewall, FlsSystemSoftware } from './systemSoftware.js';
import type { FlsDocumentationItem, FlsInterface, FlsLink, FlsNode, FlsNodeClass } from './topology.js';
/** The version line of the files this writer makes: the format of FILIUS 2.14. */
export const WRITTEN_VERSION_LINE = 'Filius version: 2.14.0 (mdg-filius)';
/** The system software's settings; its files and applications are later layers. */
export type SystemToWrite = Omit<FlsSystemSoftware, 'fileSystem' | 'applications'>;
export type NodeToWrite = Pick<
FlsNode,
'javaClass' | 'name' | 'x' | 'y' | 'useIpAsName' | 'useMacAsName' | 'interfaces' | 'portCount'
>;
> & { readonly system: SystemToWrite };
export interface TopologyToWrite {
readonly nodes: readonly NodeToWrite[];
@@ -124,9 +139,107 @@ function device(node: NodeToWrite, ports: BeanValue[]): BeanObject {
),
);
}
statements.push({ get: 'systemSoftware', statements: systemStatements(node.system, node.javaClass) });
return object(`filius.hardware.knoten.${node.javaClass}`, statements);
}
const WEB_SERVER_CLASS = 'filius.software.www.WebServer';
/**
* The settings the file has, each set on the constructor's system software.
* A switch's SSID the file lacked stays unwritten, so Java rolls one as it did.
*/
function systemStatements(system: SystemToWrite, javaClass: FlsNodeClass): BeanStatement[] {
const statements: BeanStatement[] = [];
if (system.firewall !== undefined) {
statements.push({
get: 'installierteAnwendungen',
statements: [
{
call: 'get',
args: [WEB_SERVER_CLASS],
statements: [
{ get: 'plugins', statements: [{ call: 'get', args: ['konfig'], statements: [{ get: 'firewall', statements: firewallStatements(system.firewall) }] }] },
],
},
],
});
}
if (system.dhcpServer !== undefined) {
const server = system.dhcpServer;
statements.push({
get: 'DHCPServer',
statements: [
{ set: 'aktiv', value: server.isActive },
{ set: 'untergrenze', value: server.lowerBound },
{ set: 'obergrenze', value: server.upperBound },
{ set: 'gatewayip', value: server.gateway },
{ set: 'dnsserverip', value: server.dnsServer },
{ set: 'ownSettings', value: server.useOwnSettings },
{
set: 'staticAssignedAddresses',
value: object(
'java.util.ArrayList',
server.staticAssignedAddresses.map(({ mac, ip }) => ({ call: 'add', args: [`${mac} ${ip}`] })),
),
},
],
});
}
if (system.dhcpClient !== undefined) statements.push({ set: 'DHCPKonfiguration', value: system.dhcpClient });
if (system.manualRoutes !== undefined) {
statements.push({ get: 'weiterleitungstabelle', statements: [{ set: 'manuelleTabelle', value: rows(system.manualRoutes) }] });
}
if (system.ripEnabled !== undefined) statements.push({ set: 'ripEnabled', value: system.ripEnabled });
if (system.retentionTime !== undefined) statements.push({ set: 'retentionTime', value: long(system.retentionTime) });
if (system.staticNAT !== undefined) statements.push({ set: 'staticNAT', value: rows(system.staticNAT) });
if (system.modem !== undefined) {
statements.push(
{ set: 'mode', value: int(system.modem.mode) },
{ set: 'port', value: int(system.modem.port) },
{ set: 'ipAdresse', value: system.modem.ipAddress },
);
}
// SwitchFirmware.setSSID, Betriebssystem.setSsid.
if (system.ssid !== undefined) statements.push({ set: javaClass === 'Switch' ? 'SSID' : 'ssid', value: system.ssid });
return statements;
}
/** A LinkedList of String arrays: routes, static NAT. */
function rows(table: readonly (readonly (string | null)[])[]): BeanObject {
return listOf(table.map((row) => ({ kind: 'array', componentClass: 'java.lang.String', items: [...row] })));
}
function firewallStatements(firewall: FlsFirewall): BeanStatement[] {
return [
{ set: 'activated', value: firewall.activated },
{ set: 'defaultPolicy', value: short(firewall.defaultPolicy) },
{ set: 'dropICMP', value: firewall.dropICMP },
{ set: 'filterSYNSegmentsOnly', value: firewall.filterSYNSegmentsOnly },
{ set: 'filterUdp', value: firewall.filterUdp },
{
set: 'ruleset',
value: object(
'java.util.Vector',
firewall.rules.map((rule) => ({
call: 'add',
args: [
object('filius.software.firewall.FirewallRule', [], [
rule.srcIP,
rule.srcMask,
rule.destIP,
rule.destMask,
int(rule.port),
short(rule.protocol),
short(rule.action),
]),
],
})),
),
},
];
}
/**
* A list the constructor filled with `constructed` elements: those are
* changed through `get(index)`, more are added. With fewer, the list is set