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:
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user