feat(fls): export the editor model's system software
exportProject.ts, step X2: a switch's SSID and learning time, a host's SSID, manual routes, RIP, DHCP client and server with static assignments, a home router's port forwarding, the firewall of a router or home router (the core's names as Java's numbers), and modems. What the model has no setting for is what Java's constructors make. A remote link pair becomes modems on the port its identifier names (12345 when it names none); the first of a pair in node order serves, the others connect to localhost. Checked by Java: every corpus file, imported, exported and written, loads with the system software of the original up to two new named departures: a switch SSID Java rolls is the one the model derives from the node id, and the model keeps a modem pair, not which one serves (modem_beispiel.fls has the client first; both ways work within one project). The synthetic model now has settings away from every default, a home router with port forwarding and two modem pairs. 21 mutation probes, each loaded by Java, were all caught. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+2
-2
@@ -51,8 +51,8 @@ Swift-Pfade relativ zu `filius-ipad/ios/FiliusPad/TopologyEditor/` (Stand
|
|||||||
| `test/javaBeansWriter.test.ts`, `test/beans/` | — | neu: 11 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` 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); Dateisysteme (an die Wurzel des Konstruktors angehängt wie bei XMLEncoder, beim Router hinter dessen Vorlagen); installierte Programme nach den Dateien (Webserver, Tauschbörse und E-Mail-Server legen ihre Ordner nur an, wo sie fehlen; der DNS-Server liest `/dns/hosts` beim Installieren), je unter ihrem Schlüssel mit der Systemsoftware wie `installApp`, samt Einstellungen und persönlicher Firewall; E-Mail: Domain und Bean-Konten des Servers samt Postfächern, Ordner und Adressbuch des Clients (sein Konto steht in `/konten.txt`, einer Datei); Javas Fehler nicht übernommen: ein Empfänger, den eine ältere Datei als Zeichenkette hält, wird als der `AddressEntry` geschrieben, den Javas `AddressEntry(String)` daraus macht (Java legt die Zeichenkette ungeprüft in die typisierte Liste, sein E-Mail-Fenster scheitert daran), ein `null`-Empfänger fällt weg; 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); Dateisysteme (an die Wurzel des Konstruktors angehängt wie bei XMLEncoder, beim Router hinter dessen Vorlagen); installierte Programme nach den Dateien (Webserver, Tauschbörse und E-Mail-Server legen ihre Ordner nur an, wo sie fehlen; der DNS-Server liest `/dns/hosts` beim Installieren), je unter ihrem Schlüssel mit der Systemsoftware wie `installApp`, samt Einstellungen und persönlicher Firewall; E-Mail: Domain und Bean-Konten des Servers samt Postfächern, Ordner und Adressbuch des Clients (sein Konto steht in `/konten.txt`, einer Datei); Javas Fehler nicht übernommen: ein Empfänger, den eine ältere Datei als Zeichenkette hält, wird als der `AddressEntry` geschrieben, den Javas `AddressEntry(String)` daraus macht (Java legt die Zeichenkette ungeprüft in die typisierte Liste, sein E-Mail-Fenster scheitert daran), ein `null`-Empfänger fällt weg; 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, Systemsoftware, Dateisysteme und Programme samt E-Mail wie beim Original (bis auf die benannte Abweichung bei Empfängern als Zeichenkette, nur in `man-in-the-middle.fls`) (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, verschachtelter Dateibaum mit umbenannter Wurzel, namenlosem Ordner, Datei ohne Typ und Inhalt mit CR/CRLF und Zeichen jenseits der BMP, Heimrouter mit eigener Wurzel, jedes Filius-Programm mit Einstellungen abseits der Vorgabe samt persönlicher Firewall, von Java angelegte Ordner `/peer2peer` und `/mailserver`, E-Mail-Server und -Client mit Konto, Postfach, drei Ordnern, Adressbuch und Empfängern aller Formen) |
|
| `test/topologyWriter.test.ts`, `test/written.java.json` | — | neu: alle 23 Korpus-Dateien gelesen und neu geschrieben, von Java geladen — Topologie, Systemsoftware, Dateisysteme und Programme samt E-Mail wie beim Original (bis auf die benannte Abweichung bei Empfängern als Zeichenkette, nur in `man-in-the-middle.fls`) (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, verschachtelter Dateibaum mit umbenannter Wurzel, namenlosem Ordner, Datei ohne Typ und Inhalt mit CR/CRLF und Zeichen jenseits der BMP, Heimrouter mit eigener Wurzel, jedes Filius-Programm mit Einstellungen abseits der Vorgabe samt persönlicher Firewall, von Java angelegte Ordner `/peer2peer` und `/mailserver`, E-Mail-Server und -Client mit Konto, Postfach, drei Ordnern, Adressbuch und Empfängern aller Formen) |
|
||||||
| `src/exportProject.ts` | Gegenstück zu `importProject.ts`; Java `JHostKonfiguration.updateWifiConnection`, `InternetKnotenBetriebssystem.getStandardGateway`/`setStandardGateway` | neu: Editor-Modell → Javas Sicht für den Schreiber, Schritt X1 Topologie — Geräte, Schnittstellen (MAC wie in der Simulation: aus der Datei oder aus der Port-ID abgeleitet), Kabel samt Funk (Karte drahtlos und Switch, wie Java verbindet), Gateway und DNS an allen Schnittstellen wie Javas eigene Setter, Doku-Elemente |
|
| `src/exportProject.ts` | Gegenstück zu `importProject.ts`; Java `JHostKonfiguration.updateWifiConnection`, `InternetKnotenBetriebssystem.getStandardGateway`/`setStandardGateway` | neu: Editor-Modell → Javas Sicht für den Schreiber, Schritt X1 Topologie — Geräte, Schnittstellen (MAC wie in der Simulation: aus der Datei oder aus der Port-ID abgeleitet), Kabel samt Funk (Karte drahtlos und Switch, wie Java verbindet), Gateway und DNS an allen Schnittstellen wie Javas eigene Setter, Doku-Elemente; X2 Systemsoftware — SSIDs, Lernzeit, Routen, RIP, DHCP-Client und -Server, Portweiterleitung, Firewall (Namen des Kerns als Javas Zahlen), Modems aus den Paaren des Modells (Port aus dem Paarnamen, der erste eines Paars dient, Ziel `localhost`) |
|
||||||
| `test/exportProject.test.ts`, `test/exported.java.json` | — | neu: alle 23 Korpus-Dateien importiert, exportiert, geschrieben und von Java geladen — Topologie wie beim Original bis auf benannte Abweichungen (abgeleiteter MAC statt gewürfeltem, Doku in Zeichenreihenfolge mit Text, Schrift und Mindestgröße des Modells); dazu ein künstliches Modell, das Java ebenfalls lädt |
|
| `test/exportProject.test.ts`, `test/exported.java.json` | — | neu: alle 23 Korpus-Dateien importiert, exportiert, geschrieben und von Java geladen — Topologie wie beim Original bis auf benannte Abweichungen (abgeleiteter MAC und abgeleitete Switch-SSID statt gewürfelter, Modem-Rollen nach Reihenfolge im Paar, Doku in Zeichenreihenfolge mit Text, Schrift und Mindestgröße des Modells); dazu ein künstliches Modell, das Java ebenfalls lädt |
|
||||||
| `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 |
|
||||||
|
|||||||
@@ -7,7 +7,15 @@
|
|||||||
// becomes a cable between those, drawn between its two nodes; each
|
// becomes a cable between those, drawn between its two nodes; each
|
||||||
// documentation item a GUIDocuItem. A host whose wireless is enabled has a
|
// documentation item a GUIDocuItem. A host whose wireless is enabled has a
|
||||||
// wireless card, and its link to a switch is a wireless cable — the only
|
// wireless card, and its link to a switch is a wireless cable — the only
|
||||||
// wireless connection Java makes (JHostKonfiguration.updateWifiConnection). System software, files and programs
|
// wireless connection Java makes (JHostKonfiguration.updateWifiConnection).
|
||||||
|
//
|
||||||
|
// The system software: a switch's SSID and learning time, a host's SSID,
|
||||||
|
// manual routes, RIP, DHCP client and server, a home router's port
|
||||||
|
// forwarding, the firewall of a router or home router, and a modem. What the
|
||||||
|
// model has no setting for is what Java's constructors make. A remote link
|
||||||
|
// pair becomes modems on the port its identifier names (12345 when it names
|
||||||
|
// none): the first of a pair in node order serves, the others connect to
|
||||||
|
// localhost — the model's pairs live in one project. System software, files and programs
|
||||||
// follow in later steps.
|
// follow in later steps.
|
||||||
//
|
//
|
||||||
// What the model keeps is written as it keeps it, and so differs from the
|
// What the model keeps is written as it keeps it, and so differs from the
|
||||||
@@ -21,15 +29,28 @@
|
|||||||
// Java reads them from the first interface only (getStandardGateway,
|
// Java reads them from the first interface only (getStandardGateway,
|
||||||
// getDNSServer), and the model keeps no others;
|
// getDNSServer), and the model keeps no others;
|
||||||
// - a documentation item always has a text and a font, bold or plain: where
|
// - a documentation item always has a text and a font, bold or plain: where
|
||||||
// Java has none, the empty text and Dialog 12 the model gives it.
|
// Java has none, the empty text and Dialog 12 the model gives it;
|
||||||
|
// - a switch's SSID the file lacked (Java rolls one) is the one the model
|
||||||
|
// derives from the node's id.
|
||||||
//
|
//
|
||||||
// Checked by Java: every corpus file, imported, exported and written, loads in
|
// Checked by Java: every corpus file, imported, exported and written, loads in
|
||||||
// Java FILIUS to the topology of the original up to those departures.
|
// Java FILIUS to the topology of the original up to those departures.
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
defaultSwitchConfiguration,
|
||||||
|
dhcpServerConfiguration,
|
||||||
effectiveMacAddress,
|
effectiveMacAddress,
|
||||||
|
firewallConfiguration,
|
||||||
|
FIREWALL_PROTOCOL,
|
||||||
interfaceKey,
|
interfaceKey,
|
||||||
type DeviceConfiguration,
|
type DeviceConfiguration,
|
||||||
|
type DhcpClientConfiguration,
|
||||||
|
type DhcpServerConfiguration,
|
||||||
|
type FirewallConfiguration,
|
||||||
|
type GatewayPortForwardingRow,
|
||||||
|
type ManualRoute,
|
||||||
|
type RemoteLinkConfiguration,
|
||||||
|
type SwitchConfiguration,
|
||||||
type DocumentationItem,
|
type DocumentationItem,
|
||||||
type HostLabelPolicy,
|
type HostLabelPolicy,
|
||||||
type HostWirelessConfiguration,
|
type HostWirelessConfiguration,
|
||||||
@@ -40,8 +61,9 @@ import {
|
|||||||
type TopologyGraph,
|
type TopologyGraph,
|
||||||
type TopologyNode,
|
type TopologyNode,
|
||||||
} from '@mdg-filius/core';
|
} from '@mdg-filius/core';
|
||||||
|
import type { FlsDhcpServer, FlsFirewall } from './systemSoftware.js';
|
||||||
import type { FlsDocumentationItem, FlsInterface, FlsLink, FlsNodeClass } from './topology.js';
|
import type { FlsDocumentationItem, FlsInterface, FlsLink, FlsNodeClass } from './topology.js';
|
||||||
import type { NodeToWrite, TopologyToWrite } from './topologyWriter.js';
|
import type { NodeToWrite, SystemToWrite, TopologyToWrite } from './topologyWriter.js';
|
||||||
|
|
||||||
/** The parts of the editor model the topology is made of. */
|
/** The parts of the editor model the topology is made of. */
|
||||||
export interface ProjectToExport {
|
export interface ProjectToExport {
|
||||||
@@ -49,6 +71,14 @@ export interface ProjectToExport {
|
|||||||
readonly deviceConfigurations: ReadonlyMap<Id, DeviceConfiguration>;
|
readonly deviceConfigurations: ReadonlyMap<Id, DeviceConfiguration>;
|
||||||
readonly interfaceConfigurations: ReadonlyMap<InterfaceKey, InterfaceConfiguration>;
|
readonly interfaceConfigurations: ReadonlyMap<InterfaceKey, InterfaceConfiguration>;
|
||||||
readonly hostWirelessConfigurations: ReadonlyMap<Id, HostWirelessConfiguration>;
|
readonly hostWirelessConfigurations: ReadonlyMap<Id, HostWirelessConfiguration>;
|
||||||
|
readonly switchConfigurations: ReadonlyMap<Id, SwitchConfiguration>;
|
||||||
|
readonly manualRoutesByNodeId: ReadonlyMap<Id, readonly ManualRoute[]>;
|
||||||
|
readonly ripEnabledByNodeId: ReadonlyMap<Id, boolean>;
|
||||||
|
readonly firewallConfigurationsByNodeId: ReadonlyMap<Id, FirewallConfiguration>;
|
||||||
|
readonly dhcpClientConfigurations: ReadonlyMap<Id, DhcpClientConfiguration>;
|
||||||
|
readonly dhcpServerConfigurations: ReadonlyMap<Id, DhcpServerConfiguration>;
|
||||||
|
readonly portForwardingRowsByNodeId: ReadonlyMap<Id, readonly GatewayPortForwardingRow[]>;
|
||||||
|
readonly remoteLinkConfigurations: ReadonlyMap<Id, RemoteLinkConfiguration>;
|
||||||
readonly documentationItems: readonly DocumentationItem[];
|
readonly documentationItems: readonly DocumentationItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +104,8 @@ export function exportProject(project: ProjectToExport): ExportedTopology {
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
const indexOfNode = new Map(exported.map((node, index) => [node.id, index]));
|
const indexOfNode = new Map(exported.map((node, index) => [node.id, index]));
|
||||||
const nodes = exported.map((node) => exportNode(node, project));
|
const servingModems = firstOfEachPair(exported, project);
|
||||||
|
const nodes = exported.map((node) => exportNode(node, project, servingModems));
|
||||||
|
|
||||||
const links: FlsLink[] = [];
|
const links: FlsLink[] = [];
|
||||||
project.graph.links.forEach((link, index) => {
|
project.graph.links.forEach((link, index) => {
|
||||||
@@ -102,7 +133,7 @@ export function exportProject(project: ProjectToExport): ExportedTopology {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function exportNode(node: TopologyNode, project: ProjectToExport): NodeToWrite {
|
function exportNode(node: TopologyNode, project: ProjectToExport, servingModems: ReadonlySet<Id>): NodeToWrite {
|
||||||
const javaClass = CLASS_OF_KIND[node.kind as Exclude<NodeKind, 'unsupported'>];
|
const javaClass = CLASS_OF_KIND[node.kind as Exclude<NodeKind, 'unsupported'>];
|
||||||
const interfaces = exportInterfaces(node, project);
|
const interfaces = exportInterfaces(node, project);
|
||||||
return {
|
return {
|
||||||
@@ -114,7 +145,119 @@ function exportNode(node: TopologyNode, project: ProjectToExport): NodeToWrite {
|
|||||||
useMacAsName: showsMac(node.hostLabelPolicy),
|
useMacAsName: showsMac(node.hostLabelPolicy),
|
||||||
interfaces,
|
interfaces,
|
||||||
portCount: javaClass === 'Switch' || javaClass === 'Modem' ? node.ports.length : interfaces.length,
|
portCount: javaClass === 'Switch' || javaClass === 'Modem' ? node.ports.length : interfaces.length,
|
||||||
system: {},
|
system: exportSystem(node, project, servingModems),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The first node of each remote link pair: its modem serves. */
|
||||||
|
function firstOfEachPair(nodes: readonly TopologyNode[], project: ProjectToExport): Set<Id> {
|
||||||
|
const seen = new Set<string>();
|
||||||
|
const serving = new Set<Id>();
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (node.kind !== 'remoteLink') continue;
|
||||||
|
const pair = project.remoteLinkConfigurations.get(node.id)?.pairIdentifier ?? '';
|
||||||
|
if (seen.has(pair)) continue;
|
||||||
|
seen.add(pair);
|
||||||
|
serving.add(node.id);
|
||||||
|
}
|
||||||
|
return serving;
|
||||||
|
}
|
||||||
|
|
||||||
|
const JAVA_MODEM_PAIR = /^java-modem-port-([0-9]{1,5})$/;
|
||||||
|
const DEFAULT_MODEM_PORT = 12_345;
|
||||||
|
|
||||||
|
function modemPort(pairIdentifier: string | undefined): number {
|
||||||
|
const match = JAVA_MODEM_PAIR.exec(pairIdentifier ?? '');
|
||||||
|
const port = match === null ? DEFAULT_MODEM_PORT : Number(match[1]);
|
||||||
|
return port >= 1 && port <= 65_535 ? port : DEFAULT_MODEM_PORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportSystem(node: TopologyNode, project: ProjectToExport, servingModems: ReadonlySet<Id>): SystemToWrite {
|
||||||
|
const id = node.id;
|
||||||
|
switch (node.kind) {
|
||||||
|
case 'networkSwitch': {
|
||||||
|
const configuration = project.switchConfigurations.get(id) ?? defaultSwitchConfiguration(id);
|
||||||
|
return { ssid: configuration.ssid, retentionTime: configuration.retentionTimeMilliseconds };
|
||||||
|
}
|
||||||
|
case 'remoteLink':
|
||||||
|
return {
|
||||||
|
modem: {
|
||||||
|
mode: servingModems.has(id) ? 1 : 2,
|
||||||
|
port: modemPort(project.remoteLinkConfigurations.get(id)?.pairIdentifier),
|
||||||
|
ipAddress: 'localhost',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
case 'unsupported':
|
||||||
|
return {};
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const system: SystemToWrite = {
|
||||||
|
manualRoutes: (project.manualRoutesByNodeId.get(id) ?? []).map((route) => [
|
||||||
|
route.destinationNetwork,
|
||||||
|
route.subnetMask,
|
||||||
|
route.gateway,
|
||||||
|
route.interfaceIPAddress,
|
||||||
|
]),
|
||||||
|
dhcpClient: project.dhcpClientConfigurations.get(id)?.isEnabled === true,
|
||||||
|
dhcpServer: exportDhcpServer(project.dhcpServerConfigurations.get(id) ?? dhcpServerConfiguration()),
|
||||||
|
};
|
||||||
|
switch (node.kind) {
|
||||||
|
case 'pc':
|
||||||
|
case 'notebook': {
|
||||||
|
const ssid = project.hostWirelessConfigurations.get(id)?.ssid ?? '';
|
||||||
|
return { ...system, ssid: ssid === '' ? null : ssid };
|
||||||
|
}
|
||||||
|
case 'router':
|
||||||
|
return {
|
||||||
|
...system,
|
||||||
|
ripEnabled: project.ripEnabledByNodeId.get(id) === true,
|
||||||
|
firewall: exportFirewall(project.firewallConfigurationsByNodeId.get(id) ?? firewallConfiguration()),
|
||||||
|
};
|
||||||
|
case 'gateway':
|
||||||
|
return {
|
||||||
|
...system,
|
||||||
|
staticNAT: (project.portForwardingRowsByNodeId.get(id) ?? []).map((row) => [
|
||||||
|
row.protocolValue,
|
||||||
|
row.publicPortValue,
|
||||||
|
row.lanIPAddress,
|
||||||
|
row.lanPortValue,
|
||||||
|
]),
|
||||||
|
firewall: exportFirewall(project.firewallConfigurationsByNodeId.get(id) ?? firewallConfiguration()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportDhcpServer(server: DhcpServerConfiguration): FlsDhcpServer {
|
||||||
|
return {
|
||||||
|
isActive: server.isActive,
|
||||||
|
lowerBound: server.lowerBoundIPAddress,
|
||||||
|
upperBound: server.upperBoundIPAddress,
|
||||||
|
gateway: server.gatewayIPAddress,
|
||||||
|
dnsServer: server.dnsServerIPAddress,
|
||||||
|
useOwnSettings: server.useOwnSettings,
|
||||||
|
staticAssignedAddresses: server.staticAssignments.map((entry) => ({ mac: entry.macAddress, ip: entry.ipAddress })),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The core's names as Java's numbers: FirewallRule.DROP = 0, ACCEPT = 1. */
|
||||||
|
function exportFirewall(firewall: FirewallConfiguration): FlsFirewall {
|
||||||
|
const action = (value: 'drop' | 'accept') => (value === 'accept' ? 1 : 0);
|
||||||
|
return {
|
||||||
|
activated: firewall.isActive,
|
||||||
|
defaultPolicy: action(firewall.defaultPolicy),
|
||||||
|
dropICMP: firewall.dropIcmp,
|
||||||
|
filterSYNSegmentsOnly: firewall.filterSynSegmentsOnly,
|
||||||
|
filterUdp: firewall.filterUdp,
|
||||||
|
rules: firewall.rules.map((rule) => ({
|
||||||
|
srcIP: rule.sourceIPAddress,
|
||||||
|
srcMask: rule.sourceSubnetMask,
|
||||||
|
destIP: rule.destinationIPAddress,
|
||||||
|
destMask: rule.destinationSubnetMask,
|
||||||
|
port: rule.port,
|
||||||
|
protocol: FIREWALL_PROTOCOL[rule.protocol],
|
||||||
|
action: action(rule.action),
|
||||||
|
})),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,16 @@ import { createHash } from 'node:crypto';
|
|||||||
import { mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
import { mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
||||||
import { dirname, join, relative } from 'node:path';
|
import { dirname, join, relative } from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { DOCUMENTATION_MINIMUM_SIZE, stableGeneratedMacAddress, type Id, type TopologyNode } from '@mdg-filius/core';
|
import {
|
||||||
|
defaultSwitchConfiguration,
|
||||||
|
dhcpServerConfiguration,
|
||||||
|
DOCUMENTATION_MINIMUM_SIZE,
|
||||||
|
firewallConfiguration,
|
||||||
|
firewallRule,
|
||||||
|
stableGeneratedMacAddress,
|
||||||
|
type Id,
|
||||||
|
type TopologyNode,
|
||||||
|
} from '@mdg-filius/core';
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import {
|
import {
|
||||||
decodeJavaBeans,
|
decodeJavaBeans,
|
||||||
@@ -42,13 +51,27 @@ function counter(): () => Id {
|
|||||||
return () => `00000000-0000-0000-0000-${String(++next).padStart(12, '0')}`;
|
return () => `00000000-0000-0000-0000-${String(++next).padStart(12, '0')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 exportedPath = join(here, 'exported.java.json');
|
const exportedPath = join(here, 'exported.java.json');
|
||||||
const exported = JSON.parse(readFileSync(exportedPath, 'utf8')) as Record<string, JavaFile>;
|
const exported = JSON.parse(readFileSync(exportedPath, 'utf8')) as Record<string, JavaFile>;
|
||||||
|
|
||||||
|
/** The system software without its files and applications, later steps. */
|
||||||
|
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. */
|
/** The topology of Java's view: the label's position, not its size. */
|
||||||
function topologyOf(view: JavaFile) {
|
function topologyOf(view: JavaFile) {
|
||||||
return {
|
return {
|
||||||
@@ -60,6 +83,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,
|
||||||
@@ -73,7 +97,12 @@ function topologyOf(view: JavaFile) {
|
|||||||
* each load) has the one the simulation uses, derived from its port id;
|
* each load) has the one the simulation uses, derived from its port id;
|
||||||
* - documentation: the items in the model's render order (rectangles below
|
* - documentation: the items in the model's render order (rectangles below
|
||||||
* text), a missing text empty, a missing font Dialog 12, and at least the
|
* text), a missing text empty, a missing font Dialog 12, and at least the
|
||||||
* editor's smallest size (DOCUMENTATION_MINIMUM_SIZE).
|
* editor's smallest size (DOCUMENTATION_MINIMUM_SIZE);
|
||||||
|
* - switchSsid: a switch's SSID the file lacked (Java rolls one) is the one
|
||||||
|
* the model derives from the node's id;
|
||||||
|
* - modemRoles: the model keeps a pair of modems, not which one serves: the
|
||||||
|
* first of a pair in node order serves, the others connect. Both ways
|
||||||
|
* work within one project, where the client connects to localhost.
|
||||||
*/
|
*/
|
||||||
const DEPARTURES = {
|
const DEPARTURES = {
|
||||||
generatedMac(view: JavaFile, macs: (string | undefined)[][]): JavaFile {
|
generatedMac(view: JavaFile, macs: (string | undefined)[][]): JavaFile {
|
||||||
@@ -87,6 +116,20 @@ const DEPARTURES = {
|
|||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
switchSsid(view: JavaFile, nodeIds: Id[]): JavaFile {
|
||||||
|
return mapSystems(view, (node, index) =>
|
||||||
|
node.class === 'Switch' && node.system.ssid === null ? { ...node.system, ssid: defaultSwitchConfiguration(nodeIds[index]!).ssid } : node.system,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
modemRoles(view: JavaFile, pairs: (string | undefined)[]): JavaFile {
|
||||||
|
const seen = new Set<string | undefined>();
|
||||||
|
return mapSystems(view, (node, index) => {
|
||||||
|
if (node.class !== 'Modem') return node.system;
|
||||||
|
const serves = !seen.has(pairs[index]);
|
||||||
|
seen.add(pairs[index]);
|
||||||
|
return { ...node.system, modem: { ...(node.system.modem as object), mode: serves ? 1 : 2 } };
|
||||||
|
});
|
||||||
|
},
|
||||||
documentation(view: JavaFile): JavaFile {
|
documentation(view: JavaFile): JavaFile {
|
||||||
const items = (view.documentation as Record<string, unknown>[]).map((item) => {
|
const items = (view.documentation as Record<string, unknown>[]).map((item) => {
|
||||||
const [x, y, width, height] = item.bounds as number[];
|
const [x, y, width, height] = item.bounds as number[];
|
||||||
@@ -101,6 +144,10 @@ const DEPARTURES = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function mapSystems(view: JavaFile, system: (node: JavaNode, index: number) => Record<string, unknown>): JavaFile {
|
||||||
|
return { ...view, nodes: view.nodes.map((node, index) => ({ ...node, system: system(node, index) })) };
|
||||||
|
}
|
||||||
|
|
||||||
/** Documentation items in an order that does not depend on the list's. */
|
/** Documentation items in an order that does not depend on the list's. */
|
||||||
function sortedDocumentation(items: unknown[]): unknown[] {
|
function sortedDocumentation(items: unknown[]): unknown[] {
|
||||||
return items.map((item) => JSON.stringify(item)).sort().map((text) => JSON.parse(text) as unknown);
|
return items.map((item) => JSON.stringify(item)).sort().map((text) => JSON.parse(text) as unknown);
|
||||||
@@ -111,7 +158,12 @@ function portIds(project: ReturnType<typeof importProject>): Id[][] {
|
|||||||
return project.graph.nodes.map((node) => node.ports.map((port) => port.id));
|
return project.graph.nodes.map((node) => node.ports.map((port) => port.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
const departed = { generatedMac: new Set<string>(), documentation: new Set<string>() };
|
const departed = {
|
||||||
|
generatedMac: new Set<string>(),
|
||||||
|
documentation: new Set<string>(),
|
||||||
|
switchSsid: new Set<string>(),
|
||||||
|
modemRoles: new Set<string>(),
|
||||||
|
};
|
||||||
|
|
||||||
describe('the editor model exported, as Java loads it', () => {
|
describe('the editor model exported, as Java loads it', () => {
|
||||||
for (const file of flsFiles(corpusRoot)) {
|
for (const file of flsFiles(corpusRoot)) {
|
||||||
@@ -138,22 +190,54 @@ describe('the editor model exported, as Java loads it', () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const source = original[name]!;
|
const source = original[name]!;
|
||||||
const withMacs = DEPARTURES.generatedMac(source, macs);
|
const nodeIds = project.graph.nodes.map((node) => node.id);
|
||||||
const expected = DEPARTURES.documentation(withMacs);
|
const pairs = nodeIds.map((id) => project.remoteLinkConfigurations.get(id)?.pairIdentifier);
|
||||||
if (JSON.stringify(withMacs) !== JSON.stringify(source)) departed.generatedMac.add(name);
|
const steps: [keyof typeof departed, (view: JavaFile) => JavaFile][] = [
|
||||||
if (JSON.stringify(expected.documentation) !== JSON.stringify(source.documentation)) departed.documentation.add(name);
|
['generatedMac', (view) => DEPARTURES.generatedMac(view, macs)],
|
||||||
|
['switchSsid', (view) => DEPARTURES.switchSsid(view, nodeIds)],
|
||||||
|
['modemRoles', (view) => DEPARTURES.modemRoles(view, pairs)],
|
||||||
|
['documentation', (view) => DEPARTURES.documentation(view)],
|
||||||
|
];
|
||||||
|
let expected = source;
|
||||||
|
for (const [departure, apply] of steps) {
|
||||||
|
const next = apply(expected);
|
||||||
|
if (departure === 'documentation') {
|
||||||
|
if (JSON.stringify(next.documentation) !== JSON.stringify(expected.documentation)) departed[departure].add(name);
|
||||||
|
} else if (JSON.stringify(next) !== JSON.stringify(expected)) {
|
||||||
|
departed[departure].add(name);
|
||||||
|
}
|
||||||
|
expected = next;
|
||||||
|
}
|
||||||
expect(topologyOf({ ...java!, documentation: sortedDocumentation(java!.documentation) })).toEqual(topologyOf(expected));
|
expect(topologyOf({ ...java!, documentation: sortedDocumentation(java!.documentation) })).toEqual(topologyOf(expected));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
it('departs from Java where the model decides differently', () => {
|
it('departs from Java where the model decides differently', () => {
|
||||||
expect(departed.generatedMac.size).toBeGreaterThan(0);
|
for (const [departure, files] of Object.entries(departed)) expect(files.size, departure).toBeGreaterThan(0);
|
||||||
expect(departed.documentation.size).toBeGreaterThan(0);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── A model beyond the corpus, loaded by Java ──────────────────────────────
|
// ── A model beyond the corpus, loaded by Java ──────────────────────────────
|
||||||
|
|
||||||
|
/** A model without nodes, settings or documentation. */
|
||||||
|
function emptyProject(): ProjectToExport {
|
||||||
|
return {
|
||||||
|
graph: { nodes: [], links: [] },
|
||||||
|
deviceConfigurations: new Map(),
|
||||||
|
interfaceConfigurations: new Map(),
|
||||||
|
hostWirelessConfigurations: new Map(),
|
||||||
|
switchConfigurations: new Map(),
|
||||||
|
manualRoutesByNodeId: new Map(),
|
||||||
|
ripEnabledByNodeId: new Map(),
|
||||||
|
firewallConfigurationsByNodeId: new Map(),
|
||||||
|
dhcpClientConfigurations: new Map(),
|
||||||
|
dhcpServerConfigurations: new Map(),
|
||||||
|
portForwardingRowsByNodeId: new Map(),
|
||||||
|
remoteLinkConfigurations: new Map(),
|
||||||
|
documentationItems: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const port = (id: string) => ({ id, label: id, isOccupied: false });
|
const port = (id: string) => ({ id, label: id, isOccupied: false });
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -162,12 +246,17 @@ const port = (id: string) => ({ id, label: id, isOccupied: false });
|
|||||||
* interfaces and a gateway, a half-transparent bold text and a rectangle.
|
* interfaces and a gateway, a half-transparent bold text and a rectangle.
|
||||||
*/
|
*/
|
||||||
const SYNTHETIC: ProjectToExport = {
|
const SYNTHETIC: ProjectToExport = {
|
||||||
|
...emptyProject(),
|
||||||
graph: {
|
graph: {
|
||||||
nodes: [
|
nodes: [
|
||||||
{ id: 'pc', kind: 'pc', displayName: 'PC', hostLabelPolicy: 'ipAndMAC', position: { x: 10.6, y: 20.2 }, ports: [port('p0')] },
|
{ id: 'pc', kind: 'pc', displayName: 'PC', hostLabelPolicy: 'ipAndMAC', position: { x: 10.6, y: 20.2 }, ports: [port('p0')] },
|
||||||
{ id: 'sw', kind: 'networkSwitch', displayName: 'SW', hostLabelPolicy: 'manual', position: { x: 100, y: 20 }, ports: ['s0', 's1', 's2', 's3', 's4'].map(port) },
|
{ id: 'sw', kind: 'networkSwitch', displayName: 'SW', hostLabelPolicy: 'manual', position: { x: 100, y: 20 }, ports: ['s0', 's1', 's2', 's3', 's4'].map(port) },
|
||||||
{ id: 'nb', kind: 'notebook', displayName: 'NB', hostLabelPolicy: 'macAddress', position: { x: 100, y: 120 }, ports: [port('n0')] },
|
{ id: 'nb', kind: 'notebook', displayName: 'NB', hostLabelPolicy: 'macAddress', position: { x: 100, y: 120 }, ports: [port('n0')] },
|
||||||
{ id: 'rt', kind: 'router', displayName: 'RT', hostLabelPolicy: 'manual', position: { x: 200, y: 20 }, ports: ['r0', 'r1', 'r2'].map(port) },
|
{ id: 'rt', kind: 'router', displayName: 'RT', hostLabelPolicy: 'manual', position: { x: 200, y: 20 }, ports: ['r0', 'r1', 'r2'].map(port) },
|
||||||
|
{ id: 'gw', kind: 'gateway', displayName: 'GW', hostLabelPolicy: 'manual', position: { x: 300, y: 20 }, ports: ['g0', 'g1'].map(port) },
|
||||||
|
{ id: 'm1', kind: 'remoteLink', displayName: 'M1', hostLabelPolicy: 'manual', position: { x: 400, y: 20 }, ports: [port('m1p')] },
|
||||||
|
{ id: 'm2', kind: 'remoteLink', displayName: 'M2', hostLabelPolicy: 'manual', position: { x: 500, y: 20 }, ports: [port('m2p')] },
|
||||||
|
{ id: 'm3', kind: 'remoteLink', displayName: 'M3', hostLabelPolicy: 'manual', position: { x: 600, y: 20 }, ports: [port('m3p')] },
|
||||||
],
|
],
|
||||||
links: [
|
links: [
|
||||||
{ id: 'l1', sourceNodeId: 'pc', sourcePortId: 'p0', targetNodeId: 'sw', targetPortId: 's4' },
|
{ id: 'l1', sourceNodeId: 'pc', sourcePortId: 'p0', targetNodeId: 'sw', targetPortId: 's4' },
|
||||||
@@ -184,6 +273,56 @@ const SYNTHETIC: ProjectToExport = {
|
|||||||
['rt/r0', { ipAddress: '10.1.0.1', subnetMask: '255.255.0.0' }],
|
['rt/r0', { ipAddress: '10.1.0.1', subnetMask: '255.255.0.0' }],
|
||||||
['rt/r1', { ipAddress: '10.0.0.254', subnetMask: '255.255.255.0' }],
|
['rt/r1', { ipAddress: '10.0.0.254', subnetMask: '255.255.255.0' }],
|
||||||
['rt/r2', { ipAddress: '10.2.0.1', subnetMask: '255.255.0.0' }],
|
['rt/r2', { ipAddress: '10.2.0.1', subnetMask: '255.255.0.0' }],
|
||||||
|
['gw/g0', { ipAddress: '42.0.0.7', subnetMask: '255.0.0.0' }],
|
||||||
|
['gw/g1', { ipAddress: '192.168.1.1', subnetMask: '255.255.255.0' }],
|
||||||
|
]),
|
||||||
|
switchConfigurations: new Map([['sw', { ssid: 'S-net', retentionTimeMilliseconds: 1234 }]]),
|
||||||
|
manualRoutesByNodeId: new Map([
|
||||||
|
['pc', [{ destinationNetwork: '10.5.0.0', subnetMask: '255.255.0.0', gateway: '10.0.0.254', interfaceIPAddress: '10.0.0.1' }]],
|
||||||
|
['rt', [{ destinationNetwork: '10.6.0.0', subnetMask: '255.255.0.0', gateway: '10.1.0.2', interfaceIPAddress: '10.1.0.1' }]],
|
||||||
|
]),
|
||||||
|
ripEnabledByNodeId: new Map([['rt', true]]),
|
||||||
|
firewallConfigurationsByNodeId: new Map([
|
||||||
|
[
|
||||||
|
'rt',
|
||||||
|
firewallConfiguration({
|
||||||
|
isActive: true,
|
||||||
|
defaultPolicy: 'accept',
|
||||||
|
dropIcmp: true,
|
||||||
|
filterSynSegmentsOnly: false,
|
||||||
|
filterUdp: false,
|
||||||
|
rules: [
|
||||||
|
firewallRule({ sourceIPAddress: '10.0.0.0', sourceSubnetMask: '255.0.0.0', protocol: 'all', action: 'accept' }),
|
||||||
|
firewallRule({ destinationIPAddress: '10.2.0.5', destinationSubnetMask: '255.255.255.255', port: 80, protocol: 'tcp' }),
|
||||||
|
firewallRule({ port: 53, protocol: 'udp', action: 'accept' }),
|
||||||
|
firewallRule({ protocol: 'icmp' }),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
dhcpClientConfigurations: new Map([
|
||||||
|
['pc', { isEnabled: true }],
|
||||||
|
['gw', { isEnabled: true }],
|
||||||
|
]),
|
||||||
|
dhcpServerConfigurations: new Map([
|
||||||
|
[
|
||||||
|
'gw',
|
||||||
|
dhcpServerConfiguration({
|
||||||
|
isActive: true,
|
||||||
|
lowerBoundIPAddress: '192.168.1.100',
|
||||||
|
upperBoundIPAddress: '192.168.1.199',
|
||||||
|
gatewayIPAddress: '192.168.1.1',
|
||||||
|
dnsServerIPAddress: '8.8.8.8',
|
||||||
|
useOwnSettings: true,
|
||||||
|
staticAssignments: [{ id: 'a1', macAddress: 'AA:BB:CC:DD:EE:77', ipAddress: '192.168.1.77' }],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
portForwardingRowsByNodeId: new Map([['gw', [{ protocolValue: 'TCP', publicPortValue: '8080', lanIPAddress: '192.168.1.10', lanPortValue: '80' }]]]),
|
||||||
|
remoteLinkConfigurations: new Map([
|
||||||
|
['m1', { pairIdentifier: 'java-modem-port-4242', latencyMilliseconds: 20, isEnabled: true }],
|
||||||
|
['m2', { pairIdentifier: 'java-modem-port-4242', latencyMilliseconds: 20, isEnabled: true }],
|
||||||
|
['m3', { pairIdentifier: 'our own pair', latencyMilliseconds: 20, isEnabled: true }],
|
||||||
]),
|
]),
|
||||||
hostWirelessConfigurations: new Map([['nb', { isEnabled: true, ssid: 'net' }]]),
|
hostWirelessConfigurations: new Map([['nb', { isEnabled: true, ssid: 'net' }]]),
|
||||||
documentationItems: [
|
documentationItems: [
|
||||||
@@ -227,6 +366,15 @@ describe('a model beyond the corpus, as Java loads it', () => {
|
|||||||
);
|
);
|
||||||
expect(warnings).toEqual([]);
|
expect(warnings).toEqual([]);
|
||||||
const card = (ip: string, mask: string, mac: string, gateway: string, dns: string, wireless = false) => ({ ip, mask, mac, gateway, dns, wireless });
|
const card = (ip: string, mask: string, mac: string, gateway: string, dns: string, wireless = false) => ({ ip, mask, mac, gateway, dns, wireless });
|
||||||
|
const modem = (name: string, x: number, mode: number, port: number) => ({
|
||||||
|
class: 'Modem',
|
||||||
|
name,
|
||||||
|
position: [x, 20],
|
||||||
|
useIpAsName: undefined,
|
||||||
|
useMacAsName: undefined,
|
||||||
|
ports: [{}],
|
||||||
|
system: { modem: { mode, port, ipAdresse: 'localhost' } },
|
||||||
|
});
|
||||||
expect(topologyOf(java!)).toEqual({
|
expect(topologyOf(java!)).toEqual({
|
||||||
success: true,
|
success: true,
|
||||||
nodes: [
|
nodes: [
|
||||||
@@ -237,8 +385,22 @@ describe('a model beyond the corpus, as Java loads it', () => {
|
|||||||
useIpAsName: true,
|
useIpAsName: true,
|
||||||
useMacAsName: true,
|
useMacAsName: true,
|
||||||
ports: [card('10.0.0.1', '255.255.255.0', stableGeneratedMacAddress('p0'), '10.0.0.254', '10.0.0.53')],
|
ports: [card('10.0.0.1', '255.255.255.0', stableGeneratedMacAddress('p0'), '10.0.0.254', '10.0.0.53')],
|
||||||
|
system: {
|
||||||
|
ssid: null,
|
||||||
|
manualRoutes: [['10.5.0.0', '255.255.0.0', '10.0.0.254', '10.0.0.1']],
|
||||||
|
dhcpClient: true,
|
||||||
|
dhcpServer: JAVA_DHCP_DEFAULTS,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class: 'Switch',
|
||||||
|
name: 'SW',
|
||||||
|
position: [100, 20],
|
||||||
|
useIpAsName: undefined,
|
||||||
|
useMacAsName: undefined,
|
||||||
|
ports: [{}, {}, {}, {}, {}],
|
||||||
|
system: { ssid: 'S-net', retentionTime: 1234 },
|
||||||
},
|
},
|
||||||
{ class: 'Switch', name: 'SW', position: [100, 20], useIpAsName: undefined, useMacAsName: undefined, ports: [{}, {}, {}, {}, {}] },
|
|
||||||
{
|
{
|
||||||
class: 'Notebook',
|
class: 'Notebook',
|
||||||
name: 'NB',
|
name: 'NB',
|
||||||
@@ -246,6 +408,7 @@ describe('a model beyond the corpus, as Java loads it', () => {
|
|||||||
useIpAsName: false,
|
useIpAsName: false,
|
||||||
useMacAsName: true,
|
useMacAsName: true,
|
||||||
ports: [card('10.0.0.2', '255.255.255.0', stableGeneratedMacAddress('n0'), '', '', true)],
|
ports: [card('10.0.0.2', '255.255.255.0', stableGeneratedMacAddress('n0'), '', '', true)],
|
||||||
|
system: { ssid: 'net', manualRoutes: [], dhcpClient: false, dhcpServer: JAVA_DHCP_DEFAULTS },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
class: 'Vermittlungsrechner',
|
class: 'Vermittlungsrechner',
|
||||||
@@ -258,7 +421,56 @@ describe('a model beyond the corpus, as Java loads it', () => {
|
|||||||
card('10.0.0.254', '255.255.255.0', stableGeneratedMacAddress('r1'), '10.9.9.9', '10.0.0.53'),
|
card('10.0.0.254', '255.255.255.0', stableGeneratedMacAddress('r1'), '10.9.9.9', '10.0.0.53'),
|
||||||
card('10.2.0.1', '255.255.0.0', stableGeneratedMacAddress('r2'), '10.9.9.9', '10.0.0.53'),
|
card('10.2.0.1', '255.255.0.0', stableGeneratedMacAddress('r2'), '10.9.9.9', '10.0.0.53'),
|
||||||
],
|
],
|
||||||
|
system: {
|
||||||
|
ripEnabled: true,
|
||||||
|
manualRoutes: [['10.6.0.0', '255.255.0.0', '10.1.0.2', '10.1.0.1']],
|
||||||
|
dhcpClient: false,
|
||||||
|
dhcpServer: JAVA_DHCP_DEFAULTS,
|
||||||
|
firewall: {
|
||||||
|
activated: true,
|
||||||
|
defaultPolicy: 1,
|
||||||
|
dropICMP: true,
|
||||||
|
filterSYNSegmentsOnly: false,
|
||||||
|
filterUdp: false,
|
||||||
|
rules: [
|
||||||
|
['10.0.0.0', '255.0.0.0', '', '', -1, -1, 1],
|
||||||
|
['', '', '10.2.0.5', '255.255.255.255', 80, 6, 0],
|
||||||
|
['', '', '', '', 53, 17, 1],
|
||||||
|
['', '', '', '', -1, 1, 0],
|
||||||
|
],
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
class: 'Gateway',
|
||||||
|
name: 'GW',
|
||||||
|
position: [300, 20],
|
||||||
|
useIpAsName: undefined,
|
||||||
|
useMacAsName: undefined,
|
||||||
|
ports: [
|
||||||
|
card('42.0.0.7', '255.0.0.0', stableGeneratedMacAddress('g0'), '', ''),
|
||||||
|
card('192.168.1.1', '255.255.255.0', stableGeneratedMacAddress('g1'), '', ''),
|
||||||
|
],
|
||||||
|
system: {
|
||||||
|
manualRoutes: [],
|
||||||
|
dhcpClient: true,
|
||||||
|
dhcpServer: {
|
||||||
|
aktiv: true,
|
||||||
|
untergrenze: '192.168.1.100',
|
||||||
|
obergrenze: '192.168.1.199',
|
||||||
|
gatewayip: '192.168.1.1',
|
||||||
|
dnsserverip: '8.8.8.8',
|
||||||
|
ownSettings: true,
|
||||||
|
staticAssignedAddresses: ['AA:BB:CC:DD:EE:77 192.168.1.77'],
|
||||||
|
},
|
||||||
|
staticNAT: [['TCP', '8080', '192.168.1.10', '80']],
|
||||||
|
firewall: { activated: false, defaultPolicy: 0, dropICMP: false, filterSYNSegmentsOnly: true, filterUdp: true, rules: [] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// A pair on Java's port: the first serves; a pair of our own: Java's default port.
|
||||||
|
modem('M1', 400, 1, 4242),
|
||||||
|
modem('M2', 500, 2, 4242),
|
||||||
|
modem('M3', 600, 1, 12345),
|
||||||
],
|
],
|
||||||
cables: [
|
cables: [
|
||||||
{ ends: [[0, 0], [1, 4]], wireless: false, panel: [0, 1] },
|
{ ends: [[0, 0], [1, 4]], wireless: false, panel: [0, 1] },
|
||||||
@@ -273,6 +485,17 @@ describe('a model beyond the corpus, as Java loads it', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** 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: [],
|
||||||
|
};
|
||||||
|
|
||||||
// ── Rules the corpus does not exercise ─────────────────────────────────────
|
// ── Rules the corpus does not exercise ─────────────────────────────────────
|
||||||
|
|
||||||
describe('exportProject rules', () => {
|
describe('exportProject rules', () => {
|
||||||
@@ -293,11 +516,9 @@ describe('exportProject rules', () => {
|
|||||||
targetPortId: target[1],
|
targetPortId: target[1],
|
||||||
});
|
});
|
||||||
const project = (nodes: TopologyNode[], links: ReturnType<typeof link>[], wireless: [string, boolean][] = []): ProjectToExport => ({
|
const project = (nodes: TopologyNode[], links: ReturnType<typeof link>[], wireless: [string, boolean][] = []): ProjectToExport => ({
|
||||||
|
...emptyProject(),
|
||||||
graph: { nodes, links },
|
graph: { nodes, links },
|
||||||
deviceConfigurations: new Map(),
|
|
||||||
interfaceConfigurations: new Map(),
|
|
||||||
hostWirelessConfigurations: new Map(wireless.map(([id, isEnabled]) => [id, { isEnabled, ssid: 'net' }])),
|
hostWirelessConfigurations: new Map(wireless.map(([id, isEnabled]) => [id, { isEnabled, ssid: 'net' }])),
|
||||||
documentationItems: [],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('leaves out a node of a kind Java does not have, and the links to it', () => {
|
it('leaves out a node of a kind Java does not have, and the links to it', () => {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user