// Ported from filius-ipad (GPLv2/GPLv3): // State/TopologyEditorState.swift (runtime configuration value types) import type { Id } from '../ids.js'; import { maskAddress, parseIPv4 } from './ipv4.js'; /** The IP settings of a PC or notebook (its single interface) — and a router's gateway. */ export interface DeviceConfiguration { readonly ipAddress: string; readonly subnetMask: string; readonly defaultGateway: string; readonly dnsServer: string; } export function deviceConfiguration( ipAddress: string, subnetMask: string, defaultGateway = '', dnsServer = '', ): DeviceConfiguration { return { ipAddress, subnetMask, defaultGateway, dnsServer }; } /** One interface of a router or gateway. */ export interface InterfaceConfiguration { readonly ipAddress: string; readonly subnetMask: string; } /** Map key for an interface: node and port. */ export type InterfaceKey = string; export function interfaceKey(nodeId: Id, portId: Id): InterfaceKey { return `${nodeId}/${portId}`; } /** A row of the manual routing table (Java `Weiterleitungstabelle`). */ export interface ManualRoute { readonly destinationNetwork: string; readonly subnetMask: string; readonly gateway: string; readonly interfaceIPAddress: string; } /** Longest numeric mask wins; on a tie the first row stays. */ export function bestMatchingManualRoute( targetIPAddress: string, routes: readonly ManualRoute[], ): ManualRoute | undefined { const target = parseIPv4(targetIPAddress); if (target === undefined) return undefined; let best: ManualRoute | undefined; let bestMask: number | undefined; for (const route of routes) { const destination = parseIPv4(route.destinationNetwork); const mask = parseIPv4(route.subnetMask); if (destination === undefined || mask === undefined) continue; if (destination !== maskAddress(target, mask)) continue; if (bestMask !== undefined && mask <= bestMask) continue; bestMask = mask; best = route; } return best; } export interface SwitchConfiguration { readonly ssid: string; readonly retentionTimeMilliseconds: number; } /** Java SwitchFirmware keeps a learned MAC address for five minutes. */ export const DEFAULT_SWITCH_RETENTION_MILLISECONDS = 300_000; export function switchConfiguration( ssid: string, retentionTimeMilliseconds = DEFAULT_SWITCH_RETENTION_MILLISECONDS, ): SwitchConfiguration { return { ssid, retentionTimeMilliseconds }; } /** The first six hex digits of the node's UUID, lower case. */ export function defaultSwitchConfiguration(nodeId: Id): SwitchConfiguration { const compact = nodeId.replaceAll('-', '').toLowerCase(); return switchConfiguration(compact.slice(0, 6)); } /** A PC or notebook joining a switch's wireless network (Java Betriebssystem.ssid). */ export interface HostWirelessConfiguration { readonly isEnabled: boolean; readonly ssid: string; } // ── DHCP (Java filius.software.dhcp) ───────────────────────────────────────── export interface DhcpClientConfiguration { readonly isEnabled: boolean; } export interface DhcpStaticAssignment { readonly id: Id; readonly macAddress: string; readonly ipAddress: string; } export interface DhcpServerConfiguration { readonly isActive: boolean; readonly lowerBoundIPAddress: string; readonly upperBoundIPAddress: string; readonly gatewayIPAddress: string; readonly dnsServerIPAddress: string; /** Hand out the gateway and DNS server set here instead of the server's own. */ readonly useOwnSettings: boolean; readonly staticAssignments: readonly DhcpStaticAssignment[]; } /** Java's DHCPServer as its constructor leaves it: off, all addresses 0.0.0.0. */ export function dhcpServerConfiguration(fields: Partial = {}): DhcpServerConfiguration { return { isActive: fields.isActive ?? false, lowerBoundIPAddress: fields.lowerBoundIPAddress ?? '0.0.0.0', upperBoundIPAddress: fields.upperBoundIPAddress ?? '0.0.0.0', gatewayIPAddress: fields.gatewayIPAddress ?? '0.0.0.0', dnsServerIPAddress: fields.dnsServerIPAddress ?? '0.0.0.0', useOwnSettings: fields.useOwnSettings ?? false, staticAssignments: fields.staticAssignments ?? [], }; } // ── Gateway NAT and remote links ──────────────────────────────────────────── /** A row of a home router's port forwarding (Java GatewayFirmware.staticNAT), as text. */ export interface GatewayPortForwardingRow { readonly protocolValue: string; readonly publicPortValue: string; readonly lanIPAddress: string; readonly lanPortValue: string; } /** * The in-project pairing of two remote links (Java Modem). Swift's LAN * transport settings belong to its Network-framework controller and are not * part of the port. */ export interface RemoteLinkConfiguration { readonly pairIdentifier: string; readonly latencyMilliseconds: number; readonly isEnabled: boolean; } export const DEFAULT_REMOTE_LINK_LATENCY_MILLISECONDS = 20; // ── Installed programs (Java installierteAnwendungen) ────────────────────── /** Swift's TopologyRuntimeInstallableProgram. */ export type InstallableProgram = | 'commandPrompt' | 'fileExplorer' | 'imageViewer' | 'textEditor' | 'webServer' | 'webBrowser' | 'echoServer' | 'simpleClient' | 'dnsServer' | 'dhcpServer' | 'firewall' | 'emailClient' | 'emailServer' | 'gnutella'; export interface WebServerConfiguration { readonly port: number; /** Where the served files are: Java keeps them in /webserver, Swift moves them to /www. */ readonly documentRoot: string; /** Serve by host name, from the hosts named in www.conf/vhosts (Java's useVHost). */ readonly virtualHosts: boolean; } export const DEFAULT_WEB_SERVER_PORT = 80; export interface GnutellaConfiguration { readonly maximumKnownPeers: number; } export type DnsRecordType = 'A' | 'MX' | 'NS'; /** * One record a DNS server answers with (Swift's TopologyDNSResourceRecord): * `name` in lower case without the final dot (the root is "."), `target` an * IPv4 address for A, an absolute name ("mail.filius.de.") for MX and NS. */ export interface DnsResourceRecord { readonly name: string; readonly type: DnsRecordType; readonly ttlSeconds: number; readonly target: string; } export interface DnsServerConfiguration { readonly recursiveResolutionEnabled: boolean; readonly forwardingServerIPAddress?: string; /** In the order of Java's /dns/hosts, which stays in the file system as it is. */ readonly records: readonly DnsResourceRecord[]; } /** * An e-mail address as Java's AddressEntry holds it: a display name, an * address, or both (Swift's TopologyRuntimeEmailAddress wants the address). */ export interface EmailAddress { readonly name: string | null; readonly mailAddress: string | null; } /** An e-mail (Swift's TopologyRuntimeEmailMessage without its run-time delivery identity). */ export interface EmailMessage { /** Numbered from 1 per program, in folder order, as Swift numbers them. */ readonly id: number; readonly from: EmailAddress; readonly to: readonly EmailAddress[]; readonly cc: readonly EmailAddress[]; readonly bcc: readonly EmailAddress[]; readonly subject: string; readonly body: string; /** Only when the file gives one; Java never sets it. */ readonly receivedAtMilliseconds?: number; readonly isNew: boolean; readonly isMarkedForDeletion: boolean; /** A message of the sent folder. */ readonly isSent: boolean; } /** The account an e-mail client uses. First and last name stay apart, as Java keeps them. */ export interface EmailClientAccount { readonly pop3Host: string; readonly pop3Port: number; readonly smtpHost: string; readonly smtpPort: number; readonly username: string; readonly password: string; readonly firstName: string; readonly lastName: string; readonly email: string; } /** Java's Kontakt: an address book entry. */ export interface EmailContact { readonly name: string; readonly firstName: string; readonly street: string; readonly houseNumber: number; readonly postalCode: number; readonly city: string; readonly email: string; readonly phone: string; } export interface EmailClientConfiguration { /** Null when the client has none (Java opens it without one). */ readonly account: EmailClientAccount | null; readonly inbox: readonly EmailMessage[]; readonly sent: readonly EmailMessage[]; readonly drafts: readonly EmailMessage[]; readonly addressBook: readonly EmailContact[]; readonly nextMessageID: number; } export interface EmailServerAccount { readonly username: string; readonly password: string; readonly firstName: string; readonly lastName: string; readonly mailbox: readonly EmailMessage[]; } export interface EmailServerConfiguration { readonly domain: string; readonly pop3Port: number; readonly accounts: readonly EmailServerAccount[]; readonly nextMessageID: number; } export const DEFAULT_POP3_PORT = 110; export const DEFAULT_SMTP_PORT = 25; /** * Java's echo server (ServerBaustein) and generic client (ClientBaustein) * keep their port and target in the file; Swift asks for them at run time. */ export interface EchoServerConfiguration { readonly port: number; } export interface SimpleClientConfiguration { readonly targetAddress: string; readonly targetPort: number; } // ── Firewall (Java filius.software.firewall.Firewall, FirewallRule) ───────── export const FIREWALL_PROTOCOL = { all: -1, icmp: 1, tcp: 6, udp: 17 } as const; export type FirewallProtocol = keyof typeof FIREWALL_PROTOCOL; export type FirewallAction = 'drop' | 'accept'; /** Java writes this as the source of a rule that means "any directly connected network". */ export const DIRECTLY_CONNECTED_SOURCE_MARKER = '999.999.999.999'; export const ALL_PORTS = -1; export interface FirewallRule { readonly sourceIPAddress: string; readonly sourceSubnetMask: string; readonly destinationIPAddress: string; readonly destinationSubnetMask: string; /** A port number or ALL_PORTS. */ readonly port: number; readonly protocol: FirewallProtocol; readonly action: FirewallAction; } export function firewallRule(fields: Partial = {}): FirewallRule { return { sourceIPAddress: fields.sourceIPAddress ?? '', sourceSubnetMask: fields.sourceSubnetMask ?? '', destinationIPAddress: fields.destinationIPAddress ?? '', destinationSubnetMask: fields.destinationSubnetMask ?? '', port: fields.port ?? ALL_PORTS, protocol: fields.protocol ?? 'tcp', action: fields.action ?? 'drop', }; } export interface FirewallConfiguration { readonly isActive: boolean; readonly defaultPolicy: FirewallAction; readonly dropIcmp: boolean; readonly filterSynSegmentsOnly: boolean; readonly filterUdp: boolean; readonly rules: readonly FirewallRule[]; } /** What a router or gateway has without a firewall configured: inactive. */ export function firewallConfiguration(fields: Partial = {}): FirewallConfiguration { return { isActive: fields.isActive ?? false, defaultPolicy: fields.defaultPolicy ?? 'drop', dropIcmp: fields.dropIcmp ?? false, filterSynSegmentsOnly: fields.filterSynSegmentsOnly ?? true, filterUdp: fields.filterUdp ?? true, rules: fields.rules ?? [], }; } /** What Java installs as the personal firewall of a PC: active, drop by default. */ export const JAVA_PERSONAL_FIREWALL_DEFAULTS: FirewallConfiguration = firewallConfiguration({ isActive: true, defaultPolicy: 'drop', dropIcmp: false, filterSynSegmentsOnly: true, filterUdp: true, rules: [], });