feat(fls): export the editor model's programs

exportProject.ts, step X4a: each installed program of a PC or notebook as
the FILIUS application of its class, keyed by that class name as Java keys
them, with its settings: servers switched on as the model has them, a web
server's port and virtual hosts, the DNS server's recursion on port 53, the
echo server's port, the generic client's target (none as null, as its
constructor leaves it), the peer count, the personal firewall. E-mail
contents are the next step.

The model lost a web server's virtual hosts switch (useVHost, on for 3 web
servers in the corpus); Swift sets it off on import. The core now has
WebServerConfiguration.virtualHosts (Swift's name), the import carries
Java's value, and the Swift comparison names Swift's error.

Checked by Java: every corpus file, imported, exported and written, loads
with the programs of the original, with no new departure. In the synthetic
model the PC has every program with settings away from the defaults, the
notebook programs without settings (Java's defaults, 3 peers), where Java
makes /peer2peer itself. 12 mutation probes, 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 09:17:30 +00:00
co-authored by Claude Opus 5.5
parent 698a775c37
commit 95daa64276
7 changed files with 1927 additions and 131 deletions
+110 -3
View File
@@ -19,6 +19,7 @@ import {
firewallRule,
stableGeneratedMacAddress,
type Id,
type InstallableProgram,
type TopologyNode,
VirtualFileSystem,
} from '@mdg-filius/core';
@@ -68,9 +69,21 @@ const original = JSON.parse(readFileSync(join(corpusRoot, 'corpus.java.json'), '
const exportedPath = join(here, 'exported.java.json');
const exported = JSON.parse(readFileSync(exportedPath, 'utf8')) as Record<string, JavaFile>;
/** The system software without its applications, a later step; files in any order. */
/**
* The system software without what a later step exports — the e-mail
* programs' domain, bean accounts, folders and address book; files in any
* order.
*/
function systemOf(system: Record<string, unknown>) {
const { applications: _applications, ...settings } = system;
const settings =
system.applications === undefined
? system
: {
...system,
applications: (system.applications as Record<string, unknown>[]).map(
({ mailDomain: _domain, beanAccounts: _accounts, received: _received, sent: _sent, drafts: _drafts, addressBook: _book, ...rest }) => rest,
),
};
return settings.fileSystem === undefined || settings.fileSystem === null
? settings
: { ...settings, fileSystem: sortedTree(settings.fileSystem as JavaTree) };
@@ -315,6 +328,13 @@ function emptyProject(): ProjectToExport {
portForwardingRowsByNodeId: new Map(),
remoteLinkConfigurations: new Map(),
virtualFileSystems: new Map(),
installedPrograms: new Map(),
activePrograms: new Map(),
webServerConfigurations: new Map(),
gnutellaConfigurations: new Map(),
dnsServerConfigurations: new Map(),
echoServerConfigurations: new Map(),
simpleClientConfigurations: new Map(),
documentationItems: [],
};
}
@@ -341,6 +361,10 @@ function syntheticFiles(): VirtualFileSystem {
files.writeImageFile('/foto.jpg', bytesOf(100), 'image/jpeg');
files.writeBinaryFile('/daten.bin', bytesOf(58), 'application/zip');
files.writeBinaryFile('/roh', bytesOf(3));
// The web server's and the peer-to-peer application's own: Java makes none.
files.createDirectory('/webserver');
files.writeTextFile('/webserver/index.html', '<p>own</p>');
files.createDirectory('/peer2peer');
return files;
}
@@ -400,6 +424,14 @@ const SYNTHETIC: ProjectToExport = {
]),
ripEnabledByNodeId: new Map([['rt', true]]),
firewallConfigurationsByNodeId: new Map([
[
'pc',
firewallConfiguration({
isActive: false,
defaultPolicy: 'accept',
rules: [firewallRule({ sourceIPAddress: '10.0.0.0', sourceSubnetMask: '255.0.0.0', port: 22, action: 'accept' })],
}),
],
[
'rt',
firewallConfiguration({
@@ -437,6 +469,34 @@ const SYNTHETIC: ProjectToExport = {
]),
portForwardingRowsByNodeId: new Map([['gw', [{ protocolValue: 'TCP', publicPortValue: '8080', lanIPAddress: '192.168.1.10', lanPortValue: '80' }]]]),
virtualFileSystems: new Map([['pc', syntheticFiles()]]),
installedPrograms: new Map<Id, Set<InstallableProgram>>([
[
'pc',
new Set<InstallableProgram>([
'webServer',
'dnsServer',
'echoServer',
'simpleClient',
'gnutella',
'firewall',
'commandPrompt',
'textEditor',
'fileExplorer',
'imageViewer',
'webBrowser',
]),
],
['nb', new Set<InstallableProgram>(['gnutella', 'simpleClient', 'firewall'])],
]),
activePrograms: new Map([['pc', new Set<InstallableProgram>(['webServer', 'dnsServer', 'echoServer'])]]),
webServerConfigurations: new Map([['pc', { port: 8080, documentRoot: '/webserver', virtualHosts: true }]]),
dnsServerConfigurations: new Map([['pc', { recursiveResolutionEnabled: true, records: [] }]]),
echoServerConfigurations: new Map([['pc', { port: 7777 }]]),
simpleClientConfigurations: new Map([
['pc', { targetAddress: '10.0.0.9', targetPort: 7777 }],
['nb', { targetAddress: '', targetPort: 55_555 }],
]),
gnutellaConfigurations: new Map([['pc', { maximumKnownPeers: 7 }]]),
remoteLinkConfigurations: new Map([
['m1', { pairIdentifier: 'java-modem-port-4242', latencyMilliseconds: 20, isEnabled: true }],
['m2', { pairIdentifier: 'java-modem-port-4242', latencyMilliseconds: 20, isEnabled: true }],
@@ -520,8 +580,33 @@ describe('a model beyond the corpus, as Java loads it', () => {
javaFile('foto.jpg', 'jpg', javaBase64(bytesOf(100))),
javaFile('index.HTML', 'html', '<p>upper case extension</p>'),
javaFile('roh', '', javaBase64(bytesOf(3))),
{ directory: 'webserver', children: [javaFile('index.html', 'html', '<p>own</p>')] },
{ directory: 'peer2peer', children: [] },
],
}),
// In the order of Java's key.
applications: [
{ key: 'filius.software.clientserver.ClientBaustein', class: 'filius.software.clientserver.ClientBaustein', zielIPAdresse: '10.0.0.9', zielPort: 7777 },
{ key: 'filius.software.clientserver.ServerBaustein', class: 'filius.software.clientserver.ServerBaustein', aktiv: true, port: 7777 },
{ key: 'filius.software.dateiaustausch.PeerToPeerAnwendung', class: 'filius.software.dateiaustausch.PeerToPeerAnwendung', maxTeilnehmerZahl: 7 },
{ key: 'filius.software.dns.DNSServer', class: 'filius.software.dns.DNSServer', aktiv: true, port: 53, recursiveResolutionEnabled: true },
{
key: 'filius.software.firewall.Firewall',
class: 'filius.software.firewall.Firewall',
activated: false,
defaultPolicy: 1,
dropICMP: false,
filterSYNSegmentsOnly: true,
filterUdp: true,
rules: [['10.0.0.0', '255.0.0.0', '', '', 22, 6, 1]],
},
{ key: 'filius.software.lokal.FileExplorer', class: 'filius.software.lokal.FileExplorer' },
{ key: 'filius.software.lokal.ImageViewer', class: 'filius.software.lokal.ImageViewer' },
{ key: 'filius.software.lokal.Terminal', class: 'filius.software.lokal.Terminal' },
{ key: 'filius.software.lokal.TextEditor', class: 'filius.software.lokal.TextEditor' },
{ key: 'filius.software.www.WebBrowser', class: 'filius.software.www.WebBrowser' },
{ key: 'filius.software.www.WebServer', class: 'filius.software.www.WebServer', aktiv: true, port: 8080, useVHost: true },
],
},
},
{
@@ -540,7 +625,29 @@ describe('a model beyond the corpus, as Java loads it', () => {
useIpAsName: false,
useMacAsName: true,
ports: [card('10.0.0.2', '255.255.255.0', stableGeneratedMacAddress('n0'), '', '', true)],
system: { ssid: 'net', manualRoutes: [], dhcpClient: false, dhcpServer: JAVA_DHCP_DEFAULTS, fileSystem: { directory: 'root', children: [] } },
system: {
ssid: 'net',
manualRoutes: [],
dhcpClient: false,
dhcpServer: JAVA_DHCP_DEFAULTS,
// Made by the peer-to-peer application.
fileSystem: { directory: 'root', children: [{ directory: 'peer2peer', children: [] }] },
applications: [
// Without a setting: no target (null), the model's 3 peers, Java's personal firewall.
{ key: 'filius.software.clientserver.ClientBaustein', class: 'filius.software.clientserver.ClientBaustein', zielIPAdresse: null, zielPort: 55_555 },
{ key: 'filius.software.dateiaustausch.PeerToPeerAnwendung', class: 'filius.software.dateiaustausch.PeerToPeerAnwendung', maxTeilnehmerZahl: 3 },
{
key: 'filius.software.firewall.Firewall',
class: 'filius.software.firewall.Firewall',
activated: true,
defaultPolicy: 0,
dropICMP: false,
filterSYNSegmentsOnly: true,
filterUdp: true,
rules: [],
},
],
},
},
{
class: 'Vermittlungsrechner',
File diff suppressed because it is too large Load Diff
+10 -2
View File
@@ -140,7 +140,7 @@ function systemOf(project: ImportedProject, nodeId: Id) {
const web = project.webServerConfigurations.get(nodeId);
if (web !== undefined) {
// Swift serves the same files from /www after moving them there.
result.webServer = { port: web.port, documentRoot: web.documentRoot === '/webserver' ? '/www' : web.documentRoot, virtualHosts: false };
result.webServer = { port: web.port, documentRoot: web.documentRoot === '/webserver' ? '/www' : web.documentRoot, virtualHosts: web.virtualHosts };
}
const gnutella = project.gnutellaConfigurations.get(nodeId);
if (gnutella !== undefined) result.gnutella = gnutella;
@@ -330,7 +330,15 @@ const unnumberedMailbox = (numbered: string[]) => (node: SwiftNode) => {
},
};
};
/** Swift reads a web server's useVHost as off. */
const servesVirtualHosts = (node: SwiftNode) => ({
...node,
system: { ...node.system, webServer: { ...(node.system.webServer as object), virtualHosts: true } },
});
const SWIFT_ERRORS: Record<string, Record<number, (node: SwiftNode) => SwiftNode>> = {
'ipad-fixtures/Public_and_Private_Networks_2_EN.fls': { 2: servesVirtualHosts },
'java-examples/Public_and_Private_Networks_2_EN.fls': { 2: servesVirtualHosts },
'java-examples/private_und_public_networks_mit_provider_DE.fls': { 6: servesVirtualHosts },
'java-examples/man-in-the-middle.fls': { 5: unnumberedMailbox(['1', '2']) },
'java-examples/multicast_server.fls': {
// useIPAsName is `<object idref="Boolean0"/>`, the shared result of a
@@ -894,7 +902,7 @@ describe('importProject rules', () => {
expect([...project.installedPrograms.get(notebook!)!].sort()).toEqual(['commandPrompt', 'echoServer', 'firewall', 'simpleClient', 'webServer']);
expect([...project.activePrograms.get(notebook!)!]).toEqual(['webServer']);
expect(project.webServerConfigurations.get(notebook!)).toEqual({ port: 8080, documentRoot: '/webserver' });
expect(project.webServerConfigurations.get(notebook!)).toEqual({ port: 8080, documentRoot: '/webserver', virtualHosts: false });
expect(project.echoServerConfigurations.get(notebook!)).toEqual({ port: 7 });
expect(project.simpleClientConfigurations.get(notebook!)).toEqual({ targetAddress: '10.0.0.9', targetPort: 7 });
expect(project.firewallConfigurationsByNodeId.get(notebook!)).toMatchObject({ isActive: true, defaultPolicy: 'accept' });