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:
@@ -7,7 +7,15 @@
|
||||
// becomes a cable between those, drawn between its two nodes; each
|
||||
// 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 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.
|
||||
//
|
||||
// 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,
|
||||
// getDNSServer), and the model keeps no others;
|
||||
// - 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
|
||||
// Java FILIUS to the topology of the original up to those departures.
|
||||
|
||||
import {
|
||||
defaultSwitchConfiguration,
|
||||
dhcpServerConfiguration,
|
||||
effectiveMacAddress,
|
||||
firewallConfiguration,
|
||||
FIREWALL_PROTOCOL,
|
||||
interfaceKey,
|
||||
type DeviceConfiguration,
|
||||
type DhcpClientConfiguration,
|
||||
type DhcpServerConfiguration,
|
||||
type FirewallConfiguration,
|
||||
type GatewayPortForwardingRow,
|
||||
type ManualRoute,
|
||||
type RemoteLinkConfiguration,
|
||||
type SwitchConfiguration,
|
||||
type DocumentationItem,
|
||||
type HostLabelPolicy,
|
||||
type HostWirelessConfiguration,
|
||||
@@ -40,8 +61,9 @@ import {
|
||||
type TopologyGraph,
|
||||
type TopologyNode,
|
||||
} from '@mdg-filius/core';
|
||||
import type { FlsDhcpServer, FlsFirewall } from './systemSoftware.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. */
|
||||
export interface ProjectToExport {
|
||||
@@ -49,6 +71,14 @@ export interface ProjectToExport {
|
||||
readonly deviceConfigurations: ReadonlyMap<Id, DeviceConfiguration>;
|
||||
readonly interfaceConfigurations: ReadonlyMap<InterfaceKey, InterfaceConfiguration>;
|
||||
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[];
|
||||
}
|
||||
|
||||
@@ -74,7 +104,8 @@ export function exportProject(project: ProjectToExport): ExportedTopology {
|
||||
return false;
|
||||
});
|
||||
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[] = [];
|
||||
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 interfaces = exportInterfaces(node, project);
|
||||
return {
|
||||
@@ -114,7 +145,119 @@ function exportNode(node: TopologyNode, project: ProjectToExport): NodeToWrite {
|
||||
useMacAsName: showsMac(node.hostLabelPolicy),
|
||||
interfaces,
|
||||
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),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user