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:
+1
-1
@@ -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/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 |
|
| `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) |
|
| `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/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 |
|
| `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 |
|
| `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
@@ -1,5 +1,8 @@
|
|||||||
{
|
{
|
||||||
|
"ClientBaustein#zielIPAdresse": null,
|
||||||
|
"ClientBaustein#zielPort": 55555,
|
||||||
"DHCPServer#port": 67,
|
"DHCPServer#port": 67,
|
||||||
|
"DNSServer#aktiv": false,
|
||||||
"DNSServer#port": 53,
|
"DNSServer#port": 53,
|
||||||
"Gateway#maxLayerOfOperation": 2,
|
"Gateway#maxLayerOfOperation": 2,
|
||||||
"JSidebarButton#debugGraphicsOptions": 0,
|
"JSidebarButton#debugGraphicsOptions": 0,
|
||||||
@@ -8,11 +11,15 @@
|
|||||||
"JSidebarButton#iconTextGap": 4,
|
"JSidebarButton#iconTextGap": 4,
|
||||||
"JSidebarButton#verticalTextPosition": 3,
|
"JSidebarButton#verticalTextPosition": 3,
|
||||||
"Notebook#maxLayerOfOperation": 4,
|
"Notebook#maxLayerOfOperation": 4,
|
||||||
|
"PeerToPeerAnwendung#maxTeilnehmerZahl": 5,
|
||||||
"RIPServer#port": 520,
|
"RIPServer#port": 520,
|
||||||
"Rechner#maxLayerOfOperation": 4,
|
"Rechner#maxLayerOfOperation": 4,
|
||||||
|
"ServerBaustein#aktiv": false,
|
||||||
"ServerBaustein#port": 55555,
|
"ServerBaustein#port": 55555,
|
||||||
"SwingConstants.CENTER": 0,
|
"SwingConstants.CENTER": 0,
|
||||||
"ThreadGroup#maxPriority": 10,
|
"ThreadGroup#maxPriority": 10,
|
||||||
"Vermittlungsrechner#maxLayerOfOperation": 2,
|
"Vermittlungsrechner#maxLayerOfOperation": 2,
|
||||||
"WebServer#port": 80
|
"WebServer#aktiv": false,
|
||||||
|
"WebServer#port": 80,
|
||||||
|
"WebServer#useVHost": false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 PRIMITIVE_ID_PREFIXES = new Set(['Integer', 'Boolean', 'Long', 'Short', 'Byte', 'Float', 'Double', 'Character']);
|
||||||
const DEFAULTS = javaDefaults as Readonly<Record<string, string | number | boolean | null>>;
|
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. */
|
/** `getFoo`/`isFoo` → `foo`, as java.beans.Introspector derives it. */
|
||||||
function propertyOfGetter(method: string): string | undefined {
|
function propertyOfGetter(method: string): string | undefined {
|
||||||
const match = /^(?:get|is)([A-Z].*)$/.exec(method);
|
const match = /^(?:get|is)([A-Z].*)$/.exec(method);
|
||||||
|
|||||||
@@ -27,7 +27,16 @@
|
|||||||
// not know Java's constructors and sees a placeholder per path; they are
|
// not know Java's constructors and sees a placeholder per path; they are
|
||||||
// merged here.
|
// 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';
|
import type { FlsNodeClass } from './topology.js';
|
||||||
|
|
||||||
export interface FlsFirewallRule {
|
export interface FlsFirewallRule {
|
||||||
@@ -79,6 +88,55 @@ export type FlsFileTree =
|
|||||||
/** A node whose user object is neither a name nor a file. */
|
/** A node whose user object is neither a name nor a file. */
|
||||||
| { kind: 'other'; className: string; children: FlsFileTree[] };
|
| { 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 {
|
export interface FlsSystemSoftware {
|
||||||
/**
|
/**
|
||||||
* Switch: its SSID, undefined when the file has none (Java rolls one).
|
* Switch: its SSID, undefined when the file has none (Java rolls one).
|
||||||
@@ -101,6 +159,8 @@ export interface FlsSystemSoftware {
|
|||||||
staticNAT?: (string | null)[][];
|
staticNAT?: (string | null)[][];
|
||||||
/** Modem. */
|
/** Modem. */
|
||||||
modem?: FlsModem;
|
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). */
|
/** Rechner, Notebook: the root directory (named "root" by Java's constructor). */
|
||||||
fileSystem?: FlsFileTree;
|
fileSystem?: FlsFileTree;
|
||||||
}
|
}
|
||||||
@@ -125,6 +185,7 @@ export function readSystemSoftware(device: JavaObject, javaClass: FlsNodeClass,
|
|||||||
case 'Notebook':
|
case 'Notebook':
|
||||||
result.ssid = optionalString(value('ssid'), 'ssid', warn) ?? null;
|
result.ssid = optionalString(value('ssid'), 'ssid', warn) ?? null;
|
||||||
result.fileSystem = fileSystem(value('dateisystem'), warn);
|
result.fileSystem = fileSystem(value('dateisystem'), warn);
|
||||||
|
result.applications = applications(value('installierteAnwendungen'), warn);
|
||||||
break;
|
break;
|
||||||
case 'Vermittlungsrechner':
|
case 'Vermittlungsrechner':
|
||||||
result.ripEnabled = boolean(value('ripEnabled'), false, 'ripEnabled', warn);
|
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;
|
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
|
* 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
|
* `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. */
|
/** The firewall as the router's constructor leaves it (off, drop), then the file's settings. */
|
||||||
function firewall(software: JavaObject | undefined, warn: Warn): FlsFirewall {
|
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>();
|
const properties = new Map<string, JavaValue>();
|
||||||
for (const object of firewallObjects(software)) {
|
for (const object of objects) {
|
||||||
for (const [property, value] of object.properties) {
|
for (const [property, value] of object.properties) {
|
||||||
if (properties.has(property) && properties.get(property) !== value) {
|
if (properties.has(property) && properties.get(property) !== value) {
|
||||||
warn(`firewall: ${property} is set in two places, the later one is used`);
|
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.
|
// setAllowRelatedPackets is the old name of setFilterSYNSegmentsOnly.
|
||||||
const filterSyn = at('filterSYNSegmentsOnly') ?? at('allowRelatedPackets');
|
const filterSyn = at('filterSYNSegmentsOnly') ?? at('allowRelatedPackets');
|
||||||
return {
|
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),
|
defaultPolicy: integer(at('defaultPolicy'), 0, 'firewall defaultPolicy', warn),
|
||||||
dropICMP: boolean(at('dropICMP'), false, 'firewall dropICMP', warn),
|
dropICMP: boolean(at('dropICMP'), false, 'firewall dropICMP', warn),
|
||||||
filterSYNSegmentsOnly: boolean(filterSyn, true, 'firewall filterSYNSegmentsOnly', warn),
|
filterSYNSegmentsOnly: boolean(filterSyn, true, 'firewall filterSYNSegmentsOnly', warn),
|
||||||
|
|||||||
@@ -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",
|
||||||
|
],
|
||||||
|
};
|
||||||
@@ -33,6 +33,7 @@ import {
|
|||||||
type FlsTopology,
|
type FlsTopology,
|
||||||
type ImportedProject,
|
type ImportedProject,
|
||||||
} from '../src/index.js';
|
} from '../src/index.js';
|
||||||
|
import { EXPECTED_WARNINGS } from './expectedWarnings.js';
|
||||||
|
|
||||||
const corpusRoot = join(dirname(fileURLToPath(import.meta.url)), '../../../corpus');
|
const corpusRoot = join(dirname(fileURLToPath(import.meta.url)), '../../../corpus');
|
||||||
interface SwiftSummary {
|
interface SwiftSummary {
|
||||||
@@ -289,7 +290,7 @@ describe('corpus: the imported model as Swift builds it', () => {
|
|||||||
expect(withoutApplicationDirectories(asSwiftState(project), swiftWebRoots(expected))).toEqual(
|
expect(withoutApplicationDirectories(asSwiftState(project), swiftWebRoots(expected))).toEqual(
|
||||||
withoutApplicationDirectories(expected),
|
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.nodes).toHaveLength(javaView[name]!.nodes.length);
|
||||||
expect(project.graph.links).toHaveLength(javaView[name]!.cables.length);
|
expect(project.graph.links).toHaveLength(javaView[name]!.cables.length);
|
||||||
expect(project.warnings).toEqual([]);
|
expect(project.warnings).toEqual(EXPECTED_WARNINGS[name] ?? []);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,12 +12,14 @@ import {
|
|||||||
decodeJavaBeans,
|
decodeJavaBeans,
|
||||||
readFlsArchive,
|
readFlsArchive,
|
||||||
readTopology,
|
readTopology,
|
||||||
|
type FlsApplication,
|
||||||
type FlsDhcpServer,
|
type FlsDhcpServer,
|
||||||
type FlsFileTree,
|
type FlsFileTree,
|
||||||
type FlsModem,
|
type FlsModem,
|
||||||
type FlsNodeClass,
|
type FlsNodeClass,
|
||||||
type FlsTopology,
|
type FlsTopology,
|
||||||
} from '../src/index.js';
|
} from '../src/index.js';
|
||||||
|
import { EXPECTED_WARNINGS } from './expectedWarnings.js';
|
||||||
|
|
||||||
const corpusRoot = join(dirname(fileURLToPath(import.meta.url)), '../../../corpus');
|
const corpusRoot = join(dirname(fileURLToPath(import.meta.url)), '../../../corpus');
|
||||||
const javaView = JSON.parse(readFileSync(join(corpusRoot, 'corpus.java.json'), 'utf8')) as Record<string, unknown>;
|
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. */
|
/** Java's property names, as FlsOracle.java writes them. */
|
||||||
function javaDhcpServer(server: FlsDhcpServer) {
|
function javaDhcpServer(server: FlsDhcpServer) {
|
||||||
return {
|
return {
|
||||||
@@ -106,6 +137,7 @@ function asJavaView(topology: FlsTopology, javaBounds: (number[] | null)[]) {
|
|||||||
}),
|
}),
|
||||||
...(node.system.modem === undefined ? {} : { modem: javaModem(node.system.modem) }),
|
...(node.system.modem === undefined ? {} : { modem: javaModem(node.system.modem) }),
|
||||||
...(node.system.fileSystem === undefined ? {} : { fileSystem: javaFileTree(node.system.fileSystem) }),
|
...(node.system.fileSystem === undefined ? {} : { fileSystem: javaFileTree(node.system.fileSystem) }),
|
||||||
|
...(node.system.applications === undefined ? {} : { applications: javaApplications(node.system.applications) }),
|
||||||
...(node.system.firewall === undefined
|
...(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 }[] };
|
const expected = javaView[name] as { nodes: { bounds: number[] | null }[] };
|
||||||
|
|
||||||
expect(asJavaView(topology, expected.nodes.map((node) => node.bounds))).toEqual(withoutTemplateFiles(expected));
|
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']);
|
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", () => {
|
it("gives a modem Java's defaults and keeps an address the file sets to null", () => {
|
||||||
const { nodes } = topologyOf(`<object class="java.util.LinkedList">
|
const { nodes } = topologyOf(`<object class="java.util.LinkedList">
|
||||||
${nodeItem('<object class="filius.hardware.knoten.Modem"/>')}
|
${nodeItem('<object class="filius.hardware.knoten.Modem"/>')}
|
||||||
|
|||||||
@@ -26,3 +26,9 @@ packages/fls/::obergrenze
|
|||||||
packages/fls/::gatewayip
|
packages/fls/::gatewayip
|
||||||
packages/fls/::dnsserverip
|
packages/fls/::dnsserverip
|
||||||
packages/fls/::adresse
|
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
|
||||||
|
|||||||
@@ -30,7 +30,10 @@ virtuellen Bildschirm statt „headless".
|
|||||||
NAT-Tabelle des Heimrouters, die Verbindung eines Modems und das
|
NAT-Tabelle des Heimrouters, die Verbindung eines Modems und das
|
||||||
Dateisystem (Inhalte als Länge und SHA-256, damit die Referenz klein
|
Dateisystem (Inhalte als Länge und SHA-256, damit die Referenz klein
|
||||||
bleibt; die Dateien von Router und Heimrouter erzeugt Javas Konstruktor aus
|
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.
|
Doku-Elemente.
|
||||||
Systemsoftware, Dateisysteme und Anwendungen folgen schrittweise.
|
Systemsoftware, Dateisysteme und Anwendungen folgen schrittweise.
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,13 @@ HOME="$here/.build/home" xvfb-run -a java -cp "$classpath:$here/.build" GetterDe
|
|||||||
filius.software.rip.RIPServer#port \
|
filius.software.rip.RIPServer#port \
|
||||||
filius.software.www.WebServer#port \
|
filius.software.www.WebServer#port \
|
||||||
javax.swing.SwingConstants::CENTER \
|
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
|
2>&1 | grep -v 'logback\|^$' || true
|
||||||
test -s "$defaults"
|
test -s "$defaults"
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,13 @@ import filius.hardware.knoten.InternetKnoten;
|
|||||||
import filius.hardware.knoten.Knoten;
|
import filius.hardware.knoten.Knoten;
|
||||||
import filius.hardware.knoten.LokalerKnoten;
|
import filius.hardware.knoten.LokalerKnoten;
|
||||||
import filius.rahmenprogramm.SzenarioVerwaltung;
|
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.dhcp.DHCPServer;
|
||||||
|
import filius.software.dns.DNSServer;
|
||||||
|
import filius.software.www.WebServer;
|
||||||
import filius.software.firewall.Firewall;
|
import filius.software.firewall.Firewall;
|
||||||
import filius.software.firewall.FirewallRule;
|
import filius.software.firewall.FirewallRule;
|
||||||
import filius.software.system.Betriebssystem;
|
import filius.software.system.Betriebssystem;
|
||||||
@@ -239,6 +245,7 @@ public class FlsOracle {
|
|||||||
}
|
}
|
||||||
json.put("manualRoutes", routes);
|
json.put("manualRoutes", routes);
|
||||||
json.put("fileSystem", system.getDateisystem() == null ? null : fileTree(system.getDateisystem().getRoot()));
|
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());
|
json.put("dhcpClient", system.isDHCPKonfiguration());
|
||||||
DHCPServer server = system.getDHCPServer();
|
DHCPServer server = system.getDHCPServer();
|
||||||
if (server != null) {
|
if (server != null) {
|
||||||
@@ -287,6 +294,43 @@ public class FlsOracle {
|
|||||||
return json;
|
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 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
|
* a file one whose user object is a Datei. Contents as SHA-256 and length
|
||||||
|
|||||||
Reference in New Issue
Block a user