feat(fls): write the system software of a .fls, loaded by Java as the original
topologyWriter.ts now sets the system software each device's constructor makes, through its setters: a switch's SSID and learning time, a host's SSID, RIP, manual routes, DHCP client and server with static assignments, static NAT, the modem, and a router's or home router's firewall. The firewall is reached as XMLEncoder reaches it, through the web server's `konfig` plug-in: the constructor hangs the same object there and among the applications, where its key differs (Firewall on a router, NatGateway on a home router). A switch's SSID the file lacked stays unwritten, so Java rolls one as it did. Files and applications are later layers. javaBeansWriter.ts: a call can carry statements on its result (a map's value changed through get(key)), checked by Java's XMLDecoder in a new case; a short() helper for Java's short values. Checked by Java against Java: every corpus file, read and written again, loads with the same system software (without files and applications) as the original. The synthetic topology adds what the corpus lacks: a router firewall switched on with rules of every protocol (one with a null address), every flag away from its default, a modem with its own port and target, and a PC's SSID, routes and DHCP server. 16 mutation probes, each rewritten and loaded by Java, were all caught. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+3
-3
@@ -48,9 +48,9 @@ Swift-Pfade relativ zu `filius-ipad/ios/FiliusPad/TopologyEditor/` (Stand
|
|||||||
| `test/javaIp.test.ts`, `test/ip-check.*` | — | neu: `IP.ipCheck`-Fälle, Ergebnis aus Java |
|
| `test/javaIp.test.ts`, `test/ip-check.*` | — | neu: `IP.ipCheck`-Fälle, Ergebnis aus Java |
|
||||||
| `src/javaEmail.ts` | Java `EmailServer` (Konten-Bean, `kontenLaden`/`stringZuListeBenutzerkonten`), `EmailAnwendung` (Ordner, Adressbuch, `laden`), `EmailKonto`, `Email`, `AddressEntry`, `EmailUtils` | neu nach Java: E-Mail-Beans über ihre Setter, `/mailserver/konten.txt` und `/konten.txt` im Format von Javas Lesern, ohne ihre Fehler (leere Felder zählen, eine kurze Zeile fällt allein weg, Nachrichten mit leerem Text oder Betreff bleiben, `;` im Betreff bleibt, CRLF, beim Client die letzte vollständige Zeile); Empfänger älterer Dateien als Zeichenketten; gegen Java auf dem Korpus und an 43 Fällen geprüft (13 benannte Java-Fehler) |
|
| `src/javaEmail.ts` | Java `EmailServer` (Konten-Bean, `kontenLaden`/`stringZuListeBenutzerkonten`), `EmailAnwendung` (Ordner, Adressbuch, `laden`), `EmailKonto`, `Email`, `AddressEntry`, `EmailUtils` | neu nach Java: E-Mail-Beans über ihre Setter, `/mailserver/konten.txt` und `/konten.txt` im Format von Javas Lesern, ohne ihre Fehler (leere Felder zählen, eine kurze Zeile fällt allein weg, Nachrichten mit leerem Text oder Betreff bleiben, `;` im Betreff bleibt, CRLF, beim Client die letzte vollständige Zeile); Empfänger älterer Dateien als Zeichenketten; gegen Java auf dem Korpus und an 43 Fällen geprüft (13 benannte Java-Fehler) |
|
||||||
| `src/javaBeansWriter.ts` | — | neu: Schreiber für das `XMLDecoder`-Format, Gegenstück zu `javabeans.ts`; ausdrücklich typisierte Eingabe, geteilte Objekte über `id`/`idref`, was XML nicht fassen kann (und CR) als `<char code>`; von Java selbst geprüft (`BeansDump`) |
|
| `src/javaBeansWriter.ts` | — | neu: Schreiber für das `XMLDecoder`-Format, Gegenstück zu `javabeans.ts`; ausdrücklich typisierte Eingabe, geteilte Objekte über `id`/`idref`, was XML nicht fassen kann (und CR) als `<char code>`; von Java selbst geprüft (`BeansDump`) |
|
||||||
| `test/javaBeansWriter.test.ts`, `test/beans/` | — | neu: 9 Dateien des Schreibers, von Javas `XMLDecoder` gelesen und beschrieben; Erwartungen von Hand |
|
| `test/javaBeansWriter.test.ts`, `test/beans/` | — | neu: 11 Dateien des Schreibers, von Javas `XMLDecoder` gelesen und beschrieben; Erwartungen von Hand |
|
||||||
| `src/topologyWriter.ts` | Java `SzenarioVerwaltung.netzwerkSpeichern`, Konstruktoren von `NetzwerkInterface`, `Port`, `GUIKabelItem` | neu nach Java: `konfiguration.xml` aus Javas Sicht — Geräte, Schnittstellen (vom Konstruktor angelegte über Getter, weitere per `add`), Ports über Getter-Verweise, Kabel samt Zeichen-Enden (`kabelpanel`), Doku-Elemente; Kopf „Filius version: 2.14.0 (mdg-filius)“ |
|
| `src/topologyWriter.ts` | Java `SzenarioVerwaltung.netzwerkSpeichern`, Konstruktoren von `NetzwerkInterface`, `Port`, `GUIKabelItem` und der Systemsoftware | neu nach Java: `konfiguration.xml` aus Javas Sicht — Geräte, Schnittstellen (vom Konstruktor angelegte über Getter, weitere per `add`), Ports über Getter-Verweise, Kabel samt Zeichen-Enden (`kabelpanel`), Doku-Elemente; Systemsoftware über ihre Setter: SSID, Lernzeit, RIP, manuelle Routen, DHCP-Client und -Server, statische NAT, Modem, Router-Firewall (über das `konfig`-Plug-in des Webservers, wie XMLEncoder sie erreicht); Kopf „Filius version: 2.14.0 (mdg-filius)“ |
|
||||||
| `test/topologyWriter.test.ts`, `test/written.java.json` | — | neu: alle 23 Korpus-Dateien gelesen und neu geschrieben, von Java geladen — Topologie wie beim Original (Java gegen Java); dazu eine künstliche Topologie mit dem, was der Korpus nicht hat |
|
| `test/topologyWriter.test.ts`, `test/written.java.json` | — | neu: alle 23 Korpus-Dateien gelesen und neu geschrieben, von Java geladen — Topologie und Systemsoftware (ohne Dateien und Programme) wie beim Original (Java gegen Java); dazu eine künstliche Topologie mit dem, was der Korpus nicht hat (u. a. eingeschaltete Router-Firewall mit Regeln jedes Protokolls, alle Firewall-Schalter abseits der Vorgabe, Modem mit eigenem Port und Ziel) |
|
||||||
| `src/importEmail.ts` | `Persistence/TopologyProjectStore.swift` (`registerJavaEmailConfigurations`, `assignedJavaEmailMessages`) | übertragen, Eingabe ist Javas Sicht: Server-Konten aus `/mailserver/konten.txt`, ohne Datei aus der Bean-Liste (Java verlöre sie); Client-Konto aus `/konten.txt`, ohne Datei aus der älteren `kontoListe` (Filius 2.14 liest sie nicht mehr); Konten und Nachrichten bleiben, wo Swift verwirft, was seine Prüfung nicht besteht; Vor- und Nachname getrennt; Empfangszeit nur aus der Datei (Swift nummeriert); gegen Swifts Modell geprüft |
|
| `src/importEmail.ts` | `Persistence/TopologyProjectStore.swift` (`registerJavaEmailConfigurations`, `assignedJavaEmailMessages`) | übertragen, Eingabe ist Javas Sicht: Server-Konten aus `/mailserver/konten.txt`, ohne Datei aus der Bean-Liste (Java verlöre sie); Client-Konto aus `/konten.txt`, ohne Datei aus der älteren `kontoListe` (Filius 2.14 liest sie nicht mehr); Konten und Nachrichten bleiben, wo Swift verwirft, was seine Prüfung nicht besteht; Vor- und Nachname getrennt; Empfangszeit nur aus der Datei (Swift nummeriert); gegen Swifts Modell geprüft |
|
||||||
| `test/importEmail.test.ts`, `test/email-*.swift.json` | `State/TopologyEmail.swift` (`decodeJavaServer`, `decodeJavaClient`) als Vergleich | neu: dieselben 43 Kontodateien mit Swifts Leser; wie Swift bis auf benannte Fälle, in denen Swift prüft und die ganze Datei verwirft, wo Java liest |
|
| `test/importEmail.test.ts`, `test/email-*.swift.json` | `State/TopologyEmail.swift` (`decodeJavaServer`, `decodeJavaClient`) als Vergleich | neu: dieselben 43 Kontodateien mit Swifts Leser; wie Swift bis auf benannte Fälle, in denen Swift prüft und die ganze Datei verwirft, wo Java liest |
|
||||||
| `src/javaFileSystem.ts`, `src/javaString.ts` | Java `Dateisystem.holeDatei`, `String.trim`/`split`/`equalsIgnoreCase` | neu nach Java; die Groß-/Kleinschreibung gegenüber ASCII am JDK über die ganze BMP gemessen |
|
| `src/javaFileSystem.ts`, `src/javaString.ts` | Java `Dateisystem.holeDatei`, `String.trim`/`split`/`equalsIgnoreCase` | neu nach Java; die Groß-/Kleinschreibung gegenüber ASCII am JDK über die ganze BMP gemessen |
|
||||||
|
|||||||
@@ -59,14 +59,15 @@ export type BeanStatement =
|
|||||||
| { readonly set: string; readonly value: BeanValue }
|
| { readonly set: string; readonly value: BeanValue }
|
||||||
/** target.getP(), then the statements on its result; `as` names the result */
|
/** target.getP(), then the statements on its result; `as` names the result */
|
||||||
| { readonly get: string; readonly statements: readonly BeanStatement[]; readonly as?: BeanReference }
|
| { readonly get: string; readonly statements: readonly BeanStatement[]; readonly as?: BeanReference }
|
||||||
/** target.m(args) */
|
/** target.m(args), then the statements on its result */
|
||||||
| { readonly call: string; readonly args: readonly BeanValue[] }
|
| { readonly call: string; readonly args: readonly BeanValue[]; readonly statements?: readonly BeanStatement[] }
|
||||||
/** target.set(index, value) on a list */
|
/** target.set(index, value) on a list */
|
||||||
| { readonly setIndex: number; readonly value: BeanValue }
|
| { readonly setIndex: number; readonly value: BeanValue }
|
||||||
/** target.get(index), then the statements on its result; `as` names the result */
|
/** target.get(index), then the statements on its result; `as` names the result */
|
||||||
| { readonly getIndex: number; readonly statements: readonly BeanStatement[]; readonly as?: BeanReference };
|
| { readonly getIndex: number; readonly statements: readonly BeanStatement[]; readonly as?: BeanReference };
|
||||||
|
|
||||||
export const int = (value: number): BeanPrimitive => ({ kind: 'int', value });
|
export const int = (value: number): BeanPrimitive => ({ kind: 'int', value });
|
||||||
|
export const short = (value: number): BeanPrimitive => ({ kind: 'short', value });
|
||||||
export const long = (value: number | bigint): BeanPrimitive => ({ kind: 'long', value });
|
export const long = (value: number | bigint): BeanPrimitive => ({ kind: 'long', value });
|
||||||
export const double = (value: number): BeanPrimitive => ({ kind: 'double', value });
|
export const double = (value: number): BeanPrimitive => ({ kind: 'double', value });
|
||||||
export const float = (value: number): BeanPrimitive => ({ kind: 'float', value });
|
export const float = (value: number): BeanPrimitive => ({ kind: 'float', value });
|
||||||
@@ -194,12 +195,14 @@ class Writer {
|
|||||||
} else if ('get' in statement) {
|
} else if ('get' in statement) {
|
||||||
this.getter(`property="${escapeAttribute(statement.get)}"`, statement.statements, statement.as, depth, lines);
|
this.getter(`property="${escapeAttribute(statement.get)}"`, statement.statements, statement.as, depth, lines);
|
||||||
} else if ('call' in statement) {
|
} else if ('call' in statement) {
|
||||||
if (statement.args.length === 0) {
|
const inner = statement.statements ?? [];
|
||||||
|
if (statement.args.length === 0 && inner.length === 0) {
|
||||||
lines.push(`${indent}<void method="${escapeAttribute(statement.call)}"/>`);
|
lines.push(`${indent}<void method="${escapeAttribute(statement.call)}"/>`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
nested(`<void method="${escapeAttribute(statement.call)}"`, () => {
|
nested(`<void method="${escapeAttribute(statement.call)}"`, () => {
|
||||||
for (const arg of statement.args) this.value(arg, depth + 1, lines);
|
for (const arg of statement.args) this.value(arg, depth + 1, lines);
|
||||||
|
for (const each of inner) this.statement(each, depth + 1, lines);
|
||||||
});
|
});
|
||||||
} else if ('setIndex' in statement) {
|
} else if ('setIndex' in statement) {
|
||||||
nested(`<void index="${statement.setIndex}"`, () => this.value(statement.value, depth + 1, lines));
|
nested(`<void index="${statement.setIndex}"`, () => this.value(statement.value, depth + 1, lines));
|
||||||
@@ -252,7 +255,10 @@ function sharedObjects(roots: readonly BeanValue[]): Set<BeanObject | BeanArray>
|
|||||||
const shared = new Set<BeanObject | BeanArray>();
|
const shared = new Set<BeanObject | BeanArray>();
|
||||||
const visitStatement = (statement: BeanStatement): void => {
|
const visitStatement = (statement: BeanStatement): void => {
|
||||||
if ('set' in statement || 'setIndex' in statement) visit(statement.value);
|
if ('set' in statement || 'setIndex' in statement) visit(statement.value);
|
||||||
else if ('call' in statement) statement.args.forEach(visit);
|
else if ('call' in statement) {
|
||||||
|
statement.args.forEach(visit);
|
||||||
|
(statement.statements ?? []).forEach(visitStatement);
|
||||||
|
}
|
||||||
else statement.statements.forEach(visitStatement);
|
else statement.statements.forEach(visitStatement);
|
||||||
};
|
};
|
||||||
const visit = (value: BeanValue): void => {
|
const visit = (value: BeanValue): void => {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Writes `projekt/konfiguration.xml` from Java's view of a project — the
|
// Writes `projekt/konfiguration.xml` from Java's view of a project — the
|
||||||
// counterpart of topology.ts. The four values Java's netzwerkLaden reads: the
|
// counterpart of topology.ts and, for the system software, of
|
||||||
|
// systemSoftware.ts. The four values Java's netzwerkLaden reads: the
|
||||||
// version line, the list of GUIKnotenItem, of GUIKabelItem and of
|
// version line, the list of GUIKnotenItem, of GUIKabelItem and of
|
||||||
// GUIDocuItem. What a node's constructor makes (its interfaces, a switch's
|
// GUIDocuItem. What a node's constructor makes (its interfaces, a switch's
|
||||||
// ports) is reached through getters and changed there, as XMLEncoder does;
|
// ports) is reached through getters and changed there, as XMLEncoder does;
|
||||||
@@ -7,28 +8,42 @@
|
|||||||
// objects of the nodes, so the same cable joins the same ports when Java
|
// objects of the nodes, so the same cable joins the same ports when Java
|
||||||
// loads the file.
|
// loads the file.
|
||||||
//
|
//
|
||||||
|
// The system software is the one the node's constructor makes; its settings
|
||||||
|
// are set through getters. A router's or home router's firewall is reached
|
||||||
|
// the way XMLEncoder reaches it: through the web server's `konfig` plug-in,
|
||||||
|
// which holds the same object (its key among the installed applications
|
||||||
|
// differs: Firewall on a router, NatGateway on a home router). Files and
|
||||||
|
// applications are later layers.
|
||||||
|
//
|
||||||
// Checked by Java: every corpus file, read and written again, loads in Java
|
// Checked by Java: every corpus file, read and written again, loads in Java
|
||||||
// FILIUS to the same topology (topologyWriter.test.ts, tools/java-oracle).
|
// FILIUS to the same topology and system software (topologyWriter.test.ts,
|
||||||
|
// tools/java-oracle).
|
||||||
|
|
||||||
import {
|
import {
|
||||||
encodeJavaBeans,
|
encodeJavaBeans,
|
||||||
int,
|
int,
|
||||||
|
long,
|
||||||
object,
|
object,
|
||||||
reference,
|
reference,
|
||||||
type BeanObject,
|
type BeanObject,
|
||||||
type BeanReference,
|
type BeanReference,
|
||||||
type BeanStatement,
|
type BeanStatement,
|
||||||
type BeanValue,
|
type BeanValue,
|
||||||
|
short,
|
||||||
} from './javaBeansWriter.js';
|
} from './javaBeansWriter.js';
|
||||||
|
import type { FlsFirewall, FlsSystemSoftware } from './systemSoftware.js';
|
||||||
import type { FlsDocumentationItem, FlsInterface, FlsLink, FlsNode, FlsNodeClass } from './topology.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. */
|
/** 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)';
|
export const WRITTEN_VERSION_LINE = 'Filius version: 2.14.0 (mdg-filius)';
|
||||||
|
|
||||||
|
/** The system software's settings; its files and applications are later layers. */
|
||||||
|
export type SystemToWrite = Omit<FlsSystemSoftware, 'fileSystem' | 'applications'>;
|
||||||
|
|
||||||
export type NodeToWrite = Pick<
|
export type NodeToWrite = Pick<
|
||||||
FlsNode,
|
FlsNode,
|
||||||
'javaClass' | 'name' | 'x' | 'y' | 'useIpAsName' | 'useMacAsName' | 'interfaces' | 'portCount'
|
'javaClass' | 'name' | 'x' | 'y' | 'useIpAsName' | 'useMacAsName' | 'interfaces' | 'portCount'
|
||||||
>;
|
> & { readonly system: SystemToWrite };
|
||||||
|
|
||||||
export interface TopologyToWrite {
|
export interface TopologyToWrite {
|
||||||
readonly nodes: readonly NodeToWrite[];
|
readonly nodes: readonly NodeToWrite[];
|
||||||
@@ -124,9 +139,107 @@ function device(node: NodeToWrite, ports: BeanValue[]): BeanObject {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
statements.push({ get: 'systemSoftware', statements: systemStatements(node.system, node.javaClass) });
|
||||||
return object(`filius.hardware.knoten.${node.javaClass}`, statements);
|
return object(`filius.hardware.knoten.${node.javaClass}`, statements);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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[] {
|
||||||
|
const statements: BeanStatement[] = [];
|
||||||
|
if (system.firewall !== undefined) {
|
||||||
|
statements.push({
|
||||||
|
get: 'installierteAnwendungen',
|
||||||
|
statements: [
|
||||||
|
{
|
||||||
|
call: 'get',
|
||||||
|
args: [WEB_SERVER_CLASS],
|
||||||
|
statements: [
|
||||||
|
{ get: 'plugins', statements: [{ call: 'get', args: ['konfig'], statements: [{ get: 'firewall', statements: firewallStatements(system.firewall) }] }] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (system.dhcpServer !== undefined) {
|
||||||
|
const server = system.dhcpServer;
|
||||||
|
statements.push({
|
||||||
|
get: 'DHCPServer',
|
||||||
|
statements: [
|
||||||
|
{ set: 'aktiv', value: server.isActive },
|
||||||
|
{ set: 'untergrenze', value: server.lowerBound },
|
||||||
|
{ set: 'obergrenze', value: server.upperBound },
|
||||||
|
{ set: 'gatewayip', value: server.gateway },
|
||||||
|
{ set: 'dnsserverip', value: server.dnsServer },
|
||||||
|
{ set: 'ownSettings', value: server.useOwnSettings },
|
||||||
|
{
|
||||||
|
set: 'staticAssignedAddresses',
|
||||||
|
value: object(
|
||||||
|
'java.util.ArrayList',
|
||||||
|
server.staticAssignedAddresses.map(({ mac, ip }) => ({ call: 'add', args: [`${mac} ${ip}`] })),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (system.dhcpClient !== undefined) statements.push({ set: 'DHCPKonfiguration', value: system.dhcpClient });
|
||||||
|
if (system.manualRoutes !== undefined) {
|
||||||
|
statements.push({ get: 'weiterleitungstabelle', statements: [{ set: 'manuelleTabelle', value: rows(system.manualRoutes) }] });
|
||||||
|
}
|
||||||
|
if (system.ripEnabled !== undefined) statements.push({ set: 'ripEnabled', value: system.ripEnabled });
|
||||||
|
if (system.retentionTime !== undefined) statements.push({ set: 'retentionTime', value: long(system.retentionTime) });
|
||||||
|
if (system.staticNAT !== undefined) statements.push({ set: 'staticNAT', value: rows(system.staticNAT) });
|
||||||
|
if (system.modem !== undefined) {
|
||||||
|
statements.push(
|
||||||
|
{ set: 'mode', value: int(system.modem.mode) },
|
||||||
|
{ set: 'port', value: int(system.modem.port) },
|
||||||
|
{ set: 'ipAdresse', value: system.modem.ipAddress },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// SwitchFirmware.setSSID, Betriebssystem.setSsid.
|
||||||
|
if (system.ssid !== undefined) statements.push({ set: javaClass === 'Switch' ? 'SSID' : 'ssid', value: system.ssid });
|
||||||
|
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] })));
|
||||||
|
}
|
||||||
|
|
||||||
|
function firewallStatements(firewall: FlsFirewall): BeanStatement[] {
|
||||||
|
return [
|
||||||
|
{ set: 'activated', value: firewall.activated },
|
||||||
|
{ set: 'defaultPolicy', value: short(firewall.defaultPolicy) },
|
||||||
|
{ set: 'dropICMP', value: firewall.dropICMP },
|
||||||
|
{ set: 'filterSYNSegmentsOnly', value: firewall.filterSYNSegmentsOnly },
|
||||||
|
{ set: 'filterUdp', value: firewall.filterUdp },
|
||||||
|
{
|
||||||
|
set: 'ruleset',
|
||||||
|
value: object(
|
||||||
|
'java.util.Vector',
|
||||||
|
firewall.rules.map((rule) => ({
|
||||||
|
call: 'add',
|
||||||
|
args: [
|
||||||
|
object('filius.software.firewall.FirewallRule', [], [
|
||||||
|
rule.srcIP,
|
||||||
|
rule.srcMask,
|
||||||
|
rule.destIP,
|
||||||
|
rule.destMask,
|
||||||
|
int(rule.port),
|
||||||
|
short(rule.protocol),
|
||||||
|
short(rule.action),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list the constructor filled with `constructed` elements: those are
|
* A list the constructor filled with `constructed` elements: those are
|
||||||
* changed through `get(index)`, more are added. With fewer, the list is set
|
* changed through `get(index)`, more are added. With fewer, the list is set
|
||||||
|
|||||||
@@ -316,7 +316,7 @@
|
|||||||
"string": "10.0.0.1"
|
"string": "10.0.0.1"
|
||||||
},
|
},
|
||||||
"mac": {
|
"mac": {
|
||||||
"string": "8E:A8:7E:96:38:62"
|
"string": "EE:8A:4E:46:17:34"
|
||||||
},
|
},
|
||||||
"port": {
|
"port": {
|
||||||
"id": 2,
|
"id": 2,
|
||||||
@@ -337,6 +337,34 @@
|
|||||||
],
|
],
|
||||||
"errors": []
|
"errors": []
|
||||||
},
|
},
|
||||||
|
"results.xml": {
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"class": "java.util.HashMap",
|
||||||
|
"entries": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"string": "key"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"class": "java.util.ArrayList",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"string": "first"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"string": "second"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"errors": []
|
||||||
|
},
|
||||||
"shared.xml": {
|
"shared.xml": {
|
||||||
"values": [
|
"values": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<java version="17" class="java.beans.XMLDecoder">
|
||||||
|
<object class="java.util.HashMap">
|
||||||
|
<void method="put">
|
||||||
|
<string>key</string>
|
||||||
|
<object class="java.util.ArrayList">
|
||||||
|
<void method="add">
|
||||||
|
<string>first</string>
|
||||||
|
</void>
|
||||||
|
</object>
|
||||||
|
</void>
|
||||||
|
<void method="get">
|
||||||
|
<string>key</string>
|
||||||
|
<void method="add">
|
||||||
|
<string>second</string>
|
||||||
|
</void>
|
||||||
|
</void>
|
||||||
|
</object>
|
||||||
|
</java>
|
||||||
@@ -82,6 +82,18 @@ const CASES: Record<string, { values: BeanValue[]; expected: unknown[] }> = {
|
|||||||
{ class: 'java.util.HashMap', entries: [[text('key'), { int: 8 }]] },
|
{ class: 'java.util.HashMap', entries: [[text('key'), { int: 8 }]] },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
// A method's result changed by the statements inside its call: a map's
|
||||||
|
// value reached through get(key), as XMLEncoder reaches what a
|
||||||
|
// constructor put into a map.
|
||||||
|
results: {
|
||||||
|
values: [
|
||||||
|
object('java.util.HashMap', [
|
||||||
|
{ call: 'put', args: ['key', list('java.util.ArrayList', ['first'])] },
|
||||||
|
{ call: 'get', args: ['key'], statements: [{ call: 'add', args: ['second'] }] },
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
expected: [{ class: 'java.util.HashMap', entries: [[text('key'), items('java.util.ArrayList', [text('first'), text('second')])]] }],
|
||||||
|
},
|
||||||
shared: {
|
shared: {
|
||||||
values: [list('java.util.LinkedList', [inner, other, inner, other]), file, file],
|
values: [list('java.util.LinkedList', [inner, other, inner, other]), file, file],
|
||||||
expected: [
|
expected: [
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
// Every corpus file, read and written again: Java FILIUS loads the written
|
// Every corpus file, read and written again: Java FILIUS loads the written
|
||||||
// file (tools/java-oracle, written.java.json) to the same topology as the
|
// file (tools/java-oracle, written.java.json) to the same topology as the
|
||||||
// original (corpus.java.json) — devices, names, positions, interfaces,
|
// original (corpus.java.json) — devices, names, positions, interfaces,
|
||||||
// cables and documentation. Java against Java: no mapping of this package
|
// system software (without files and applications), cables and
|
||||||
// stands between the two.
|
// documentation. Java against Java: no mapping of this package stands
|
||||||
|
// between the two.
|
||||||
//
|
//
|
||||||
// UPDATE_WRITTEN=1 writes the files to test/written/ (not checked in); the
|
// UPDATE_WRITTEN=1 writes the files to test/written/ (not checked in); the
|
||||||
// Java oracle then loads them and records each file's SHA-256, which must
|
// Java oracle then loads them and records each file's SHA-256, which must
|
||||||
@@ -28,13 +29,30 @@ function flsFiles(directory: string): string[] {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
type JavaNode = { class: string; name: string | null; bounds: number[]; useIpAsName: boolean; useMacAsName: boolean; ports: unknown };
|
type JavaNode = {
|
||||||
|
class: string;
|
||||||
|
name: string | null;
|
||||||
|
bounds: number[];
|
||||||
|
useIpAsName: boolean;
|
||||||
|
useMacAsName: boolean;
|
||||||
|
ports: unknown;
|
||||||
|
system: Record<string, unknown>;
|
||||||
|
};
|
||||||
type JavaFile = { success: boolean; nodes: JavaNode[]; cables: unknown[]; documentation: unknown[]; sha256?: string };
|
type JavaFile = { success: boolean; nodes: JavaNode[]; cables: unknown[]; documentation: unknown[]; sha256?: string };
|
||||||
|
|
||||||
const original = JSON.parse(readFileSync(join(corpusRoot, 'corpus.java.json'), 'utf8')) as Record<string, JavaFile>;
|
const original = JSON.parse(readFileSync(join(corpusRoot, 'corpus.java.json'), 'utf8')) as Record<string, JavaFile>;
|
||||||
const written = JSON.parse(readFileSync(join(here, 'written.java.json'), 'utf8')) as Record<string, JavaFile>;
|
const written = JSON.parse(readFileSync(join(here, 'written.java.json'), 'utf8')) as Record<string, JavaFile>;
|
||||||
|
|
||||||
/** The topology of Java's view: the label's position, not its size, which Java works out itself. */
|
/** The system software without its files and applications, which are later layers. */
|
||||||
|
function systemOf(system: Record<string, unknown>) {
|
||||||
|
const { fileSystem: _files, applications: _applications, ...settings } = system;
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The topology of Java's view: the label's position, not its size, which
|
||||||
|
* Java works out itself.
|
||||||
|
*/
|
||||||
function topologyOf(view: JavaFile) {
|
function topologyOf(view: JavaFile) {
|
||||||
return {
|
return {
|
||||||
success: view.success,
|
success: view.success,
|
||||||
@@ -45,6 +63,7 @@ function topologyOf(view: JavaFile) {
|
|||||||
useIpAsName: node.useIpAsName,
|
useIpAsName: node.useIpAsName,
|
||||||
useMacAsName: node.useMacAsName,
|
useMacAsName: node.useMacAsName,
|
||||||
ports: node.ports,
|
ports: node.ports,
|
||||||
|
system: systemOf(node.system),
|
||||||
})),
|
})),
|
||||||
cables: view.cables,
|
cables: view.cables,
|
||||||
documentation: view.documentation,
|
documentation: view.documentation,
|
||||||
@@ -70,7 +89,13 @@ describe('the topology written again, as Java loads it', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/** What the corpus does not have: a name set to null, useMACAsName, fewer ports than the constructor makes, more interfaces. */
|
/**
|
||||||
|
* What the corpus does not have: a name set to null, useMACAsName, fewer
|
||||||
|
* ports than the constructor makes, more interfaces; a PC's SSID, routes and
|
||||||
|
* DHCP server with static assignments; a router's firewall switched on, with
|
||||||
|
* every flag away from its default and rules of every protocol, one with a
|
||||||
|
* null address; a modem's own port and address.
|
||||||
|
*/
|
||||||
const SYNTHETIC: TopologyToWrite = {
|
const SYNTHETIC: TopologyToWrite = {
|
||||||
nodes: [
|
nodes: [
|
||||||
{
|
{
|
||||||
@@ -82,8 +107,32 @@ const SYNTHETIC: TopologyToWrite = {
|
|||||||
useMacAsName: true,
|
useMacAsName: true,
|
||||||
interfaces: [{ ip: '10.0.0.1', mask: '255.0.0.0', mac: 'AA:BB:CC:DD:EE:01', gateway: null, dns: '10.0.0.53' }],
|
interfaces: [{ ip: '10.0.0.1', mask: '255.0.0.0', mac: 'AA:BB:CC:DD:EE:01', gateway: null, dns: '10.0.0.53' }],
|
||||||
portCount: 1,
|
portCount: 1,
|
||||||
|
system: {
|
||||||
|
ssid: 'Lab',
|
||||||
|
manualRoutes: [['10.1.0.0', '255.255.0.0', '10.0.0.254', '10.0.0.1']],
|
||||||
|
dhcpClient: true,
|
||||||
|
dhcpServer: {
|
||||||
|
isActive: true,
|
||||||
|
lowerBound: '10.0.0.100',
|
||||||
|
upperBound: '10.0.0.199',
|
||||||
|
gateway: '10.0.0.254',
|
||||||
|
dnsServer: '10.0.0.53',
|
||||||
|
useOwnSettings: true,
|
||||||
|
staticAssignedAddresses: [{ mac: 'AA:BB:CC:DD:EE:77', ip: '10.0.0.77' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
javaClass: 'Switch',
|
||||||
|
name: 'S',
|
||||||
|
x: 100,
|
||||||
|
y: 20,
|
||||||
|
useIpAsName: false,
|
||||||
|
useMacAsName: false,
|
||||||
|
interfaces: [],
|
||||||
|
portCount: 3,
|
||||||
|
system: { ssid: 'S-net', retentionTime: 1234 },
|
||||||
},
|
},
|
||||||
{ javaClass: 'Switch', name: 'S', x: 100, y: 20, useIpAsName: false, useMacAsName: false, interfaces: [], portCount: 3 },
|
|
||||||
{
|
{
|
||||||
javaClass: 'Vermittlungsrechner',
|
javaClass: 'Vermittlungsrechner',
|
||||||
name: 'R',
|
name: 'R',
|
||||||
@@ -99,8 +148,34 @@ const SYNTHETIC: TopologyToWrite = {
|
|||||||
dns: '',
|
dns: '',
|
||||||
})),
|
})),
|
||||||
portCount: 3,
|
portCount: 3,
|
||||||
|
system: {
|
||||||
|
ripEnabled: true,
|
||||||
|
firewall: {
|
||||||
|
activated: true,
|
||||||
|
defaultPolicy: 1,
|
||||||
|
dropICMP: true,
|
||||||
|
filterSYNSegmentsOnly: false,
|
||||||
|
filterUdp: false,
|
||||||
|
rules: [
|
||||||
|
{ srcIP: '10.0.1.0', srcMask: '255.255.255.0', destIP: '', destMask: '', port: -1, protocol: -1, action: 1 },
|
||||||
|
{ srcIP: '', srcMask: '', destIP: '10.0.2.5', destMask: '255.255.255.255', port: 80, protocol: 6, action: 0 },
|
||||||
|
{ srcIP: null, srcMask: '', destIP: '', destMask: '', port: 53, protocol: 17, action: 1 },
|
||||||
|
{ srcIP: '', srcMask: '', destIP: '', destMask: '', port: -1, protocol: 1, action: 0 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
javaClass: 'Modem',
|
||||||
|
name: 'M',
|
||||||
|
x: 300,
|
||||||
|
y: 20,
|
||||||
|
useIpAsName: false,
|
||||||
|
useMacAsName: false,
|
||||||
|
interfaces: [],
|
||||||
|
portCount: 1,
|
||||||
|
system: { modem: { mode: 1, port: 4242, ipAddress: '192.168.1.5' } },
|
||||||
},
|
},
|
||||||
{ javaClass: 'Modem', name: 'M', x: 300, y: 20, useIpAsName: false, useMacAsName: false, interfaces: [], portCount: 1 },
|
|
||||||
],
|
],
|
||||||
links: [
|
links: [
|
||||||
{ ends: [{ node: 0, port: 0 }, { node: 1, port: 2 }], wireless: false, panel: [0, 1] },
|
{ ends: [{ node: 0, port: 0 }, { node: 1, port: 2 }], wireless: false, panel: [0, 1] },
|
||||||
@@ -109,6 +184,17 @@ const SYNTHETIC: TopologyToWrite = {
|
|||||||
documentation: [{ type: 1, text: null, x: 5, y: 6, width: 70, height: 80, color: null, font: null }],
|
documentation: [{ type: 1, text: null, x: 5, y: 6, width: 70, height: 80, color: null, font: null }],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** The DHCP server as every IP stack's constructor leaves it. */
|
||||||
|
const JAVA_DHCP_DEFAULTS = {
|
||||||
|
aktiv: false,
|
||||||
|
untergrenze: '0.0.0.0',
|
||||||
|
obergrenze: '0.0.0.0',
|
||||||
|
gatewayip: '0.0.0.0',
|
||||||
|
dnsserverip: '0.0.0.0',
|
||||||
|
ownSettings: false,
|
||||||
|
staticAssignedAddresses: [],
|
||||||
|
};
|
||||||
|
|
||||||
describe('a topology beyond the corpus, as Java loads it', () => {
|
describe('a topology beyond the corpus, as Java loads it', () => {
|
||||||
it('keeps what the corpus never sets', () => {
|
it('keeps what the corpus never sets', () => {
|
||||||
const name = 'synthetic/synthetic.fls';
|
const name = 'synthetic/synthetic.fls';
|
||||||
@@ -132,8 +218,30 @@ describe('a topology beyond the corpus, as Java loads it', () => {
|
|||||||
useIpAsName: false,
|
useIpAsName: false,
|
||||||
useMacAsName: true,
|
useMacAsName: true,
|
||||||
ports: [{ ip: '10.0.0.1', mask: '255.0.0.0', mac: 'AA:BB:CC:DD:EE:01', gateway: null, dns: '10.0.0.53' }],
|
ports: [{ ip: '10.0.0.1', mask: '255.0.0.0', mac: 'AA:BB:CC:DD:EE:01', gateway: null, dns: '10.0.0.53' }],
|
||||||
|
system: {
|
||||||
|
ssid: 'Lab',
|
||||||
|
manualRoutes: [['10.1.0.0', '255.255.0.0', '10.0.0.254', '10.0.0.1']],
|
||||||
|
dhcpClient: true,
|
||||||
|
dhcpServer: {
|
||||||
|
aktiv: true,
|
||||||
|
untergrenze: '10.0.0.100',
|
||||||
|
obergrenze: '10.0.0.199',
|
||||||
|
gatewayip: '10.0.0.254',
|
||||||
|
dnsserverip: '10.0.0.53',
|
||||||
|
ownSettings: true,
|
||||||
|
staticAssignedAddresses: ['AA:BB:CC:DD:EE:77 10.0.0.77'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class: 'Switch',
|
||||||
|
name: 'S',
|
||||||
|
position: [100, 20],
|
||||||
|
useIpAsName: undefined,
|
||||||
|
useMacAsName: undefined,
|
||||||
|
ports: [{}, {}, {}],
|
||||||
|
system: { ssid: 'S-net', retentionTime: 1234 },
|
||||||
},
|
},
|
||||||
{ class: 'Switch', name: 'S', position: [100, 20], useIpAsName: undefined, useMacAsName: undefined, ports: [{}, {}, {}] },
|
|
||||||
{
|
{
|
||||||
class: 'Vermittlungsrechner',
|
class: 'Vermittlungsrechner',
|
||||||
name: 'R',
|
name: 'R',
|
||||||
@@ -141,8 +249,35 @@ describe('a topology beyond the corpus, as Java loads it', () => {
|
|||||||
useIpAsName: undefined,
|
useIpAsName: undefined,
|
||||||
useMacAsName: undefined,
|
useMacAsName: undefined,
|
||||||
ports: ['1', '2', '3'].map(plainInterface),
|
ports: ['1', '2', '3'].map(plainInterface),
|
||||||
|
system: {
|
||||||
|
ripEnabled: true,
|
||||||
|
manualRoutes: [],
|
||||||
|
dhcpClient: false,
|
||||||
|
dhcpServer: JAVA_DHCP_DEFAULTS,
|
||||||
|
firewall: {
|
||||||
|
activated: true,
|
||||||
|
defaultPolicy: 1,
|
||||||
|
dropICMP: true,
|
||||||
|
filterSYNSegmentsOnly: false,
|
||||||
|
filterUdp: false,
|
||||||
|
rules: [
|
||||||
|
['10.0.1.0', '255.255.255.0', '', '', -1, -1, 1],
|
||||||
|
['', '', '10.0.2.5', '255.255.255.255', 80, 6, 0],
|
||||||
|
[null, '', '', '', 53, 17, 1],
|
||||||
|
['', '', '', '', -1, 1, 0],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class: 'Modem',
|
||||||
|
name: 'M',
|
||||||
|
position: [300, 20],
|
||||||
|
useIpAsName: undefined,
|
||||||
|
useMacAsName: undefined,
|
||||||
|
ports: [{}],
|
||||||
|
system: { modem: { mode: 1, port: 4242, ipAdresse: '192.168.1.5' } },
|
||||||
},
|
},
|
||||||
{ class: 'Modem', name: 'M', position: [300, 20], useIpAsName: undefined, useMacAsName: undefined, ports: [{}] },
|
|
||||||
],
|
],
|
||||||
cables: [
|
cables: [
|
||||||
{ ends: [[0, 0], [1, 2]], wireless: false, panel: [0, 1] },
|
{ ends: [[0, 0], [1, 2]], wireless: false, panel: [0, 1] },
|
||||||
|
|||||||
+416
-207
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user