Files
mdg-filius/packages/core/src/runtime/snapshot.ts
T
julian.kiedaischandClaude Opus 5.5 016f9461df feat(core): the command line with its local commands after Java, measured against Java
The terminal splits a line as Java's window does and runs ls, cd, pwd,
mkdir, touch, cp, mv, rm, cat, test, help, ipconfig and route on the
node's files, which are the project's own. Its texts are generated from
the bundle Java loads. TerminalCases feeds Java's Terminal without a
network (16 cases, de and en); eight cases name Java bugs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 20:32:35 +00:00

177 lines
8.4 KiB
TypeScript

// Ported from filius-ipad (GPLv2/GPLv3):
// State/TopologyNetworkRuntimeEngine.swift (TopologyNetworkRuntimeTopologySnapshot)
// What the runtime starts from: the topology and every configuration, frozen
// at the moment the simulation starts. Later protocol slices add their
// configuration maps here (NAT, remote link, wireless).
import { compareIds, type Id } from '../ids.js';
import type { NodeKind } from '../model/node.js';
import { canonicalMacAddress } from '../model/node.js';
import type {
DeviceConfiguration,
DhcpClientConfiguration,
DhcpServerConfiguration,
DnsServerConfiguration,
EchoServerConfiguration,
FirewallConfiguration,
GatewayPortForwardingRow,
InterfaceConfiguration,
InterfaceKey,
ManualRoute,
SimpleClientConfiguration,
SwitchConfiguration,
WebServerConfiguration,
EmailServerAccount,
EmailClientConfiguration,
} from './config.js';
import type { VirtualFileSystem } from '../model/virtualFileSystem.js';
import type { PageLanguage } from './apps/routerPages.js';
/** An installed web server: its configuration, and whether it is switched on (Java: WebServer.aktiv). */
export interface WebServerSnapshot extends WebServerConfiguration {
readonly isActive: boolean;
}
/**
* An installed e-mail server (EmailServer): its domain, its accounts with
* their mailboxes, and whether it is switched on. Java's server keeps its
* accounts in /mailserver/konten.txt; the model's are what Java loads from it.
*/
export interface EmailServerSnapshot {
readonly domain: string;
readonly accounts: readonly EmailServerAccount[];
readonly isActive: boolean;
}
/** An installed echo server: its port, and whether it is switched on (Java: ServerBaustein.aktiv). */
export interface EchoServerSnapshot extends EchoServerConfiguration {
readonly isActive: boolean;
}
export interface PortSnapshot {
readonly id: Id;
readonly label: string;
/** Canonical, or undefined for a port whose MAC address is derived from its id. */
readonly macAddress?: string;
}
export function portSnapshot(id: Id, label: string, macAddress?: string): PortSnapshot {
const canonical = canonicalMacAddress(macAddress);
return canonical === undefined ? { id, label } : { id, label, macAddress: canonical };
}
export interface NodeSnapshot {
readonly id: Id;
readonly kind: NodeKind;
readonly ports: readonly PortSnapshot[];
}
export interface LinkSnapshot {
readonly id: Id;
readonly sourceNodeId: Id;
readonly sourcePortId: Id;
readonly targetNodeId: Id;
readonly targetPortId: Id;
}
export interface TopologySnapshot {
/** Sorted by id (Swift `uuidString` order). */
readonly nodes: readonly NodeSnapshot[];
/** Sorted by id (Swift `uuidString` order). */
readonly links: readonly LinkSnapshot[];
readonly deviceConfigurations: ReadonlyMap<Id, DeviceConfiguration>;
readonly interfaceConfigurations: ReadonlyMap<InterfaceKey, InterfaceConfiguration>;
readonly manualRoutesByNodeId: ReadonlyMap<Id, readonly ManualRoute[]>;
readonly ripEnabledByNodeId: ReadonlyMap<Id, boolean>;
readonly dhcpClientConfigurationsByNodeId: ReadonlyMap<Id, DhcpClientConfiguration>;
readonly dhcpServerConfigurationsByNodeId: ReadonlyMap<Id, DhcpServerConfiguration>;
/** The DNS servers switched on (Java: aktiv), by node. */
readonly dnsServerConfigurationsByNodeId: ReadonlyMap<Id, DnsServerConfiguration>;
readonly firewallConfigurationsByNodeId: ReadonlyMap<Id, FirewallConfiguration>;
/** A home router's port forwarding (Java GatewayFirmware.staticNAT), by node. */
readonly portForwardingRowsByNodeId: ReadonlyMap<Id, readonly GatewayPortForwardingRow[]>;
readonly switchConfigurationsByNodeId: ReadonlyMap<Id, SwitchConfiguration>;
/** Nodes whose IP forwarding differs from their kind's (forwardsIPv4ByDefault): a host that forwards. */
readonly ipForwardingByNodeId: ReadonlyMap<Id, boolean>;
/** The installed echo servers (ServerBaustein), by node. */
readonly echoServersByNodeId: ReadonlyMap<Id, EchoServerSnapshot>;
/** The installed generic clients (ClientBaustein) with the server they last talked to, by node. */
readonly simpleClientsByNodeId: ReadonlyMap<Id, SimpleClientConfiguration>;
/** The installed web servers (WebServer), by node. */
readonly webServersByNodeId: ReadonlyMap<Id, WebServerSnapshot>;
/** The nodes with the web browser installed (WebBrowser). */
readonly webBrowserNodeIds: ReadonlySet<Id>;
/** The installed e-mail servers, by node; a run's deliveries change their mailboxes here. */
readonly emailServersByNodeId: ReadonlyMap<Id, EmailServerSnapshot>;
/** The nodes with the command line installed (Terminal); it works on the node's files. */
readonly terminalNodeIds: ReadonlySet<Id>;
/** The installed e-mail programs, by node; a run's sent and fetched mails change their folders here. */
readonly emailClientsByNodeId: ReadonlyMap<Id, EmailClientConfiguration>;
/** Each node's files, which its programs read (a web server its pages). */
readonly fileSystemsByNodeId: ReadonlyMap<Id, VirtualFileSystem>;
/** The language of the pages the devices write themselves (a router's), as Java's in the locale it runs in. */
readonly language: PageLanguage;
}
export interface TopologySnapshotInput {
nodes: readonly NodeSnapshot[];
links: readonly LinkSnapshot[];
deviceConfigurations?: ReadonlyMap<Id, DeviceConfiguration>;
interfaceConfigurations?: ReadonlyMap<InterfaceKey, InterfaceConfiguration>;
manualRoutesByNodeId?: ReadonlyMap<Id, readonly ManualRoute[]>;
ripEnabledByNodeId?: ReadonlyMap<Id, boolean>;
dhcpClientConfigurationsByNodeId?: ReadonlyMap<Id, DhcpClientConfiguration>;
dhcpServerConfigurationsByNodeId?: ReadonlyMap<Id, DhcpServerConfiguration>;
dnsServerConfigurationsByNodeId?: ReadonlyMap<Id, DnsServerConfiguration>;
firewallConfigurationsByNodeId?: ReadonlyMap<Id, FirewallConfiguration>;
portForwardingRowsByNodeId?: ReadonlyMap<Id, readonly GatewayPortForwardingRow[]>;
switchConfigurationsByNodeId?: ReadonlyMap<Id, SwitchConfiguration>;
ipForwardingByNodeId?: ReadonlyMap<Id, boolean>;
echoServersByNodeId?: ReadonlyMap<Id, EchoServerSnapshot>;
simpleClientsByNodeId?: ReadonlyMap<Id, SimpleClientConfiguration>;
webServersByNodeId?: ReadonlyMap<Id, WebServerSnapshot>;
webBrowserNodeIds?: ReadonlySet<Id>;
emailServersByNodeId?: ReadonlyMap<Id, EmailServerSnapshot>;
emailClientsByNodeId?: ReadonlyMap<Id, EmailClientConfiguration>;
terminalNodeIds?: ReadonlySet<Id>;
fileSystemsByNodeId?: ReadonlyMap<Id, VirtualFileSystem>;
language?: PageLanguage;
}
export function topologySnapshot(input: TopologySnapshotInput): TopologySnapshot {
return {
nodes: [...input.nodes].sort((lhs, rhs) => compareIds(lhs.id, rhs.id)),
links: [...input.links].sort((lhs, rhs) => compareIds(lhs.id, rhs.id)),
deviceConfigurations: input.deviceConfigurations ?? new Map(),
interfaceConfigurations: input.interfaceConfigurations ?? new Map(),
manualRoutesByNodeId: input.manualRoutesByNodeId ?? new Map(),
ripEnabledByNodeId: input.ripEnabledByNodeId ?? new Map(),
dhcpClientConfigurationsByNodeId: input.dhcpClientConfigurationsByNodeId ?? new Map(),
dhcpServerConfigurationsByNodeId: input.dhcpServerConfigurationsByNodeId ?? new Map(),
dnsServerConfigurationsByNodeId: input.dnsServerConfigurationsByNodeId ?? new Map(),
firewallConfigurationsByNodeId: input.firewallConfigurationsByNodeId ?? new Map(),
portForwardingRowsByNodeId: input.portForwardingRowsByNodeId ?? new Map(),
switchConfigurationsByNodeId: input.switchConfigurationsByNodeId ?? new Map(),
ipForwardingByNodeId: input.ipForwardingByNodeId ?? new Map(),
echoServersByNodeId: input.echoServersByNodeId ?? new Map(),
simpleClientsByNodeId: input.simpleClientsByNodeId ?? new Map(),
webServersByNodeId: input.webServersByNodeId ?? new Map(),
webBrowserNodeIds: input.webBrowserNodeIds ?? new Set(),
emailServersByNodeId: input.emailServersByNodeId ?? new Map(),
emailClientsByNodeId: input.emailClientsByNodeId ?? new Map(),
terminalNodeIds: input.terminalNodeIds ?? new Set(),
fileSystemsByNodeId: input.fileSystemsByNodeId ?? new Map(),
language: input.language ?? 'de',
};
}
/** The same snapshot with some fields replaced (Swift rebuilds it field by field). */
export function withSnapshot(
snapshot: TopologySnapshot,
changes: Partial<TopologySnapshotInput>,
): TopologySnapshot {
return topologySnapshot({ ...snapshot, ...changes });
}
export const EMPTY_TOPOLOGY_SNAPSHOT: TopologySnapshot = topologySnapshot({ nodes: [], links: [] });