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

topologyWriter.ts puts each installed application of a PC or notebook under
its key and gives it the system software, as installApp does, with its
settings: a server's switch and port, a client's target, DNS recursion,
virtual hosts, the peer count, the personal firewall. The applications
follow the files, as in Java's own files: the web server, the peer-to-peer
application and the e-mail server make their directories only where the
files lack them, and the DNS server reads /dns/hosts when it is installed.
E-mail accounts, messages and address books are the next step.

The Java oracle now reports a personal firewall's rules in full, not their
number (the corpus has none; its result is byte-equal).

Checked by Java against Java: every corpus file, read and written again,
loads with the same applications as the original. The synthetic topology
adds every FILIUS application with settings away from its defaults, a
personal firewall with a rule, an own index.html (Java makes no second) and
a missing /peer2peer directory, which Java makes at the end. 12 mutation
probes, among them files and applications in reverse order, each 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:36:00 +00:00
co-authored by Claude Opus 5.5
parent 84ea76e880
commit ccec734143
7 changed files with 1861 additions and 142 deletions
+55 -6
View File
@@ -14,7 +14,13 @@
// which holds the same object (its key among the installed applications
// differs: Firewall on a router, NatGateway on a home router). Files are
// added to the constructor's root, as XMLEncoder adds them — on a router
// after the files its constructor makes. Applications are a later layer.
// after the files its constructor makes. The installed applications of a PC
// or notebook follow the files, as in Java's own files: the constructors of
// the web server, the peer-to-peer application and the e-mail server make
// their directories only where the files lack them, and the DNS server reads
// /dns/hosts when it is installed. Each is put under its key and given the
// system software, as installApp does. E-mail accounts, messages and address
// books are a later layer.
//
// Checked by Java: every corpus file, read and written again, loads in Java
// FILIUS to the same topology and system software (topologyWriter.test.ts,
@@ -32,14 +38,19 @@ import {
type BeanValue,
short,
} from './javaBeansWriter.js';
import type { FlsFileTree, FlsFirewall, FlsSystemSoftware } from './systemSoftware.js';
import type { FlsApplication, FlsFileTree, 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 and files; its applications are a later layer. */
export type SystemToWrite = Omit<FlsSystemSoftware, 'applications'>;
/** An installed application; e-mail accounts, messages and address books are a later layer. */
export type ApplicationToWrite = Omit<FlsApplication, 'source'>;
/** The system software's settings, files and applications. */
export type SystemToWrite = Omit<FlsSystemSoftware, 'applications'> & {
readonly applications?: readonly ApplicationToWrite[];
};
export type NodeToWrite = Pick<
FlsNode,
@@ -140,7 +151,14 @@ function device(node: NodeToWrite, ports: BeanValue[]): BeanObject {
),
);
}
statements.push({ get: 'systemSoftware', statements: systemStatements(node.system, node.javaClass) });
// Named for the applications, which are given it.
const system = (node.system.applications?.length ?? 0) > 0 ? reference('SystemSoftware') : undefined;
const systemStatementList = systemStatements(node.system, node.javaClass, system);
statements.push(
system === undefined
? { get: 'systemSoftware', statements: systemStatementList }
: { get: 'systemSoftware', statements: systemStatementList, as: system },
);
return object(`filius.hardware.knoten.${node.javaClass}`, statements);
}
@@ -150,7 +168,7 @@ 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[] {
function systemStatements(system: SystemToWrite, javaClass: FlsNodeClass, self: BeanReference | undefined): BeanStatement[] {
const statements: BeanStatement[] = [];
if (system.firewall !== undefined) {
statements.push({
@@ -167,6 +185,15 @@ function systemStatements(system: SystemToWrite, javaClass: FlsNodeClass): BeanS
});
}
if (system.fileSystem !== undefined) statements.push(fileSystemStatement(system.fileSystem, system.templateFiles !== false));
if (self !== undefined && system.applications !== undefined) {
statements.push({
get: 'installierteAnwendungen',
statements: system.applications.map((application) => ({
call: 'put',
args: [application.key, object(application.javaClass, applicationStatements(application, self))],
})),
});
}
if (system.dhcpServer !== undefined) {
const server = system.dhcpServer;
statements.push({
@@ -236,6 +263,28 @@ function userObject(tree: FlsFileTree): BeanValue {
return tree.kind === 'directory' ? tree.name : null;
}
/**
* An application's settings, after the system software it runs on. A
* peer-to-peer application's peer count the file lacked stays unwritten, so
* Java rolls one as it did.
*/
function applicationStatements(application: ApplicationToWrite, system: BeanReference): BeanStatement[] {
const statements: BeanStatement[] = [{ set: 'systemSoftware', value: system }];
if (application.isActive !== undefined) statements.push({ set: 'aktiv', value: application.isActive });
if (application.port !== undefined) statements.push({ set: 'port', value: int(application.port) });
if (application.targetAddress !== undefined) statements.push({ set: 'zielIPAdresse', value: application.targetAddress });
if (application.targetPort !== undefined) statements.push({ set: 'zielPort', value: int(application.targetPort) });
if (application.recursiveResolutionEnabled !== undefined) {
statements.push({ set: 'recursiveResolutionEnabled', value: application.recursiveResolutionEnabled });
}
if (application.useVirtualHosts !== undefined) statements.push({ set: 'useVHost', value: application.useVirtualHosts });
if (application.maxPeers !== undefined && application.maxPeers !== null) {
statements.push({ set: 'maxTeilnehmerZahl', value: int(application.maxPeers) });
}
if (application.firewall !== undefined) statements.push(...firewallStatements(application.firewall));
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] })));