Files
julian.kiedaischandClaude Opus 5.5 2b85e7432c feat: F4.6g3 a computer's firewall window, measured against Java's with traffic from two networks
The firewall-pc scenario and FirewallWindowRun use Java's window as a person
does and send a ping, a TCP connection and a DNS question from the server's
network and from behind the router after each step. The core adds and
removes port rules (personalFirewall.ts) and writes both into the firewall's
log, which now carries each line's time; the room takes the window's
actions and sends its view and the log as lines; the editor shows Java's
window with its two tabs. Not adopted from Java: any integer as a port,
a port after a space refused without a word, a log one can type into.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 21:18:05 +00:00

125 lines
7.3 KiB
TypeScript

// A server's lines as Java's web server window wrote them
// (packages/sim/test/browserWindow.java.json, BrowserWindowRun): each kind
// the window got there, in Java's words. And a computer's firewall's log and
// table as Java's firewall window wrote them
// (packages/core/test/firewallWindow.java.json, FirewallWindowRun).
import { readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { describe, expect, it } from 'vitest';
import { DIRECTLY_CONNECTED_SOURCE_MARKER, firewallRule, type FirewallLogEntry, type ServerLogEntry, type SimpleClientLogEntry } from '@mdg-filius/core';
import type { FirewallViewRule } from '@mdg-filius/protocol';
import { firewallRow } from '../src/desktop/PersonalFirewallPanel.js';
import { firewallLine, serverLine, simpleClientLine } from '../src/desktop/serverLines.js';
import { translate } from '../src/i18n/i18n.js';
const java = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), '../../sim/test/browserWindow.java.json'), 'utf8')) as { steps: { step: string; log?: string }[] };
const log = java.steps.find((step) => step.step === 'server:stop')!.log!;
const t = (key: string, values?: Readonly<Record<string, string | number>>) => translate('de', key, values);
describe("a server's lines", () => {
it("are Java's, line by line", () => {
const [, address, port] = /Verbindung zu ([\d.]+):(\d+) hergestellt/.exec(log)!;
const request = 'POST /ziel.html HTTP/1.1\nHost: www.filius.de\n\nname=Fil+ius&c=1&s=y&t=%C3%BC%26%3D&ok=Gut';
const answer = 'HTTP/1.1 200 OK\nContent-type: text/html\n\n<html><head><title>Ziel</title></head><body>Ziel</body></html>';
const entries: ServerLogEntry[] = [
{ kind: 'acceptingStarted' },
{ kind: 'connectionAccepted', address: address!, port: Number(port) },
{ kind: 'received', text: request },
{ kind: 'sent', text: answer },
{ kind: 'connectionClosed', address: address!, port: Number(port) },
{ kind: 'acceptingStopped' },
{ kind: 'serverSocketClosed' },
];
for (const entry of entries) expect(log, entry.kind).toContain(`${serverLine(entry, t)}\n`);
expect(log.startsWith(`${serverLine({ kind: 'acceptingStarted' }, t)}\n`)).toBe(true);
});
it('name a failed send and a port that is taken', () => {
expect(serverLine({ kind: 'sendFailed', failure: 'timedOut' }, t)).toBe('Zeitüberschreitung während des Versands einer Nachricht');
expect(serverLine({ kind: 'sendFailed', failure: 'notConnected' }, (key) => translate('en', key))).toBe('Connection could not be established!');
expect(serverLine({ kind: 'serverSocketFailed' }, t)).toBe('Server-Socket konnte nicht geöffnet werden.');
expect(serverLine({ kind: 'connectionAccepted', address: '1.2.3.4', port: 5 }, (key, values) => translate('en', key, values))).toBe('Connection to 1.2.3.4:5 established');
});
});
// The echo windows' lines against Java's (packages/sim/test/echoWindow.java.json, EchoWindowRun).
const echo = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), '../../sim/test/echoWindow.java.json'), 'utf8')) as {
steps: { step: string; server: { log: string }; client: { output: string } }[];
};
describe("the echo windows' lines", () => {
it("are Java's, line by line, the server's and the client's", () => {
const last = echo.steps.at(-1)!;
const [, port] = /Verbindung zu 192\.168\.0\.20:(\d+) hergestellt/.exec(last.server.log)!;
const server: ServerLogEntry[] = [
{ kind: 'acceptingStarted' },
{ kind: 'connectionAccepted', address: '192.168.0.20', port: Number(port) },
{ kind: 'received', text: 'Zeile 1\nZeile 2' },
{ kind: 'sent', text: 'Zeile 1\nZeile 2' },
{ kind: 'acceptingStopped' },
{ kind: 'connectionClosed', address: '192.168.0.20', port: Number(port) },
{ kind: 'serverSocketClosed' },
];
for (const entry of server) expect(last.server.log, entry.kind).toContain(`${serverLine(entry, t)}\n`);
const client: SimpleClientLogEntry[] = [
{ kind: 'connected' },
{ kind: 'sent', text: 'Hallo Echo' },
{ kind: 'received', text: 'Hallo Echo' },
{ kind: 'connectionClosed', address: '192.168.0.30', port: 55555 },
{ kind: 'connectFailed', failure: 'timedOut' },
{ kind: 'disconnected' },
{ kind: 'connectFailed', failure: 'unresolved' },
];
for (const entry of client) expect(last.client.output, entry.kind).toContain(`${simpleClientLine(entry, t)}\n`);
// Not in this story, in Java's words all the same (sw_clientbaustein_msg4, sw_tcpsocket_msg3, msg9, msg6).
expect([
simpleClientLine({ kind: 'notConnected' }, t),
simpleClientLine({ kind: 'connectFailed', failure: 'failed' }, t),
simpleClientLine({ kind: 'receiveFailed' }, t),
simpleClientLine({ kind: 'sendFailed', failure: 'notConnected' }, t),
]).toEqual(['Fehler beim Verbindungsaufbau', 'Fehler beim Verbindungsaufbau\nAktives Öffnen des Sockets fehlgeschlagen', 'Keine Verbindung hergestellt!', 'Verbindung nicht aufgebaut!']);
});
});
describe("a computer's firewall's lines", () => {
const firewallJava = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), '../../core/test/firewallWindow.java.json'), 'utf8')) as { steps: { step: string; log: string }[] };
const javaLog = firewallJava.steps.find((step) => step.step === 'delete row 0')!.log.split('\n');
it("are Java's, line by line", () => {
const network = { ipAddress: '10.0.0.10', subnetMask: '255.255.255.0' };
const entries: FirewallLogEntry[] = [
{ kind: 'portRuleAdded', port: 55555, protocol: 'tcp' },
{ kind: 'ruleMatched', ruleNumber: 1, rule: firewallRule({ port: 55555, protocol: 'tcp', action: 'accept' }), firstInterface: network, accepted: true },
{ kind: 'ruleMatched', ruleNumber: 2, rule: firewallRule({ port: 53, protocol: 'udp', action: 'accept', sourceIPAddress: DIRECTLY_CONNECTED_SOURCE_MARKER }), firstInterface: network, accepted: true },
{ kind: 'icmpDropped', senderIPAddress: '10.0.0.2', receiverIPAddress: '10.0.0.10', type: 8, code: 0 },
{ kind: 'portRuleAdded', port: -1, protocol: 'all' },
{ kind: 'portRuleRemoved' },
];
const lines = entries.map((entry) => firewallLine(entry, t));
for (const line of lines) expect(javaLog, line).toContain(line);
expect(firewallLine({ kind: 'ruleMatched', ruleNumber: 3, rule: firewallRule({ port: 80 }), firstInterface: undefined, accepted: false }, t)).toBe(
'Firewall-Regel hat gegriffen: #3 (*/* -> */*; TCP:80 => DROP) -> verwerfen',
);
});
it("are the table's rows as Java's ruleToVector writes them, and name a rule the window cannot make", () => {
expect(
[
{ protocol: 'tcp', port: 55555, sender: 'all', text: '' },
{ protocol: 'udp', port: 53, sender: 'sameNetwork', text: '' },
{ protocol: 'all', port: -1, sender: 'all', text: '' },
{ protocol: 'tcp', port: 0, sender: 'all', text: '' },
{ protocol: 'icmp', port: -1, sender: null, text: '10.0.0.0/255.0.0.0 -> */*; ?:* => DROP' },
].map((rule) => firewallRow(rule as FirewallViewRule, t)),
).toEqual([
['TCP', '55555', 'Alle Absender'],
['UDP', '53', 'Nur lokales Rechnernetz'],
['*', '', 'Alle Absender'],
['TCP', '0', 'Alle Absender'],
['ICMP', '', '10.0.0.0/255.0.0.0 -> */*; ?:* => DROP'],
]);
});
});