// 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; readonly interfaceConfigurations: ReadonlyMap; readonly manualRoutesByNodeId: ReadonlyMap; readonly ripEnabledByNodeId: ReadonlyMap; readonly dhcpClientConfigurationsByNodeId: ReadonlyMap; readonly dhcpServerConfigurationsByNodeId: ReadonlyMap; /** The DNS servers switched on (Java: aktiv), by node. */ readonly dnsServerConfigurationsByNodeId: ReadonlyMap; readonly firewallConfigurationsByNodeId: ReadonlyMap; /** A home router's port forwarding (Java GatewayFirmware.staticNAT), by node. */ readonly portForwardingRowsByNodeId: ReadonlyMap; readonly switchConfigurationsByNodeId: ReadonlyMap; /** Nodes whose IP forwarding differs from their kind's (forwardsIPv4ByDefault): a host that forwards. */ readonly ipForwardingByNodeId: ReadonlyMap; /** The installed echo servers (ServerBaustein), by node. */ readonly echoServersByNodeId: ReadonlyMap; /** The installed generic clients (ClientBaustein) with the server they last talked to, by node. */ readonly simpleClientsByNodeId: ReadonlyMap; /** The installed web servers (WebServer), by node. */ readonly webServersByNodeId: ReadonlyMap; /** The nodes with the web browser installed (WebBrowser). */ readonly webBrowserNodeIds: ReadonlySet; /** The installed e-mail servers, by node; a run's deliveries change their mailboxes here. */ readonly emailServersByNodeId: ReadonlyMap; /** The nodes with the command line installed (Terminal); it works on the node's files. */ readonly terminalNodeIds: ReadonlySet; /** The installed e-mail programs, by node; a run's sent and fetched mails change their folders here. */ readonly emailClientsByNodeId: ReadonlyMap; /** Each node's files, which its programs read (a web server its pages). */ readonly fileSystemsByNodeId: ReadonlyMap; /** 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; interfaceConfigurations?: ReadonlyMap; manualRoutesByNodeId?: ReadonlyMap; ripEnabledByNodeId?: ReadonlyMap; dhcpClientConfigurationsByNodeId?: ReadonlyMap; dhcpServerConfigurationsByNodeId?: ReadonlyMap; dnsServerConfigurationsByNodeId?: ReadonlyMap; firewallConfigurationsByNodeId?: ReadonlyMap; portForwardingRowsByNodeId?: ReadonlyMap; switchConfigurationsByNodeId?: ReadonlyMap; ipForwardingByNodeId?: ReadonlyMap; echoServersByNodeId?: ReadonlyMap; simpleClientsByNodeId?: ReadonlyMap; webServersByNodeId?: ReadonlyMap; webBrowserNodeIds?: ReadonlySet; emailServersByNodeId?: ReadonlyMap; emailClientsByNodeId?: ReadonlyMap; terminalNodeIds?: ReadonlySet; fileSystemsByNodeId?: ReadonlyMap; 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, ): TopologySnapshot { return topologySnapshot({ ...snapshot, ...changes }); } export const EMPTY_TOPOLOGY_SNAPSHOT: TopologySnapshot = topologySnapshot({ nodes: [], links: [] });