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:
@@ -59,14 +59,15 @@ export type BeanStatement =
|
||||
| { readonly set: string; readonly value: BeanValue }
|
||||
/** target.getP(), then the statements on its result; `as` names the result */
|
||||
| { readonly get: string; readonly statements: readonly BeanStatement[]; readonly as?: BeanReference }
|
||||
/** target.m(args) */
|
||||
| { readonly call: string; readonly args: readonly BeanValue[] }
|
||||
/** target.m(args), then the statements on its result */
|
||||
| { readonly call: string; readonly args: readonly BeanValue[]; readonly statements?: readonly BeanStatement[] }
|
||||
/** target.set(index, value) on a list */
|
||||
| { readonly setIndex: number; readonly value: BeanValue }
|
||||
/** target.get(index), then the statements on its result; `as` names the result */
|
||||
| { readonly getIndex: number; readonly statements: readonly BeanStatement[]; readonly as?: BeanReference };
|
||||
|
||||
export const int = (value: number): BeanPrimitive => ({ kind: 'int', value });
|
||||
export const short = (value: number): BeanPrimitive => ({ kind: 'short', value });
|
||||
export const long = (value: number | bigint): BeanPrimitive => ({ kind: 'long', value });
|
||||
export const double = (value: number): BeanPrimitive => ({ kind: 'double', value });
|
||||
export const float = (value: number): BeanPrimitive => ({ kind: 'float', value });
|
||||
@@ -194,12 +195,14 @@ class Writer {
|
||||
} else if ('get' in statement) {
|
||||
this.getter(`property="${escapeAttribute(statement.get)}"`, statement.statements, statement.as, depth, lines);
|
||||
} else if ('call' in statement) {
|
||||
if (statement.args.length === 0) {
|
||||
const inner = statement.statements ?? [];
|
||||
if (statement.args.length === 0 && inner.length === 0) {
|
||||
lines.push(`${indent}<void method="${escapeAttribute(statement.call)}"/>`);
|
||||
return;
|
||||
}
|
||||
nested(`<void method="${escapeAttribute(statement.call)}"`, () => {
|
||||
for (const arg of statement.args) this.value(arg, depth + 1, lines);
|
||||
for (const each of inner) this.statement(each, depth + 1, lines);
|
||||
});
|
||||
} else if ('setIndex' in statement) {
|
||||
nested(`<void index="${statement.setIndex}"`, () => this.value(statement.value, depth + 1, lines));
|
||||
@@ -252,7 +255,10 @@ function sharedObjects(roots: readonly BeanValue[]): Set<BeanObject | BeanArray>
|
||||
const shared = new Set<BeanObject | BeanArray>();
|
||||
const visitStatement = (statement: BeanStatement): void => {
|
||||
if ('set' in statement || 'setIndex' in statement) visit(statement.value);
|
||||
else if ('call' in statement) statement.args.forEach(visit);
|
||||
else if ('call' in statement) {
|
||||
statement.args.forEach(visit);
|
||||
(statement.statements ?? []).forEach(visitStatement);
|
||||
}
|
||||
else statement.statements.forEach(visitStatement);
|
||||
};
|
||||
const visit = (value: BeanValue): void => {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user