test: F2 acceptance — five of FILIUS' examples run as Java FILIUS runs them

ExampleRun opens an example as FILIUS opens it (SzenarioVerwaltung.laden
into the window's lists), starts it as the action mode does, plays a
story and writes every card's frames and tables, the calls, the
addresses and the RIP tables' changes. examplesJava.test.ts reads the
same file with this package's reader and runs it in the core
(projectSnapshot, new): routing (internetwork_komplett), DHCP
(einfaches_rechnernetz_komplett), DNS (dns_server), NAT
(nat_with_mapped_port) and firewall (Internet_Komplett_..._DE) match
card by card.

Found and fixed: a host's ipForwardingEnabled, which Java saves and
obeys, was unknown to the reader, the model, the core, the writer, the
export and FlsOracle; and Java's ARP learns only from a request for its
own address or a reply. Named Java bugs: a ping without a gateway throws
a NullPointerException; a home router drops the answer of a second
remote end. Open: Java picks between equally long RIP routes by thread
timing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 15:57:10 +00:00
co-authored by Claude Opus 5.5
parent 21a404693c
commit 0abce60aef
29 changed files with 64827 additions and 36 deletions
+4
View File
@@ -77,6 +77,7 @@ import {
base64Encode,
defaultSwitchConfiguration,
dhcpServerConfiguration,
forwardsIPv4ByDefault,
effectiveMacAddress,
firewallConfiguration,
FIREWALL_PROTOCOL,
@@ -128,6 +129,8 @@ export interface ProjectToExport {
readonly switchConfigurations: ReadonlyMap<Id, SwitchConfiguration>;
readonly manualRoutesByNodeId: ReadonlyMap<Id, readonly ManualRoute[]>;
readonly ripEnabledByNodeId: ReadonlyMap<Id, boolean>;
/** Nodes whose IP forwarding differs from their kind's; none when left out. */
readonly ipForwardingByNodeId?: ReadonlyMap<Id, boolean>;
readonly firewallConfigurationsByNodeId: ReadonlyMap<Id, FirewallConfiguration>;
readonly dhcpClientConfigurations: ReadonlyMap<Id, DhcpClientConfiguration>;
readonly dhcpServerConfigurations: ReadonlyMap<Id, DhcpServerConfiguration>;
@@ -277,6 +280,7 @@ function exportSystem(node: TopologyNode, project: ProjectToExport, servingModem
route.interfaceIPAddress,
]),
dhcpClient: project.dhcpClientConfigurations.get(id)?.isEnabled === true,
ipForwardingEnabled: project.ipForwardingByNodeId?.get(id) ?? forwardsIPv4ByDefault(node.kind),
dhcpServer: exportDhcpServer(project.dhcpServerConfigurations.get(id) ?? dhcpServerConfiguration()),
};
switch (node.kind) {
+8
View File
@@ -44,6 +44,7 @@
import {
canonicalMacAddress,
forwardsIPv4ByDefault,
deepEqual,
defaultSwitchConfiguration,
dhcpServerConfiguration,
@@ -114,6 +115,8 @@ export interface ImportedProject {
manualRoutesByNodeId: Map<Id, ManualRoute[]>;
/** Only routers with RIP switched on. */
ripEnabledByNodeId: Map<Id, boolean>;
/** Nodes whose IP forwarding differs from their kind's (forwardsIPv4ByDefault). */
ipForwardingByNodeId: Map<Id, boolean>;
firewallConfigurationsByNodeId: Map<Id, FirewallConfiguration>;
dhcpClientConfigurations: Map<Id, DhcpClientConfiguration>;
/** Only servers whose settings differ from Java's constructor. */
@@ -178,6 +181,7 @@ export function importProject(
hostWirelessConfigurations: new Map(),
manualRoutesByNodeId: new Map(),
ripEnabledByNodeId: new Map(),
ipForwardingByNodeId: new Map(),
firewallConfigurationsByNodeId: new Map(),
dhcpClientConfigurations: new Map(),
dhcpServerConfigurations: new Map(),
@@ -398,6 +402,7 @@ type SystemConfigurations = Pick<
| 'hostWirelessConfigurations'
| 'manualRoutesByNodeId'
| 'ripEnabledByNodeId'
| 'ipForwardingByNodeId'
| 'firewallConfigurationsByNodeId'
| 'dhcpClientConfigurations'
| 'dhcpServerConfigurations'
@@ -616,6 +621,9 @@ function registerSystemConfigurations(
}
if (routes.length > 0) system.manualRoutesByNodeId.set(node.id, routes);
if (node.kind === 'router' && source.system.ripEnabled === true) system.ripEnabledByNodeId.set(node.id, true);
if (source.system.ipForwardingEnabled !== undefined && source.system.ipForwardingEnabled !== forwardsIPv4ByDefault(node.kind)) {
system.ipForwardingByNodeId.set(node.id, source.system.ipForwardingEnabled);
}
if ((node.kind === 'router' || node.kind === 'gateway') && source.system.firewall !== undefined) {
system.firewallConfigurationsByNodeId.set(node.id, firewallConfigurationOf(source.system.firewall, where, warnings));
}
+1
View File
@@ -11,3 +11,4 @@ export * from './topology.js';
export * from './topologyWriter.js';
export * from './zip.js';
export * from './exportProject.js';
export * from './projectSnapshot.js';
+36
View File
@@ -0,0 +1,36 @@
// An imported project as the simulation core runs it: its nodes with their
// ports (a port's MAC from the file, else derived from its id), its cables,
// and the configurations the core reads.
//
// Only the cables count: a host's SSID connects nothing at run time, as in
// Java (Swift joins a wireless host to the switch of its SSID; not taken).
// A DNS server runs only when it is switched on (Java: DNSServer.aktiv).
import { portSnapshot, topologySnapshot, type DnsServerConfiguration, type Id, type TopologySnapshot } from '@mdg-filius/core';
import type { ImportedProject } from './importProject.js';
export function projectSnapshot(project: ImportedProject): TopologySnapshot {
const dnsServers = new Map<Id, DnsServerConfiguration>();
for (const [nodeId, configuration] of project.dnsServerConfigurations) {
if (project.activePrograms.get(nodeId)?.has('dnsServer') === true) dnsServers.set(nodeId, configuration);
}
return topologySnapshot({
nodes: project.graph.nodes.map((node) => ({
id: node.id,
kind: node.kind,
ports: node.ports.map((port) => portSnapshot(port.id, port.label, port.importedMacAddress)),
})),
links: project.graph.links,
deviceConfigurations: project.deviceConfigurations,
interfaceConfigurations: project.interfaceConfigurations,
manualRoutesByNodeId: project.manualRoutesByNodeId,
ripEnabledByNodeId: project.ripEnabledByNodeId,
dhcpClientConfigurationsByNodeId: project.dhcpClientConfigurations,
dhcpServerConfigurationsByNodeId: project.dhcpServerConfigurations,
dnsServerConfigurationsByNodeId: dnsServers,
firewallConfigurationsByNodeId: project.firewallConfigurationsByNodeId,
portForwardingRowsByNodeId: project.portForwardingRowsByNodeId,
switchConfigurationsByNodeId: project.switchConfigurations,
ipForwardingByNodeId: project.ipForwardingByNodeId,
});
}
+12
View File
@@ -200,6 +200,12 @@ export interface FlsSystemSoftware {
firewall?: FlsFirewall;
/** Every node with an IP stack: whether it asks a DHCP server for its address. */
dhcpClient?: boolean;
/**
* Every node with an IP stack: whether it forwards packets for others
* (InternetKnotenBetriebssystem.ipForwardingEnabled; routers and home
* routers do from their constructor, hosts only when the file says so).
*/
ipForwardingEnabled?: boolean;
/** Every node with an IP stack. */
dhcpServer?: FlsDhcpServer;
/** Gateway: rows of protocol, public port, LAN address, LAN port — as the file has them. */
@@ -229,6 +235,11 @@ export interface FlsSystemSoftware {
type Warn = (message: string) => void;
/** Whether Java's constructor switches IP forwarding on: routers and home routers (VermittlungsrechnerBetriebssystem, GatewayFirmware). */
export function forwardsByDefault(javaClass: FlsNodeClass): boolean {
return javaClass === 'Vermittlungsrechner' || javaClass === 'Gateway';
}
const FIREWALL_CLASS = 'filius.software.firewall.Firewall';
const WEB_SERVER_CLASS = 'filius.software.www.WebServer';
@@ -263,6 +274,7 @@ export function readSystemSoftware(device: JavaObject, javaClass: FlsNodeClass,
if (javaClass !== 'Switch' && javaClass !== 'Modem') {
result.manualRoutes = manualRoutes(value('weiterleitungstabelle'), warn);
result.dhcpClient = boolean(value('DHCPKonfiguration'), false, 'DHCPKonfiguration', warn);
result.ipForwardingEnabled = boolean(value('ipForwardingEnabled'), forwardsByDefault(javaClass), 'ipForwardingEnabled', warn);
result.dhcpServer = dhcpServer(value('DHCPServer'), warn);
}
if (javaClass === 'Gateway') result.staticNAT = rowsOfStrings(value('staticNAT'));
+5 -1
View File
@@ -48,7 +48,7 @@ import {
short,
} from './javaBeansWriter.js';
import { addressEntry, type JavaAddressEntry, type JavaContact, type JavaEmail, type JavaEmailAccount, type JavaRecipient } from './javaEmail.js';
import type { FlsApplication, FlsFileTree, FlsFirewall, FlsSystemSoftware } from './systemSoftware.js';
import { forwardsByDefault, type FlsApplication, type FlsFileTree, type FlsFirewall, type FlsSystemSoftware } from './systemSoftware.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. */
@@ -239,6 +239,10 @@ function systemStatements(system: SystemToWrite, javaClass: FlsNodeClass, self:
});
}
if (system.dhcpClient !== undefined) statements.push({ set: 'DHCPKonfiguration', value: system.dhcpClient });
// Written only where it differs from what Java's constructor sets, as XMLEncoder does.
if (system.ipForwardingEnabled !== undefined && system.ipForwardingEnabled !== forwardsByDefault(javaClass)) {
statements.push({ set: 'ipForwardingEnabled', value: system.ipForwardingEnabled });
}
if (system.manualRoutes !== undefined) {
statements.push({ get: 'weiterleitungstabelle', statements: [{ set: 'manuelleTabelle', value: rows(system.manualRoutes) }] });
}