ReportRun reads Java's report back with iText: every piece of text with its place, font, size and colour. The editor lays the report out by iText's rules (A4, leading 1.5, loose chunks in the current line, tables 80 % wide with padding 2, fixed traffic columns, page breaks) and draws it with pdf-lib in the PDF's own Helvetica, widths without kerning as iText measures. All device pages of Internet_Komplett, the first page and the data exchange of einfaches_rechnernetz_komplett match Java piece by piece; Java's iText reads our PDF with the same text in the same places. The sidebar gains Java's "Report"; the frames come from the room by a short subscription. Not adopted: true/false for DHCP (Java's own Ja/Nein), hash table orders, an image running off the page, the report writing back into the model. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
102 lines
5.4 KiB
TypeScript
102 lines
5.4 KiB
TypeScript
// What the data exchange window writes of a frame in the viewer's language:
|
|
// Java's words of both bundles, the locale's numbers ("1.000.000" in German,
|
|
// "1,000,000" in English), the time of day with milliseconds, the row of the
|
|
// highest layer, the details from the layered rows, the text file.
|
|
|
|
import { describe, expect, it } from 'vitest';
|
|
import { captureRows, TCP_FLAG, type CapturedFrame, type EthernetFrame } from '@mdg-filius/core';
|
|
import type { TraceFrame } from '@mdg-filius/protocol';
|
|
import { translate } from '../src/i18n/i18n.js';
|
|
import { captureWords, exportText, frameCells, frameDetails, reportRows, reportTraffic } from '../src/simulation/captureCells.js';
|
|
|
|
/** A TCP segment with data from 10.0.0.1:50000 to 10.0.0.2:80, as the room sends it. */
|
|
function frame(number: number, timeMilliseconds: number): TraceFrame {
|
|
const ethernet: EthernetFrame = {
|
|
identity: number,
|
|
sourceMacAddress: '02:00:00:00:00:01',
|
|
destinationMacAddress: '02:00:00:00:00:02',
|
|
payload: {
|
|
kind: 'ipv4',
|
|
packet: {
|
|
identity: 9,
|
|
senderIPAddress: '10.0.0.1',
|
|
receiverIPAddress: '10.0.0.2',
|
|
timeToLive: 64,
|
|
protocol: 'tcp',
|
|
payload: {
|
|
kind: 'tcp',
|
|
segment: {
|
|
sourcePort: 50_000,
|
|
destinationPort: 80,
|
|
sequenceNumber: 2_000_001,
|
|
acknowledgementNumber: 1_000_001,
|
|
flags: TCP_FLAG.acknowledgement,
|
|
payload: new TextEncoder().encode('GET / HTTP/1.1\nHost: x\n'),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
const captured: CapturedFrame[] = [{ number, timeMilliseconds, frame: ethernet }];
|
|
return { number, rows: captureRows(captured, { withAddresses: true }), layeredRows: captureRows(captured, { withAddresses: false }) };
|
|
}
|
|
|
|
const words = (language: 'de' | 'en') => captureWords((key) => translate(language, key), language);
|
|
const clock = new Date(2026, 9, 4, 8, 30, 0, 0).getTime();
|
|
|
|
describe("the data exchange window's cells", () => {
|
|
it("writes a frame's row by its highest layer, in either language", () => {
|
|
expect(frameCells(frame(3, 1_234), clock, words('de'))).toEqual(['3', '08:30:01.234', '10.0.0.1:50000', '10.0.0.2:80', 'HTTP', 'Anwendung', 'GET / HTTP/1.1\nHost: x\n']);
|
|
expect(frameCells(frame(3, 1_234), clock, words('en'))).toEqual(['3', '08:30:01.234', '10.0.0.1:50000', '10.0.0.2:80', 'HTTP', 'Application', 'GET / HTTP/1.1\nHost: x\n']);
|
|
expect(words('de').header).toEqual(['Nr.', 'Zeit', 'Quelle', 'Ziel', 'Protokoll', 'Schicht', 'Bemerkungen / Details']);
|
|
expect(words('en').header).toEqual(['No.', 'Time', 'Source', 'Destination', 'Protocol', 'Layer', 'Comment / Details']);
|
|
});
|
|
|
|
it("writes the details with the locale's numbers and Java's words", () => {
|
|
const german = frameDetails(frame(3, 0), clock, words('de')).map((line) => line.text);
|
|
expect(german[0]).toBe('Nr.: 3 / Zeit: 08:30:00.000');
|
|
expect(german).toContain('Bemerkungen / Details: SEQ: 2.000.001, ACK: 1.000.001');
|
|
expect(german).toContain('Bemerkungen / Details: Protokoll: 6, TTL: 64');
|
|
expect(german).toContain('Bemerkungen / Details (23 Bytes): ');
|
|
const english = frameDetails(frame(3, 0), clock, words('en')).map((line) => line.text);
|
|
expect(english[0]).toBe('No.: 3 / Time: 08:30:00.000');
|
|
expect(english).toContain('Comment / Details: SEQ: 2,000,001, ACK: 1,000,001');
|
|
expect(english).toContain('Comment / Details: Protocol: 6, TTL: 64');
|
|
expect(frameDetails({ number: 1, rows: [], layeredRows: [] }, clock, words('de'))).toEqual([]);
|
|
});
|
|
|
|
it('exports the table with the words of its header', () => {
|
|
const text = exportText([frame(1, 0), frame(2, 5)], clock, words('en'));
|
|
expect(text.split('\r\n')[1]).toBe(
|
|
`| No. | Time | Source | Destination | Protocol | Layer | ${'Comment / Details'.padEnd(40)} | `,
|
|
);
|
|
expect(text.split('\r\n')[3]).toBe(
|
|
`| 1 | 08:30:00.000 | 10.0.0.1:50000 | 10.0.0.2:80 | HTTP | Application | ${'GET / HTTP/1.1 Host: x'.padEnd(40)} | `,
|
|
);
|
|
expect(text.match(/^\+-/gm)).toHaveLength(2);
|
|
});
|
|
});
|
|
|
|
describe("the report's data exchange", () => {
|
|
it('writes every layer of a frame as a row, with addresses, as Java\'s report does', () => {
|
|
expect(reportRows([frame(3, 1_234)], clock, words('de'))).toEqual([
|
|
['3', '08:30:01.234', '02:00:00:00:00:01', '02:00:00:00:00:02', '', 'Netzzugang', '0x800'],
|
|
['3', '08:30:01.234', '10.0.0.1', '10.0.0.2', 'IP', 'Vermittlung', 'Protokoll: 6, TTL: 64'],
|
|
['3', '08:30:01.234', '10.0.0.1:50000', '10.0.0.2:80', 'TCP', 'Transport', 'SEQ: 2.000.001, ACK: 1.000.001'],
|
|
['3', '08:30:01.234', '10.0.0.1:50000', '10.0.0.2:80', 'HTTP', 'Anwendung', 'GET / HTTP/1.1\nHost: x\n'],
|
|
]);
|
|
});
|
|
|
|
it("lists each card that saw frames, under its device's label and address; one without frames not", () => {
|
|
const cards = [
|
|
{ portId: 'a', label: 'Rechner 1', ipAddress: '10.0.0.1' },
|
|
{ portId: 'b', label: 'Rechner 2', ipAddress: '10.0.0.2' },
|
|
{ portId: 'c', label: 'Rechner 3', ipAddress: '10.0.0.3' },
|
|
];
|
|
const traffic = reportTraffic(cards, new Map([['a', [frame(1, 0)]], ['b', []]]), clock, words('de'));
|
|
expect(traffic.map((card) => card.title)).toEqual(['Rechner 1 - 10.0.0.1']);
|
|
expect(traffic[0]!.header).toEqual(words('de').header);
|
|
expect(traffic[0]!.rows.length).toBe(4);
|
|
});
|
|
});
|