feat(fls): installed applications as Java loads them

readSystemSoftware lists a PC's or notebook's installed applications with
the settings the model needs: server applications switched on and their
port, the generic client's target, DNS recursion, web server virtual
hosts, Gnutella's peer limit and the personal firewall (on by default,
unlike a router's). Defaults the file leaves out are measured in Java.
All 23 corpus files match Java.

Two examples install an application of their own (Accumulator,
Broadcast), compiled into projekt/anwendungen; it is left out with a
warning, as this reader cannot run Java classes. It must survive a save.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-30 06:05:20 +00:00
co-authored by Claude Opus 5.5
parent be3b332655
commit c48d15def4
12 changed files with 1438 additions and 9 deletions
+1 -1
View File
@@ -39,7 +39,7 @@ Swift-Pfade relativ zu `filius-ipad/ios/FiliusPad/TopologyEditor/` (Stand
| `src/javabeans.ts`, `src/javaDefaults.json` | — | neu: XMLEncoder-Leser nach den Regeln von `java.beans.XMLDecoder` (Swift liest elementweise und verliert geteilte Werte); Getter-Standardwerte und statische Konstanten mit `tools/java-oracle` gemessen |
| `src/importProject.ts` | `Persistence/TopologyProjectStore.swift` (Knoten, Ports, Kabel, IP-Konfiguration, Switch/WLAN, Routen, RIP, Firewall, DHCP, Portweiterleitung, Modem, Dateisysteme, Doku-Elemente des Imports) | übertragen, Eingabe ist Javas Sicht: Name und Port-Anzahl wie Java, abgelehnte Adresse → Swift-Standard, Router-Standardgateway wie Java (Swift verliert es), jede Router-Firewall eingetragen, Konfigurationstexte wie in Swift getrimmt (`swiftTrim`), Rechner ohne Dateisystem leer wie in Java (Swift: Demo-Dateien); gegen Swifts importiertes Modell auf 13 Dateien geprüft |
| `test/importProject.test.ts` | — | neu: Swifts importiertes Modell (13 Dateien, zwei begründete Abweichungen), Vollständigkeit aller 23, Pings im Kern (über einen Router; über drei Router mit manuellen Routen und Router-Gateways) |
| `src/systemSoftware.ts` | Java `SwitchFirmware`, `Betriebssystem`, `VermittlungsrechnerBetriebssystem`, `GatewayFirmware`, `Weiterleitungstabelle`, `Firewall`, `FirewallRule`, `DHCPServer`, `ModemFirmware`, `Dateisystem`, `Datei` | neu nach Java: SSID, Lernzeit, RIP, manuelle Routen, Router-Firewall (an allen Stellen, an denen die Datei sie erreicht), DHCP, statische NAT, Modem, Dateisystem von Rechner und Notebook; `Character.isWhitespace` am JDK gemessen; gegen Java geprüft |
| `src/systemSoftware.ts` | Java `SwitchFirmware`, `Betriebssystem`, `VermittlungsrechnerBetriebssystem`, `GatewayFirmware`, `Weiterleitungstabelle`, `Firewall`, `FirewallRule`, `DHCPServer`, `ModemFirmware`, `Dateisystem`, `Datei` | neu nach Java: SSID, Lernzeit, RIP, manuelle Routen, Router-Firewall (an allen Stellen, an denen die Datei sie erreicht), DHCP, statische NAT, Modem, Dateisystem und installierte Programme von Rechner und Notebook; `Character.isWhitespace` am JDK gemessen; gegen Java geprüft |
| `src/javaIp.ts` | Java `IP.inetAton`/`ipCheck` | neu nach Java; an 46 Fällen mit Java gemessen |
| `src/topology.ts` | Java `SzenarioVerwaltung.netzwerkLaden`, Konstruktoren und Setter der Geräte; Swift `Persistence/TopologyProjectStore.swift` (Parser) als Vergleich | neu nach Java statt Übertragung: Geräteart aus der Klasse statt aus der Beschriftung, Geräte ohne Koordinaten bei 0,0 statt verworfen, Werte durch Javas Setter |
| `test/javabeans.test.ts` | — | neu: je XMLDecoder-Regel ein Test; alle 23 Korpus-Dateien, Klasse und Position jedes Geräts wie Java |
File diff suppressed because it is too large Load Diff
+8 -1
View File
@@ -1,5 +1,8 @@
{
"ClientBaustein#zielIPAdresse": null,
"ClientBaustein#zielPort": 55555,
"DHCPServer#port": 67,
"DNSServer#aktiv": false,
"DNSServer#port": 53,
"Gateway#maxLayerOfOperation": 2,
"JSidebarButton#debugGraphicsOptions": 0,
@@ -8,11 +11,15 @@
"JSidebarButton#iconTextGap": 4,
"JSidebarButton#verticalTextPosition": 3,
"Notebook#maxLayerOfOperation": 4,
"PeerToPeerAnwendung#maxTeilnehmerZahl": 5,
"RIPServer#port": 520,
"Rechner#maxLayerOfOperation": 4,
"ServerBaustein#aktiv": false,
"ServerBaustein#port": 55555,
"SwingConstants.CENTER": 0,
"ThreadGroup#maxPriority": 10,
"Vermittlungsrechner#maxLayerOfOperation": 2,
"WebServer#port": 80
"WebServer#aktiv": false,
"WebServer#port": 80,
"WebServer#useVHost": false
}
+9
View File
@@ -219,6 +219,15 @@ export function decodeXmlBytes(bytes: Uint8Array): string {
const PRIMITIVE_ID_PREFIXES = new Set(['Integer', 'Boolean', 'Long', 'Short', 'Byte', 'Float', 'Double', 'Character']);
const DEFAULTS = javaDefaults as Readonly<Record<string, string | number | boolean | null>>;
/**
* What a getter returns on a freshly constructed object of `simpleClass`, as
* measured (javaDefaults.json) — undefined where nothing was measured.
*/
export function javaDefault(simpleClass: string, property: string): string | number | boolean | null | undefined {
const key = `${simpleClass}#${property}`;
return Object.hasOwn(DEFAULTS, key) ? DEFAULTS[key] : undefined;
}
/** `getFoo`/`isFoo` → `foo`, as java.beans.Introspector derives it. */
function propertyOfGetter(method: string): string | undefined {
const match = /^(?:get|is)([A-Z].*)$/.exec(method);
+127 -3
View File
@@ -27,7 +27,16 @@
// not know Java's constructors and sees a placeholder per path; they are
// merged here.
import { isJavaObject, isUnresolved, listItems, mapEntries, type JavaObject, type JavaValue } from './javabeans.js';
import {
isJavaObject,
isUnresolved,
javaDefault,
listItems,
mapEntries,
simpleClassName,
type JavaObject,
type JavaValue,
} from './javabeans.js';
import type { FlsNodeClass } from './topology.js';
export interface FlsFirewallRule {
@@ -79,6 +88,55 @@ export type FlsFileTree =
/** A node whose user object is neither a name nor a file. */
| { kind: 'other'; className: string; children: FlsFileTree[] };
/** The applications FILIUS 2.14 can install (config/Desktop_*.txt). */
export const FILIUS_APPLICATION_CLASSES = [
'filius.software.clientserver.ClientBaustein',
'filius.software.clientserver.ServerBaustein',
'filius.software.dateiaustausch.PeerToPeerAnwendung',
'filius.software.dns.DNSServer',
'filius.software.email.EmailAnwendung',
'filius.software.email.EmailServer',
'filius.software.firewall.Firewall',
'filius.software.lokal.FileExplorer',
'filius.software.lokal.ImageViewer',
'filius.software.lokal.Terminal',
'filius.software.lokal.TextEditor',
'filius.software.www.WebBrowser',
'filius.software.www.WebServer',
] as const;
export type FlsApplicationClass = (typeof FILIUS_APPLICATION_CLASSES)[number];
/** Server applications (ServerAnwendung): they listen on a port and can be switched on. */
const SERVER_APPLICATIONS = new Set<string>([
'filius.software.clientserver.ServerBaustein',
'filius.software.dns.DNSServer',
'filius.software.www.WebServer',
]);
/** An installed application with the settings the model reads; e-mail contents are a later layer. */
export interface FlsApplication {
/** The key in installierteAnwendungen — the class name in every corpus file. */
key: string | null;
javaClass: FlsApplicationClass;
/** Server applications: switched on (Java's aktiv) and the port. */
isActive?: boolean;
port?: number;
/** ClientBaustein: the server it talks to (Java's zielIPAdresse, zielPort). */
targetAddress?: string | null;
targetPort?: number;
/** DNSServer: whether it asks other servers for names it does not know. */
recursiveResolutionEnabled?: boolean;
/** WebServer: serve by host name (Java's useVHost). */
useVirtualHosts?: boolean;
/** PeerToPeerAnwendung: how many peers it keeps (Java's maxTeilnehmerZahl). */
maxPeers?: number;
/** Firewall: the personal firewall. */
firewall?: FlsFirewall;
/** The application's JavaBean, for the e-mail layer. */
source: JavaObject;
}
export interface FlsSystemSoftware {
/**
* Switch: its SSID, undefined when the file has none (Java rolls one).
@@ -101,6 +159,8 @@ export interface FlsSystemSoftware {
staticNAT?: (string | null)[][];
/** Modem. */
modem?: FlsModem;
/** Rechner, Notebook: the installed applications, in the file's order. */
applications?: FlsApplication[];
/** Rechner, Notebook: the root directory (named "root" by Java's constructor). */
fileSystem?: FlsFileTree;
}
@@ -125,6 +185,7 @@ export function readSystemSoftware(device: JavaObject, javaClass: FlsNodeClass,
case 'Notebook':
result.ssid = optionalString(value('ssid'), 'ssid', warn) ?? null;
result.fileSystem = fileSystem(value('dateisystem'), warn);
result.applications = applications(value('installierteAnwendungen'), warn);
break;
case 'Vermittlungsrechner':
result.ripEnabled = boolean(value('ripEnabled'), false, 'ripEnabled', warn);
@@ -165,6 +226,60 @@ function modemAddress(value: string | null | undefined): string | null {
return value === undefined ? 'localhost' : value;
}
/**
* installierteAnwendungen: key → application. A class FILIUS does not ship —
* a project's own application, compiled into projekt/anwendungen — is left
* out: this reader cannot run Java classes.
*/
function applications(map: JavaValue | undefined, warn: Warn): FlsApplication[] {
const result: FlsApplication[] = [];
for (const [key, application] of mapEntries(map)) {
if (!isJavaObject(application)) {
warn(`application ${describe(key)} is ${describe(application)}, left out`);
continue;
}
const javaClass = application.className;
if (!(FILIUS_APPLICATION_CLASSES as readonly string[]).includes(javaClass)) {
warn(`application ${javaClass} is not part of FILIUS (a project's own application), left out`);
continue;
}
const simple = simpleClassName(javaClass);
const at = (property: string) => application.properties.get(property);
const measured = (property: string) => javaDefault(simple, property);
const entry: FlsApplication = {
key: typeof key === 'string' ? key : null,
javaClass: javaClass as FlsApplicationClass,
source: application,
};
if (SERVER_APPLICATIONS.has(javaClass)) {
entry.isActive = boolean(at('aktiv'), measured('aktiv') === true, `${simple} aktiv`, warn);
entry.port = integer(at('port'), Number(measured('port')), `${simple} port`, warn);
}
switch (javaClass) {
case 'filius.software.clientserver.ClientBaustein': {
const target = optionalString(at('zielIPAdresse'), 'ClientBaustein zielIPAdresse', warn);
entry.targetAddress = target === undefined ? null : target;
entry.targetPort = integer(at('zielPort'), Number(measured('zielPort')), 'ClientBaustein zielPort', warn);
break;
}
case 'filius.software.dns.DNSServer':
entry.recursiveResolutionEnabled = boolean(at('recursiveResolutionEnabled'), false, 'DNSServer recursion', warn);
break;
case 'filius.software.www.WebServer':
entry.useVirtualHosts = boolean(at('useVHost'), measured('useVHost') === true, 'WebServer useVHost', warn);
break;
case 'filius.software.dateiaustausch.PeerToPeerAnwendung':
entry.maxPeers = integer(at('maxTeilnehmerZahl'), Number(measured('maxTeilnehmerZahl')), 'Gnutella peers', warn);
break;
case 'filius.software.firewall.Firewall':
entry.firewall = firewallOf([application], true, warn);
break;
}
result.push(entry);
}
return result;
}
/**
* Dateisystem: its root is `arbeitsVerzeichnis` in files up to 1.10 and
* `root` after. The constructor's root is a node named "root"; a node the
@@ -282,8 +397,17 @@ function firewallObjects(software: JavaObject | undefined): JavaObject[] {
/** The firewall as the router's constructor leaves it (off, drop), then the file's settings. */
function firewall(software: JavaObject | undefined, warn: Warn): FlsFirewall {
return firewallOf(firewallObjects(software), false, warn);
}
/**
* A firewall's settings from every object the file reaches it by. `activated`
* starts as the constructor leaves it: a router's is switched off, a PC's
* personal firewall (new Firewall()) is on.
*/
function firewallOf(objects: JavaObject[], activatedByDefault: boolean, warn: Warn): FlsFirewall {
const properties = new Map<string, JavaValue>();
for (const object of firewallObjects(software)) {
for (const object of objects) {
for (const [property, value] of object.properties) {
if (properties.has(property) && properties.get(property) !== value) {
warn(`firewall: ${property} is set in two places, the later one is used`);
@@ -295,7 +419,7 @@ function firewall(software: JavaObject | undefined, warn: Warn): FlsFirewall {
// setAllowRelatedPackets is the old name of setFilterSYNSegmentsOnly.
const filterSyn = at('filterSYNSegmentsOnly') ?? at('allowRelatedPackets');
return {
activated: boolean(at('activated'), false, 'firewall activated', warn),
activated: boolean(at('activated'), activatedByDefault, 'firewall activated', warn),
defaultPolicy: integer(at('defaultPolicy'), 0, 'firewall defaultPolicy', warn),
dropICMP: boolean(at('dropICMP'), false, 'firewall dropICMP', warn),
filterSYNSegmentsOnly: boolean(filterSyn, true, 'firewall filterSYNSegmentsOnly', warn),
+16
View File
@@ -0,0 +1,16 @@
// Warnings the corpus is expected to produce, shared by the corpus tests.
/**
* Two examples install an application of their own, compiled into the
* archive (projekt/anwendungen). Java loads it from there; this reader
* cannot run it — and the Java oracle, reading only konfiguration.xml, does
* not see it either.
*/
export const EXPECTED_WARNINGS: Record<string, string[]> = {
'java-examples/anwendung_komplett.fls': [
"node 1: application filius.software.clientserver.Accumulator is not part of FILIUS (a project's own application), left out",
],
'java-examples/multicast_server.fls': [
"node 1: application filius.software.clientserver.Broadcast is not part of FILIUS (a project's own application), left out",
],
};
+3 -2
View File
@@ -33,6 +33,7 @@ import {
type FlsTopology,
type ImportedProject,
} from '../src/index.js';
import { EXPECTED_WARNINGS } from './expectedWarnings.js';
const corpusRoot = join(dirname(fileURLToPath(import.meta.url)), '../../../corpus');
interface SwiftSummary {
@@ -289,7 +290,7 @@ describe('corpus: the imported model as Swift builds it', () => {
expect(withoutApplicationDirectories(asSwiftState(project), swiftWebRoots(expected))).toEqual(
withoutApplicationDirectories(expected),
);
expect(project.warnings).toEqual([]);
expect(project.warnings).toEqual(EXPECTED_WARNINGS[name] ?? []);
});
}
});
@@ -309,7 +310,7 @@ describe('corpus: every file imports completely', () => {
expect(project.graph.nodes).toHaveLength(javaView[name]!.nodes.length);
expect(project.graph.links).toHaveLength(javaView[name]!.cables.length);
expect(project.warnings).toEqual([]);
expect(project.warnings).toEqual(EXPECTED_WARNINGS[name] ?? []);
});
}
});
+55 -1
View File
@@ -12,12 +12,14 @@ import {
decodeJavaBeans,
readFlsArchive,
readTopology,
type FlsApplication,
type FlsDhcpServer,
type FlsFileTree,
type FlsModem,
type FlsNodeClass,
type FlsTopology,
} from '../src/index.js';
import { EXPECTED_WARNINGS } from './expectedWarnings.js';
const corpusRoot = join(dirname(fileURLToPath(import.meta.url)), '../../../corpus');
const javaView = JSON.parse(readFileSync(join(corpusRoot, 'corpus.java.json'), 'utf8')) as Record<string, unknown>;
@@ -63,6 +65,35 @@ function javaFileTree(tree: FlsFileTree): unknown {
}
}
/** FlsOracle.applications' shape: Java's property names, sorted by key. */
function javaApplications(applications: FlsApplication[]): unknown[] {
return [...applications]
.sort((lhs, rhs) => ((lhs.key ?? '') < (rhs.key ?? '') ? -1 : 1))
.map((application) => ({
key: application.key,
class: application.javaClass,
...(application.isActive === undefined ? {} : { aktiv: application.isActive, port: application.port }),
...(application.targetPort === undefined
? {}
: { zielIPAdresse: application.targetAddress, zielPort: application.targetPort }),
...(application.recursiveResolutionEnabled === undefined
? {}
: { recursiveResolutionEnabled: application.recursiveResolutionEnabled }),
...(application.useVirtualHosts === undefined ? {} : { useVHost: application.useVirtualHosts }),
...(application.maxPeers === undefined ? {} : { maxTeilnehmerZahl: application.maxPeers }),
...(application.firewall === undefined
? {}
: {
activated: application.firewall.activated,
defaultPolicy: application.firewall.defaultPolicy,
dropICMP: application.firewall.dropICMP,
filterSYNSegmentsOnly: application.firewall.filterSYNSegmentsOnly,
filterUdp: application.firewall.filterUdp,
rules: application.firewall.rules.length,
}),
}));
}
/** Java's property names, as FlsOracle.java writes them. */
function javaDhcpServer(server: FlsDhcpServer) {
return {
@@ -106,6 +137,7 @@ function asJavaView(topology: FlsTopology, javaBounds: (number[] | null)[]) {
}),
...(node.system.modem === undefined ? {} : { modem: javaModem(node.system.modem) }),
...(node.system.fileSystem === undefined ? {} : { fileSystem: javaFileTree(node.system.fileSystem) }),
...(node.system.applications === undefined ? {} : { applications: javaApplications(node.system.applications) }),
...(node.system.firewall === undefined
? {}
: {
@@ -164,7 +196,7 @@ describe('corpus: the topology as Java loads it', () => {
const expected = javaView[name] as { nodes: { bounds: number[] | null }[] };
expect(asJavaView(topology, expected.nodes.map((node) => node.bounds))).toEqual(withoutTemplateFiles(expected));
expect(topology.warnings).toEqual([]);
expect(topology.warnings).toEqual(EXPECTED_WARNINGS[name] ?? []);
});
}
});
@@ -368,6 +400,28 @@ describe('readTopology rules', () => {
expect(warnings).toEqual(['node 2: file system: insert on a node is not read']);
});
it("gives installed applications Java's measured defaults for what the file leaves out", () => {
const put = (className: string, body = '') =>
`<void method="put"><string>${className}</string><object class="${className}">${body}</object></void>`;
const { nodes } = topologyOf(`<object class="java.util.LinkedList">${nodeItem(`<object class="filius.hardware.knoten.Rechner">
<void property="systemSoftware"><void property="installierteAnwendungen">
${put('filius.software.www.WebServer')}
${put('filius.software.clientserver.ClientBaustein')}
${put('filius.software.dateiaustausch.PeerToPeerAnwendung')}
${put('filius.software.firewall.Firewall')}
${put('filius.software.dns.DNSServer', '<void property="aktiv"><boolean>true</boolean></void><void property="port"><int>5353</int></void>')}
${put('filius.software.lokal.Terminal')}
</void></void></object>`)}</object>`);
const byClass = new Map(nodes[0]!.system.applications!.map((application) => [application.javaClass, application]));
expect(byClass.get('filius.software.www.WebServer')).toMatchObject({ isActive: false, port: 80, useVirtualHosts: false });
expect(byClass.get('filius.software.clientserver.ClientBaustein')).toMatchObject({ targetAddress: null, targetPort: 55_555 });
expect(byClass.get('filius.software.dateiaustausch.PeerToPeerAnwendung')).toMatchObject({ maxPeers: 4 });
expect(byClass.get('filius.software.firewall.Firewall')?.firewall).toMatchObject({ activated: true, defaultPolicy: 0 });
expect(byClass.get('filius.software.dns.DNSServer')).toMatchObject({ isActive: true, port: 5353, recursiveResolutionEnabled: false });
expect(Object.keys(byClass.get('filius.software.lokal.Terminal')!).sort()).toEqual(['javaClass', 'key', 'source']);
});
it("gives a modem Java's defaults and keeps an address the file sets to null", () => {
const { nodes } = topologyOf(`<object class="java.util.LinkedList">
${nodeItem('<object class="filius.hardware.knoten.Modem"/>')}
+6
View File
@@ -26,3 +26,9 @@ packages/fls/::obergrenze
packages/fls/::gatewayip
packages/fls/::dnsserverip
packages/fls/::adresse
# Property names of Java FILIUS's ClientBaustein (zielIPAdresse, zielPort) and
# PeerToPeerAnwendung (maxTeilnehmerZahl), as the Java oracle writes them;
# our types call them targetAddress, targetPort and maxPeers.
packages/fls/::ziel
packages/fls/::teilnehmer
packages/fls/::zahl
+4 -1
View File
@@ -30,7 +30,10 @@ virtuellen Bildschirm statt „headless".
NAT-Tabelle des Heimrouters, die Verbindung eines Modems und das
Dateisystem (Inhalte als Länge und SHA-256, damit die Referenz klein
bleibt; die Dateien von Router und Heimrouter erzeugt Javas Konstruktor aus
eigenen Vorlagen, sie stehen nicht in der Datei); Kabel (Enden als Gerät/Schnittstelle),
eigenen Vorlagen, sie stehen nicht in der Datei) und die installierten
Programme eines Rechners mit ihren Einstellungen (Klassen, die Filius
nicht mitbringt — eigene Anwendungen im Archiv unter `projekt/anwendungen`
— fehlen, weil das Werkzeug nur `konfiguration.xml` lädt); Kabel (Enden als Gerät/Schnittstelle),
Doku-Elemente.
Systemsoftware, Dateisysteme und Anwendungen folgen schrittweise.
+7
View File
@@ -50,6 +50,13 @@ HOME="$here/.build/home" xvfb-run -a java -cp "$classpath:$here/.build" GetterDe
filius.software.rip.RIPServer#port \
filius.software.www.WebServer#port \
javax.swing.SwingConstants::CENTER \
filius.software.clientserver.ServerBaustein#aktiv \
filius.software.clientserver.ClientBaustein#zielPort \
filius.software.clientserver.ClientBaustein#zielIPAdresse \
filius.software.dns.DNSServer#aktiv \
filius.software.www.WebServer#aktiv \
filius.software.www.WebServer#useVHost \
filius.software.dateiaustausch.PeerToPeerAnwendung#maxTeilnehmerZahl \
2>&1 | grep -v 'logback\|^$' || true
test -s "$defaults"
+44
View File
@@ -15,7 +15,13 @@ import filius.hardware.knoten.InternetKnoten;
import filius.hardware.knoten.Knoten;
import filius.hardware.knoten.LokalerKnoten;
import filius.rahmenprogramm.SzenarioVerwaltung;
import filius.software.Anwendung;
import filius.software.clientserver.ClientBaustein;
import filius.software.clientserver.ServerAnwendung;
import filius.software.dateiaustausch.PeerToPeerAnwendung;
import filius.software.dhcp.DHCPServer;
import filius.software.dns.DNSServer;
import filius.software.www.WebServer;
import filius.software.firewall.Firewall;
import filius.software.firewall.FirewallRule;
import filius.software.system.Betriebssystem;
@@ -239,6 +245,7 @@ public class FlsOracle {
}
json.put("manualRoutes", routes);
json.put("fileSystem", system.getDateisystem() == null ? null : fileTree(system.getDateisystem().getRoot()));
if (system instanceof Betriebssystem) json.put("applications", applications(system));
json.put("dhcpClient", system.isDHCPKonfiguration());
DHCPServer server = system.getDHCPServer();
if (server != null) {
@@ -287,6 +294,43 @@ public class FlsOracle {
return json;
}
/**
* The installed applications of a PC or notebook, by key: the class, and
* the settings the topology layer reads. E-mail accounts and messages are
* a later layer.
*/
static List<Object> applications(InternetKnotenBetriebssystem system) {
List<Object> result = new ArrayList<>();
Map<String, Anwendung> installed = new TreeMap<>(system.getInstallierteAnwendungen());
for (Map.Entry<String, Anwendung> entry : installed.entrySet()) {
Anwendung application = entry.getValue();
Map<String, Object> json = new LinkedHashMap<>();
json.put("key", entry.getKey());
json.put("class", application == null ? null : application.getClass().getName());
if (application instanceof ServerAnwendung server) {
json.put("aktiv", server.isAktiv());
json.put("port", server.getPort());
}
if (application instanceof ClientBaustein client) {
json.put("zielIPAdresse", client.getZielIPAdresse());
json.put("zielPort", client.getZielPort());
}
if (application instanceof DNSServer dns) json.put("recursiveResolutionEnabled", dns.isRecursiveResolutionEnabled());
if (application instanceof WebServer web) json.put("useVHost", web.isUseVHost());
if (application instanceof PeerToPeerAnwendung peer) json.put("maxTeilnehmerZahl", peer.getMaxTeilnehmerZahl());
if (application instanceof Firewall firewall) {
json.put("activated", firewall.isActivated());
json.put("defaultPolicy", (int) firewall.getDefaultPolicy());
json.put("dropICMP", firewall.getDropICMP());
json.put("filterSYNSegmentsOnly", firewall.getFilterSYNSegmentsOnly());
json.put("filterUdp", firewall.getFilterUdp());
json.put("rules", firewall.getRuleset().size());
}
result.add(json);
}
return result;
}
/**
* A file system tree: a directory is a node whose user object is its name,
* a file one whose user object is a Datei. Contents as SHA-256 and length