feat(core): the e-mail program after Java, measured against Java

The program sends to its account's SMTP server, one connection for each
recipient, and fetches its mails by POP3; the server now sends mails on
to other domains by MX and returns notices to their senders. The
program's folders are in the snapshot and go back into the project.
EmailClientRun plays six stories with two servers; the mail oracles run
in the language's locale, so Java's notice dates match the core's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 19:38:44 +00:00
co-authored by Claude Opus 5.5
parent 015635926f
commit 058675a148
30 changed files with 58319 additions and 1724 deletions
+1 -1
View File
@@ -3478,7 +3478,7 @@
"bob",
[
[
"From: <noreply@filius.de>\nTo: Bob Beispiel <bob@filius.de>\nSubject: Non Delivery Notification\n\nThe mail could not be delivered either because user or mail domain is unknown.\nMessage could not be delivered to the following recipients:\n\tniemand@filius.de\n\tKeiner@filius.de\n\nDate: 10/1/26, 6:55 PM\nMail-Server: 192.168.0.30\n\n______________________________________________________________________\nFrom: Bob Beispiel <bob@filius.de>\nTo: <niemand@filius.de>\nSubject: Wer?\n\nIst da jemand?",
"From: <noreply@filius.de>\nTo: Bob Beispiel <bob@filius.de>\nSubject: Non Delivery Notification\n\nThe mail could not be delivered either because user or mail domain is unknown.\nMessage could not be delivered to the following recipients:\n\tniemand@filius.de\n\tKeiner@filius.de\n\nDate: 01.10.26, 19:29\nMail-Server: 192.168.0.30\n\n______________________________________________________________________\nFrom: Bob Beispiel <bob@filius.de>\nTo: <niemand@filius.de>\nSubject: Wer?\n\nIst da jemand?",
true,
false
]
+1 -1
View File
@@ -3478,7 +3478,7 @@
"bob",
[
[
"From: <noreply@filius.de>\nTo: Bob Beispiel <bob@filius.de>\nSubject: Non Delivery Notification\n\nThe mail could not be delivered either because user or mail domain is unknown.\nMessage could not be delivered to the following recipients:\n\tniemand@filius.de\n\tKeiner@filius.de\n\nDate: 10/1/26, 6:55 PM\nMail-Server: 192.168.0.30\n\n______________________________________________________________________\nFrom: Bob Beispiel <bob@filius.de>\nTo: <niemand@filius.de>\nSubject: Wer?\n\nIst da jemand?",
"From: <noreply@filius.de>\nTo: Bob Beispiel <bob@filius.de>\nSubject: Non Delivery Notification\n\nThe mail could not be delivered either because user or mail domain is unknown.\nMessage could not be delivered to the following recipients:\n\tniemand@filius.de\n\tKeiner@filius.de\n\nDate: 01/10/2026, 19:29\nMail-Server: 192.168.0.30\n\n______________________________________________________________________\nFrom: Bob Beispiel <bob@filius.de>\nTo: <niemand@filius.de>\nSubject: Wer?\n\nIst da jemand?",
true,
false
]
+131
View File
@@ -0,0 +1,131 @@
// The e-mail program (apps/emailClient.ts) where emailClientJava.test.ts's
// stories do not go: what the window refuses, the account's SMTP port, Bcc,
// and the folders as the model keeps them.
import { describe, expect, it } from 'vitest';
import {
deviceConfiguration,
formatMail,
NetworkRuntimeEngine,
topologySnapshot,
type EmailClientAccount,
type EmailClientConfiguration,
type TopologySnapshotInput,
} from '../src/index.js';
import { link, pc, uuid } from './fixtures.js';
import { MAIL_ACCOUNTS } from './mailAccounts.js';
import { emailClientText } from './programLines.js';
const SERVER = uuid(0x6001);
const HOST = uuid(0x6002);
const ACCOUNT: EmailClientAccount = {
pop3Host: '10.0.0.1',
pop3Port: 110,
smtpHost: '10.0.0.1',
smtpPort: 25,
username: 'bob',
password: 'passwort',
firstName: 'Bob',
lastName: '',
email: 'bob@filius.de',
};
function started(client: Partial<EmailClientConfiguration>): NetworkRuntimeEngine {
const input: TopologySnapshotInput = {
nodes: [pc(SERVER, uuid(0x6011)), pc(HOST, uuid(0x6012))],
links: [link(SERVER, uuid(0x6011), HOST, uuid(0x6012))],
deviceConfigurations: new Map([
[SERVER, deviceConfiguration('10.0.0.1', '255.255.255.0', '', '')],
[HOST, deviceConfiguration('10.0.0.2', '255.255.255.0', '', '')],
]),
emailServersByNodeId: new Map([[SERVER, { domain: 'filius.de', accounts: MAIL_ACCOUNTS, isActive: true }]]),
emailClientsByNodeId: new Map([[HOST, { account: ACCOUNT, inbox: [], sent: [], drafts: [], addressBook: [], nextMessageID: 1, ...client }]]),
};
const engine = new NetworkRuntimeEngine(14);
engine.handle({ type: 'start', snapshot: topologySnapshot(input), seed: 14, initialTimeMilliseconds: 0 });
return engine;
}
const form = { to: '', cc: '', bcc: '', subject: 'Betreff', text: 'Text' };
describe('the e-mail program', () => {
it('refuses what its window refuses: no account, an address Java does not take, no recipient', () => {
expect(started({ account: null }).emailClient(HOST)!.send({ ...form, to: 'anna@filius.de' })).toBe('noAccount');
expect(started({ account: null }).emailClient(HOST)!.fetch()).toBe(false);
const client = started({}).emailClient(HOST)!;
expect(client.send({ ...form, to: 'anna@filius.de, anna@' })).toBe('invalidAddress');
expect(client.send({ ...form, cc: 'Anna <anna>' })).toBe('invalidAddress');
expect(client.send({ ...form, bcc: 'Anna Muster' })).toBe('invalidAddress');
expect(client.send({ ...form, to: ' ' })).toBe('noRecipient');
expect(client.send({ ...form, to: 'Anna Muster <anna@filius.de>' })).toBeUndefined();
});
it('sends to a Bcc recipient too, who gets the mail without Bcc; the sent folder keeps it', () => {
const engine = started({});
expect(engine.emailClient(HOST)!.send({ ...form, to: 'anna@filius.de', bcc: 'bob@filius.de' })).toBeUndefined();
engine.handle({ type: 'advance', toMilliseconds: 20_000 });
const mailbox = (username: string) => engine.emailServer(SERVER)!.state.accounts.find((each) => each.username === username)!.mailbox;
expect(formatMail(mailbox('bob')[0]!.mail)).toBe('From: Bob <bob@filius.de>\nTo: <anna@filius.de>\nSubject: Betreff\n\nText');
expect(formatMail(mailbox('anna').at(-1)!.mail)).toBe(formatMail(mailbox('bob')[0]!.mail));
expect(engine.emailClient(HOST)!.sent.map((entry) => entry.mail.bcc)).toEqual([[{ name: null, mailAddress: 'bob@filius.de' }]]);
});
it("uses the account's SMTP port (Java always 25): without a server there, the connection fails", () => {
const engine = started({ account: { ...ACCOUNT, smtpPort: 2525 } });
engine.emailClient(HOST)!.send({ ...form, to: 'anna@filius.de' });
engine.handle({ type: 'advance', toMilliseconds: 60_000 });
expect(engine.emailClient(HOST)!.log.all.map((line) => emailClientText(line))).toEqual([
'----',
'filius.exception.TimeOutException: Zeitüberschreitung beim Verbindungsaufbau',
]);
expect(engine.emailClient(HOST)!.sent).toEqual([]);
});
it('gives up without a word on an account address Java does not take (no MAIL FROM)', () => {
const engine = started({ account: { ...ACCOUNT, email: 'bob@' } });
engine.emailClient(HOST)!.send({ ...form, to: 'anna@filius.de' });
engine.handle({ type: 'advance', toMilliseconds: 20_000 });
expect(engine.emailClient(HOST)!.log.all.map((line) => emailClientText(line))).toEqual([
'----',
'Verbindung zu 10.0.0.1:25 hergestellt',
'Mail versendet an anna@filius.de',
'Verbindung zu 10.0.0.1:25 getrennt',
]);
expect(engine.emailServer(SERVER)!.log.all.filter((line) => line.kind === 'received').map((line) => line.kind === 'received' && line.text)).toEqual(['HELO 10.0.0.2']);
expect(engine.emailClient(HOST)!.sent).toEqual([]);
});
it('tells of a connection that ends while it waits for an answer', () => {
const engine = started({});
engine.emailClient(HOST)!.send({ ...form, to: 'anna@filius.de' });
// Switched off when the server has taken the connection: it closes it before its greeting arrives.
while (!engine.emailServer(SERVER)!.log.all.some((line) => line.kind === 'connectionOpened')) {
engine.handle({ type: 'advance', toMilliseconds: engine.state.currentTimeMilliseconds + 1 });
}
engine.emailServer(SERVER)!.setActive(false);
engine.handle({ type: 'advance', toMilliseconds: 20_000 });
expect(engine.emailClient(HOST)!.log.all.map((line) => line.kind)).toEqual(['connectionStarting', 'smtpConnected', 'failed', 'smtpDisconnected']);
expect(engine.emailClient(HOST)!.log.all[2]).toMatchObject({ failure: { kind: 'receiveFailed' } });
});
it("takes a model's mail without a sender as Java's mail without one (no From)", () => {
const engine = started({ inbox: [{ ...MAIL_ACCOUNTS[0]!.mailbox[0]!, from: { name: null, mailAddress: null } }] });
expect(formatMail(engine.emailClient(HOST)!.inbox[0]!.mail)).toBe('To: <anna@filius.de>\nSubject: Hallo Anna\n\nErste Zeile\nZweite Zeile');
});
it('keeps its folders in the snapshot: numbered inbox, sent, drafts; the drafts as they were', () => {
const draft = { ...MAIL_ACCOUNTS[0]!.mailbox[1]!, id: 9 };
const engine = started({ drafts: [draft] });
engine.emailClient(HOST)!.send({ ...form, to: 'bob@filius.de' });
engine.handle({ type: 'advance', toMilliseconds: 20_000 });
engine.emailClient(HOST)!.fetch();
engine.handle({ type: 'advance', toMilliseconds: 40_000 });
const folders = engine.state.topologySnapshot.emailClientsByNodeId.get(HOST)!;
expect(folders.inbox.map((message) => [message.id, message.subject, message.isSent])).toEqual([[1, 'Betreff', false]]);
expect(folders.sent.map((message) => [message.id, message.subject, message.isSent])).toEqual([[2, 'Betreff', true]]);
expect(folders.drafts).toEqual([{ ...draft, id: 3 }]);
expect(folders.nextMessageID).toBe(4);
expect(folders.account).toEqual(ACCOUNT);
});
});
+259
View File
@@ -0,0 +1,259 @@
// The e-mail program as Java FILIUS runs it. tools/java-oracle's
// EmailClientRun let FILIUS' e-mail program send and fetch mails with two
// e-mail servers on mail-lan (filius.de, andere.de; the DNS server knows
// both domains' MX) and wrote every frame each card saw, Java's tables of
// them, what the program and each server told their windows, and at the end
// the program's inbox and sent mails and each server's accounts
// (email-client-java-<story>.java.json). The engine plays the same stories
// with its programs (apps/emailClient.ts, apps/emailServer.ts): each card's
// frames in the same order, with the same content and rows, at about the
// same time; the same lines; the same mails at the end.
import { readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { describe, expect, it } from 'vitest';
import { formatMail, withSnapshot, type EmailClientAccount, type LogLine, type MailboxEntry, type NetworkRuntimeEngine } from '../src/index.js';
import { expectSameCaptures, GAP_TOLERANCE_MILLISECONDS, watchedEngine, withClientPortText, type JavaFrame, type JavaTables } from './javaRun.js';
import { MAIL_ACCOUNTS } from './mailAccounts.js';
import { emailClientText, emailServerText, POLL_MILLISECONDS } from './programLines.js';
import { snapshotOf, type Scenario } from './scenario.js';
const scenarioDirectory = join(dirname(fileURLToPath(import.meta.url)), 'scenarios');
const CLIENT = '00000000-0000-0000-0000-000000000201';
const SERVER = '00000000-0000-0000-0000-000000000203';
const OTHER_SERVER = '00000000-0000-0000-0000-000000000204';
type Program = 'client' | 'server' | 'otherServer';
type JavaMail = [string, string, string];
interface JavaRun extends JavaTables {
frames: Record<string, JavaFrame[]>;
events: [number, Program, string][];
inbox: JavaMail[];
sent: JavaMail[];
accounts: [string, JavaMail[]][];
otherAccounts: [string, JavaMail[]][];
}
type Act = (engine: NetworkRuntimeEngine) => void;
const send =
(to: string, cc: string, subject: string, text: string): Act =>
(engine) => {
expect(engine.emailClient(CLIENT)!.send({ to, cc, bcc: '', subject, text })).toBeUndefined();
};
const fetch: Act = (engine) => {
expect(engine.emailClient(CLIENT)!.fetch()).toBe(true);
};
interface Story {
readonly host: string;
readonly password?: string;
readonly acts: readonly [number, Act][];
/** The waits Java ends only at a look every 100 ms, one after the other: each connection opened, and the DNS answer of unknownHost (see tolerance). */
readonly looks: number;
}
const STORIES: Record<string, Story> = {
send: {
host: '192.168.0.30',
looks: 3,
acts: [[500, send('bob@filius.de', 'Carla <carla@andere.de>', 'Treffen', 'Hallo zusammen,\nbis morgen!')]],
},
fetch: { host: '192.168.0.30', looks: 1, acts: [[500, fetch]] },
byName: {
host: 'mail.filius.de',
looks: 5,
acts: [
[500, send('niemand@andere.de', '', 'Hallo?', 'Ist da wer?')],
[12_000, send('jemand@nirgendwo.de', '', 'Hallo?', 'Ist da wer?')],
[25_000, fetch],
],
},
wrongPass: {
host: '192.168.0.30',
password: 'falsch',
looks: 1,
acts: [[500, fetch]],
},
noServer: {
host: '192.168.0.10',
looks: 1,
acts: [[500, send('bob@filius.de', '', 'Ins Leere', 'Niemand da')]],
},
unknownHost: { host: 'gibtsnicht.filius.de', looks: 1, acts: [[500, send('bob@filius.de', '', 'Ins Leere', 'Niemand da')]] },
};
/** Java's wait for a SYN's answer (TCPSocket: three times 2.75 s, its timeout here). */
const JAVA_TIMEOUT_MILLISECONDS = 3 * 2_750;
/**
* Where Java errs: its run corrected, as Java means it. noServer: the
* program's SYN goes once in Java, not three times (tcpJava.test.ts,
* closedPort); its failure comes two attempts later in the engine.
*/
const JAVA_BUGS: Record<string, (java: JavaRun) => JavaRun> = {
noServer: (java) => ({
...java,
events: java.events.map(([time, who, text]) => (who === 'client' && text.includes('Exception') ? [time + 2 * JAVA_TIMEOUT_MILLISECONDS, who, text] : [time, who, text])),
}),
};
/** Where the frames are compared only up to a time (Java's). noServer: the engine's second SYN, which Java does not send. */
const UNTIL: Record<string, (java: JavaRun) => number> = {
noServer: (java) => java.events.find(([, who, text]) => who === 'client' && text.includes('Exception'))![0] - 250,
};
/**
* Java's client and server look every 100 ms whether a connection is made
* (TCPSocket.verbinden, ServerSocket.oeffnen); the engine knows at once. Java
* falls behind by up to a look with each such wait of the story (Story.looks).
*/
const tolerance = (story: Story) => GAP_TOLERANCE_MILLISECONDS + story.looks * POLL_MILLISECONDS;
/**
* byName: which socket draws the next initial sequence number, the program's
* second or the server's to andere.de, is a race of Java's threads (both
* are made within a few milliseconds); compared below the millions.
*/
const WITHOUT_INITIAL_NUMBERS = new Set(['byName']);
const withoutInitialNumbers = (text: string) =>
text
.replace(/(seq|ack)=(\d+)/g, (_, name: string, value: string) => `${name}=${Number(value) % 1_000_000}`)
.replace(/(SEQ|ACK): \d+\.(\d{3}\.\d{3})/g, '$1: n.$2');
/** A notice's date: Java's in its locale and the time it ran. */
const withoutDate = (text: string) => text.replace(/\nDate: [^\n]*\n/g, '\nDate: <date>\n');
/** A frame's text with placeholders: the client's port, a DNS query's random ID, a notice's date. */
const frameText = (text: string) => withoutDate(withClientPortText(text)).replace(/ID=\d+/g, 'ID=x');
type Lines = Record<Program, [number, string][]>;
function javaLines(java: JavaRun): Lines {
const lines: Lines = { client: [], server: [], otherServer: [] };
for (const [time, program, text] of java.events) {
// Anwendung.starten and the refreshes tell the observers nothing (null), which the windows skip.
if (text === 'null' || text.startsWith('call ')) continue;
lines[program].push([time, withClientPortText(text)]);
}
return lines;
}
function engineLines(engine: NetworkRuntimeEngine): Lines {
const lines = <E>(log: readonly LogLine<E>[], text: (entry: E) => string): [number, string][] => log.map((line) => [line.timeMilliseconds, withClientPortText(text(line))]);
return {
client: lines(engine.emailClient(CLIENT)!.log.all, emailClientText),
server: lines(engine.emailServer(SERVER)!.log.all, (entry) => emailServerText(entry)),
otherServer: lines(engine.emailServer(OTHER_SERVER)!.log.all, (entry) => emailServerText(entry)),
};
}
const mails = (entries: readonly MailboxEntry[]): JavaMail[] =>
entries.map((entry) => [withoutDate(formatMail(entry.mail)), String(entry.isNew), String(entry.isMarkedForDeletion)]);
const javaMails = (list: JavaMail[]): JavaMail[] => list.map(([text, isNew, deleted]) => [withoutDate(text), isNew, deleted]);
describe('the e-mail program as Java FILIUS runs it', () => {
it('corrects Java only where it errs, and cuts only after the story began', () => {
for (const [name, correct] of Object.entries(JAVA_BUGS)) {
const java = JSON.parse(readFileSync(join(scenarioDirectory, `email-client-java-${name}.java.json`), 'utf8')) as JavaRun;
expect(JSON.stringify(correct(java)), name).not.toBe(JSON.stringify(java));
expect(UNTIL[name]?.(java) ?? Number.POSITIVE_INFINITY, name).toBeGreaterThan(1_000);
}
});
const scenario = JSON.parse(readFileSync(join(scenarioDirectory, 'mail-lan.scenario.json'), 'utf8')) as Scenario;
for (const [name, story] of Object.entries(STORIES)) {
it(name, () => {
const measured = JSON.parse(readFileSync(join(scenarioDirectory, `email-client-java-${name}.java.json`), 'utf8')) as JavaRun;
const java = JAVA_BUGS[name]?.(measured) ?? measured;
const account: EmailClientAccount = {
pop3Host: story.host,
pop3Port: 110,
smtpHost: story.host,
smtpPort: 25,
username: 'anna',
password: story.password ?? 'geheim',
firstName: 'Anna',
lastName: 'Muster',
email: 'anna@filius.de',
};
const snapshot = withSnapshot(snapshotOf(scenario), {
emailServersByNodeId: new Map([
[SERVER, { domain: 'filius.de', accounts: MAIL_ACCOUNTS, isActive: true }],
[
OTHER_SERVER,
{
domain: 'andere.de',
accounts: [
{
username: 'carla',
password: 'carla',
firstName: 'Carla',
lastName: 'Carla',
mailbox: [],
},
],
isActive: true,
},
],
]),
emailClientsByNodeId: new Map([
[
CLIENT,
{
account,
inbox: [],
sent: [],
drafts: [],
addressBook: [],
nextMessageID: 1,
},
],
]),
});
const { engine } = watchedEngine(scenario, snapshot);
for (const [time, act] of story.acts) {
engine.handle({ type: 'advance', toMilliseconds: time });
act(engine);
}
engine.handle({
type: 'advance',
toMilliseconds: Math.max(...java.events.map(([time]) => time)) + 5_000,
});
const numbers = WITHOUT_INITIAL_NUMBERS.has(name) ? withoutInitialNumbers : (text: string) => text;
expectSameCaptures(engine, scenario, java.frames, {
text: (text) => numbers(frameText(text)),
tables: java,
cell: (text) => numbers(withoutDate(text).replace(/ID=\d+/g, 'ID=x')),
until: UNTIL[name]?.(measured),
tolerance: tolerance(story),
});
const expected = javaLines(java);
const actual = engineLines(engine);
for (const program of ['client', 'server', 'otherServer'] as const) {
const lineText = (text: string) => numbers(withoutDate(text));
expect(
actual[program].map(([, text]) => lineText(text)),
`${program}'s lines`,
).toEqual(expected[program].map(([, text]) => lineText(text)));
for (const [index, [javaTime, text]] of expected[program].entries()) {
const late = javaTime - actual[program][index]![0];
expect(Math.abs(late), `${program}'s line ${JSON.stringify(text.slice(0, 60))}: Java ${late} ms later`).toBeLessThanOrEqual(tolerance(story));
}
}
const client = engine.emailClient(CLIENT)!;
expect(mails(client.inbox), 'inbox').toEqual(javaMails(java.inbox));
expect(mails(client.sent), 'sent').toEqual(javaMails(java.sent));
const accounts = (nodeId: string) => engine.emailServer(nodeId)!.state.accounts.map((each) => [each.username, mails(each.mailbox)]);
expect(accounts(SERVER), 'filius.de').toEqual(java.accounts.map(([username, list]) => [username, javaMails(list)]));
expect(accounts(OTHER_SERVER), 'andere.de').toEqual(java.otherAccounts.map(([username, list]) => [username, javaMails(list)]));
});
}
});
+39 -3
View File
@@ -17,6 +17,7 @@ import {
type TopologySnapshotInput,
addressText,
formatMail,
isMailAddress,
mailboxEntry,
MailServerState,
mailSize,
@@ -53,6 +54,14 @@ interface JavaCase {
const CLIENT = '192.168.0.20';
/** Without a network, no mail goes to another domain (MailCases: Java's server thread never runs). */
const NO_OUTBOX = {
forward: () => undefined,
*lookUpAndSend(): Steps<string[]> {
return [];
},
};
/** Runs a dialogue's steps to their end: nothing waits here, the connection sends at once. */
function run(steps: Steps): void {
for (let next = steps.next(); next.done !== true; next = steps.next()) throw new Error('a step waited');
@@ -70,6 +79,7 @@ function play(javaCase: JavaCase, language: PageLanguage): JavaStep[] {
() => undefined,
() => '192.168.0.30',
() => 0,
NO_OUTBOX,
);
const sent: string[] = [];
const connection = {
@@ -264,6 +274,15 @@ describe('e-mail as Java writes it', () => {
expect(addressText({ name: null, mailAddress: null })).toBeNull();
});
it("checks an address as Java's pattern does: an address, or a name and an address in angle brackets", () => {
expect(isMailAddress('anna@filius.de')).toBe(true);
expect(isMailAddress('Anna Muster <anna.m-x_1@filius.de.>')).toBe(true);
expect(isMailAddress('anna@filius.de x')).toBe(false);
expect(isMailAddress('Anna <anna@filius.de> x')).toBe(false);
expect(isMailAddress('anna@')).toBe(false);
expect(isMailAddress('anna..b@filius.de')).toBe(false);
});
it('writes no Bcc, and counts a size as Java does', () => {
const mail = parseMail('From: A <a@b.de>\nTo: x@y.de, Y <y@z.de>\nBcc: geheim@b.de\nSubject: Hallo \n\nText\n');
expect(mail.bcc).toEqual([{ name: null, mailAddress: 'geheim@b.de' }]);
@@ -309,7 +328,7 @@ function talk(engine: NetworkRuntimeEngine, port: number, ...messages: string[])
describe('the e-mail server beyond the cases', () => {
it('writes a failed answer as Java does (sw_smtpmitarbeiter_msg14)', () => {
const lines: EmailServerLogEntry[] = [];
const state = new MailServerState('filius.de', [], 'de', (entry) => lines.push(entry), () => undefined, () => '10.0.0.1', () => 0);
const state = new MailServerState('filius.de', [], 'de', (entry) => lines.push(entry), () => undefined, () => '10.0.0.1', () => 0, NO_OUTBOX);
const session = new SmtpSession(state, {
address: '10.0.0.2',
*send(): Steps<boolean> {
@@ -374,12 +393,29 @@ describe('the e-mail server in the engine', () => {
expect(mailbox()).toEqual([]);
});
it('sends no notice to an empty sender, also when the recipient is unknown (Java: <> gets none)', () => {
const engine = new NetworkRuntimeEngine(14);
engine.handle({ type: 'start', snapshot: topologySnapshot(mailNetwork()), seed: 14, initialTimeMilliseconds: 0 });
expect(talk(engine, 25, 'HELO x', 'MAIL FROM:<>', 'RCPT TO:<niemand@filius.de>', 'DATA', 'Subject: x\n\ny\n.\n', 'QUIT').at(-1)).toBe('221 Server beendet Verbindung.');
expect(engine.emailServer(SERVER)!.log.all.filter((line) => line.kind === 'stored')).toEqual([]);
});
it("sends a notice to another domain's sender with an empty sender, and none back when that fails", () => {
const engine = new NetworkRuntimeEngine(14);
engine.handle({ type: 'start', snapshot: topologySnapshot(mailNetwork()), seed: 14, initialTimeMilliseconds: 0 });
// nirgendwo.de has no mail exchange (no DNS server here): the notice goes nowhere, and its own failure tells no one.
expect(talk(engine, 25, 'HELO x', 'MAIL FROM:<x@nirgendwo.de>', 'RCPT TO:<niemand@filius.de>', 'DATA', 'Subject: x\n\ny\n.\n', 'QUIT').at(-1)).toBe(
'221 Server beendet Verbindung.',
);
expect(engine.emailServer(SERVER)!.log.all.filter((line) => line.kind === 'stored' || line.kind === 'connectionStarting')).toEqual([]);
});
it("writes a notice's date in the project's language, at the wall clock's time", () => {
const at = new Date(2026, 9, 1, 18, 5).getTime();
const notice = (language: PageLanguage) => {
const accounts: MailAccount[] = MAIL_ACCOUNTS.map((account) => ({ ...account, mailbox: [] }));
const state = new MailServerState('filius.de', accounts, language, () => undefined, () => undefined, () => '10.0.0.1', () => at);
state.deliver(parseMail('Subject: x\n\ny\n'), 'anna@filius.de', ['niemand@filius.de']);
const state = new MailServerState('filius.de', accounts, language, () => undefined, () => undefined, () => '10.0.0.1', () => at, NO_OUTBOX);
run(state.deliver(parseMail('Subject: x\n\ny\n'), 'anna@filius.de', ['niemand@filius.de']));
return formatMail(accounts[0]!.mailbox[0]!.mail);
};
expect(notice('de')).toContain('\nDate: 01.10.26, 18:05\nMail-Server: 10.0.0.1\n');
+30 -1
View File
@@ -2,7 +2,7 @@
// message bundle says them: for comparing the core's structured lines with
// what the Java oracle recorded (echoJava.test.ts, webJava.test.ts).
import type { EchoServerLogEntry, EmailServerLogEntry, PageLanguage, SendFailure, SimpleClientLogEntry, WebBrowserLogEntry } from '../src/index.js';
import type { EchoServerLogEntry, EmailClientLogEntry, EmailServerLogEntry, PageLanguage, SendFailure, SimpleClientLogEntry, WebBrowserLogEntry } from '../src/index.js';
/** Java's client and server look every 100 ms whether they are connected, or switched off; the core at once. */
export const POLL_MILLISECONDS = 100;
@@ -102,5 +102,34 @@ export function emailServerText(entry: EmailServerLogEntry, language: PageLangua
return language === 'de' ? `Fehler beim Senden an ${entry.address}` : `Error while sending ${entry.address}`;
case 'stored':
return language === 'de' ? `E-Mail für ${entry.username} im Postfach gespeichert.` : `E-mail for ${entry.username} saved in mailbox.`;
default:
return emailClientText(entry);
}
}
/**
* What the SMTP and POP3 clients tell the program they work for, as Java's
* observers get it (sw_smtpclient_msg1 to msg4; a failure as Java's
* exception prints itself; the window shows its message).
*/
export function emailClientText(entry: EmailClientLogEntry): string {
switch (entry.kind) {
case 'connectionStarting':
return '----';
case 'smtpConnected':
return `Verbindung zu ${entry.address}:${entry.port} hergestellt`;
case 'mailSent':
return `Mail versendet an ${entry.recipients}`;
case 'smtpDisconnected':
return `Verbindung zu ${entry.address}:${entry.port} getrennt`;
case 'failed':
switch (entry.failure.kind) {
case 'answer':
return `java.lang.Exception: ${entry.failure.text}`;
case 'connect':
return entry.failure.failure === 'timedOut' ? `filius.exception.TimeOutException: ${CONNECT_FAILURES.timedOut}` : `filius.exception.VerbindungsException: ${CONNECT_FAILURES[entry.failure.failure]}`;
case 'receiveFailed':
return 'filius.exception.VerbindungsException: Verbindung nicht aufgebaut!';
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,457 @@
{
"frames": {
"00000000-0000-0000-0000-000000000201": [
{
"time": 518,
"source": "02:00:00:00:02:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.0.20",
"targetIp": "192.168.0.10"
},
{
"time": 756,
"source": "02:00:00:00:02:12",
"destination": "02:00:00:00:02:11",
"arp": 2,
"senderIp": "192.168.0.10",
"targetIp": "192.168.0.20"
},
{
"time": 758,
"source": "02:00:00:00:02:11",
"destination": "02:00:00:00:02:12",
"sourceIp": "192.168.0.20",
"destinationIp": "192.168.0.10",
"protocol": 6,
"sourcePort": 56629,
"destinationPort": 25,
"payload": null,
"tcpFlags": "S",
"sequence": 5000000,
"acknowledgement": 0
}
],
"00000000-0000-0000-0000-000000000202": [
{
"time": 642,
"source": "02:00:00:00:02:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.0.20",
"targetIp": "192.168.0.10"
},
{
"time": 644,
"source": "02:00:00:00:02:12",
"destination": "02:00:00:00:02:11",
"arp": 2,
"senderIp": "192.168.0.10",
"targetIp": "192.168.0.20"
},
{
"time": 872,
"source": "02:00:00:00:02:11",
"destination": "02:00:00:00:02:12",
"sourceIp": "192.168.0.20",
"destinationIp": "192.168.0.10",
"protocol": 6,
"sourcePort": 56629,
"destinationPort": 25,
"payload": null,
"tcpFlags": "S",
"sequence": 5000000,
"acknowledgement": 0
}
],
"00000000-0000-0000-0000-000000000203": [
{
"time": 642,
"source": "02:00:00:00:02:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.0.20",
"targetIp": "192.168.0.10"
}
],
"00000000-0000-0000-0000-000000000204": [
{
"time": 642,
"source": "02:00:00:00:02:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.0.20",
"targetIp": "192.168.0.10"
}
]
},
"rows": {
"00000000-0000-0000-0000-000000000201": [
[
"1",
"02:00:00:00:02:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.10",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.10 [op=REQUEST, sender=02:00:00:00:02:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.10]"
],
[
"2",
"02:00:00:00:02:12",
"02:00:00:00:02:11",
"",
"Netzzugang",
"0x806"
],
[
"2",
"192.168.0.10",
"192.168.0.20",
"ARP",
"Vermittlung",
"MAC ist 02:00:00:00:02:12 [op=REPLY, sender=02:00:00:00:02:12|192.168.0.10, target=02:00:00:00:02:11|192.168.0.20]"
],
[
"3",
"02:00:00:00:02:11",
"02:00:00:00:02:12",
"",
"Netzzugang",
"0x800"
],
[
"3",
"192.168.0.20",
"192.168.0.10",
"IP",
"Vermittlung",
"Protokoll: 6, TTL: 64"
],
[
"3",
"192.168.0.20:56629",
"192.168.0.10:25",
"TCP",
"Transport",
"SYN, SEQ: 5.000.000"
]
],
"00000000-0000-0000-0000-000000000202": [
[
"1",
"02:00:00:00:02:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.10",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.10 [op=REQUEST, sender=02:00:00:00:02:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.10]"
],
[
"2",
"02:00:00:00:02:12",
"02:00:00:00:02:11",
"",
"Netzzugang",
"0x806"
],
[
"2",
"192.168.0.10",
"192.168.0.20",
"ARP",
"Vermittlung",
"MAC ist 02:00:00:00:02:12 [op=REPLY, sender=02:00:00:00:02:12|192.168.0.10, target=02:00:00:00:02:11|192.168.0.20]"
],
[
"3",
"02:00:00:00:02:11",
"02:00:00:00:02:12",
"",
"Netzzugang",
"0x800"
],
[
"3",
"192.168.0.20",
"192.168.0.10",
"IP",
"Vermittlung",
"Protokoll: 6, TTL: 64"
],
[
"3",
"192.168.0.20:56629",
"192.168.0.10:25",
"TCP",
"Transport",
"SYN, SEQ: 5.000.000"
]
],
"00000000-0000-0000-0000-000000000203": [
[
"1",
"02:00:00:00:02:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.10",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.10 [op=REQUEST, sender=02:00:00:00:02:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.10]"
]
],
"00000000-0000-0000-0000-000000000204": [
[
"1",
"02:00:00:00:02:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.10",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.10 [op=REQUEST, sender=02:00:00:00:02:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.10]"
]
]
},
"layeredRows": {
"00000000-0000-0000-0000-000000000201": [
[
"1",
"02:00:00:00:02:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.10",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.10 [op=REQUEST, sender=02:00:00:00:02:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.10]"
],
[
"2",
"02:00:00:00:02:12",
"02:00:00:00:02:11",
"",
"Netzzugang",
"0x806"
],
[
"2",
"192.168.0.10",
"192.168.0.20",
"ARP",
"Vermittlung",
"MAC ist 02:00:00:00:02:12 [op=REPLY, sender=02:00:00:00:02:12|192.168.0.10, target=02:00:00:00:02:11|192.168.0.20]"
],
[
"3",
"02:00:00:00:02:11",
"02:00:00:00:02:12",
"",
"Netzzugang",
"0x800"
],
[
"3",
"192.168.0.20",
"192.168.0.10",
"IP",
"Vermittlung",
"Protokoll: 6, TTL: 64"
],
[
"3",
"56629",
"25",
"TCP",
"Transport",
"SYN, SEQ: 5.000.000"
]
],
"00000000-0000-0000-0000-000000000202": [
[
"1",
"02:00:00:00:02:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.10",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.10 [op=REQUEST, sender=02:00:00:00:02:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.10]"
],
[
"2",
"02:00:00:00:02:12",
"02:00:00:00:02:11",
"",
"Netzzugang",
"0x806"
],
[
"2",
"192.168.0.10",
"192.168.0.20",
"ARP",
"Vermittlung",
"MAC ist 02:00:00:00:02:12 [op=REPLY, sender=02:00:00:00:02:12|192.168.0.10, target=02:00:00:00:02:11|192.168.0.20]"
],
[
"3",
"02:00:00:00:02:11",
"02:00:00:00:02:12",
"",
"Netzzugang",
"0x800"
],
[
"3",
"192.168.0.20",
"192.168.0.10",
"IP",
"Vermittlung",
"Protokoll: 6, TTL: 64"
],
[
"3",
"56629",
"25",
"TCP",
"Transport",
"SYN, SEQ: 5.000.000"
]
],
"00000000-0000-0000-0000-000000000203": [
[
"1",
"02:00:00:00:02:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.10",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.10 [op=REQUEST, sender=02:00:00:00:02:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.10]"
]
],
"00000000-0000-0000-0000-000000000204": [
[
"1",
"02:00:00:00:02:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.10",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.10 [op=REQUEST, sender=02:00:00:00:02:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.10]"
]
]
},
"events": [
[
212,
"client",
"null"
],
[
214,
"server",
"null"
],
[
218,
"otherServer",
"null"
],
[
500,
"client",
"call versendeEmail bob@filius.de | | "
],
[
503,
"client",
"----"
],
[
8810,
"client",
"filius.exception.TimeOutException: Zeitüberschreitung beim Verbindungsaufbau"
]
],
"inbox": [],
"sent": [],
"accounts": [
[
"anna",
[
[
"From: Bob Beispiel <bob@filius.de>\nTo: <anna@filius.de>\nSubject: Hallo Anna\n\nErste Zeile\nZweite Zeile",
"true",
"false"
],
[
"From: Carla <carla@andere.de>\nTo: Anna Muster <anna@filius.de>, <bob@filius.de>\nCc: <chef@filius.de>\nSubject: Termin\n\nMorgen um 10?",
"true",
"false"
]
]
],
[
"bob",
[]
]
],
"otherAccounts": [
[
"carla",
[]
]
]
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,633 @@
{
"frames": {
"00000000-0000-0000-0000-000000000201": [
{
"time": 541,
"source": "02:00:00:00:02:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.0.20",
"targetIp": "192.168.0.10"
},
{
"time": 810,
"source": "02:00:00:00:02:12",
"destination": "02:00:00:00:02:11",
"arp": 2,
"senderIp": "192.168.0.10",
"targetIp": "192.168.0.20"
},
{
"time": 819,
"source": "02:00:00:00:02:11",
"destination": "02:00:00:00:02:12",
"sourceIp": "192.168.0.20",
"destinationIp": "192.168.0.10",
"protocol": 17,
"sourcePort": 52467,
"destinationPort": 53,
"payload": "ID=30279 QR=0 RCODE=0 QDCOUNT=1 ANCOUNT=0 NSCOUNT=0 ARCOUNT=0 \ngibtsnicht.filius.de. A IN\n"
},
{
"time": 1065,
"source": "02:00:00:00:02:12",
"destination": "02:00:00:00:02:11",
"sourceIp": "192.168.0.10",
"destinationIp": "192.168.0.20",
"protocol": 17,
"sourcePort": 53,
"destinationPort": 52467,
"payload": "ID=30279 QR=1 RCODE=0 QDCOUNT=0 ANCOUNT=0 NSCOUNT=0 ARCOUNT=0 \n"
}
],
"00000000-0000-0000-0000-000000000202": [
{
"time": 687,
"source": "02:00:00:00:02:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.0.20",
"targetIp": "192.168.0.10"
},
{
"time": 692,
"source": "02:00:00:00:02:12",
"destination": "02:00:00:00:02:11",
"arp": 2,
"senderIp": "192.168.0.10",
"targetIp": "192.168.0.20"
},
{
"time": 937,
"source": "02:00:00:00:02:11",
"destination": "02:00:00:00:02:12",
"sourceIp": "192.168.0.20",
"destinationIp": "192.168.0.10",
"protocol": 17,
"sourcePort": 52467,
"destinationPort": 53,
"payload": "ID=30279 QR=0 RCODE=0 QDCOUNT=1 ANCOUNT=0 NSCOUNT=0 ARCOUNT=0 \ngibtsnicht.filius.de. A IN\n"
},
{
"time": 949,
"source": "02:00:00:00:02:12",
"destination": "02:00:00:00:02:11",
"sourceIp": "192.168.0.10",
"destinationIp": "192.168.0.20",
"protocol": 17,
"sourcePort": 53,
"destinationPort": 52467,
"payload": "ID=30279 QR=1 RCODE=0 QDCOUNT=0 ANCOUNT=0 NSCOUNT=0 ARCOUNT=0 \n"
}
],
"00000000-0000-0000-0000-000000000203": [
{
"time": 690,
"source": "02:00:00:00:02:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.0.20",
"targetIp": "192.168.0.10"
}
],
"00000000-0000-0000-0000-000000000204": [
{
"time": 688,
"source": "02:00:00:00:02:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.0.20",
"targetIp": "192.168.0.10"
}
]
},
"rows": {
"00000000-0000-0000-0000-000000000201": [
[
"1",
"02:00:00:00:02:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.10",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.10 [op=REQUEST, sender=02:00:00:00:02:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.10]"
],
[
"2",
"02:00:00:00:02:12",
"02:00:00:00:02:11",
"",
"Netzzugang",
"0x806"
],
[
"2",
"192.168.0.10",
"192.168.0.20",
"ARP",
"Vermittlung",
"MAC ist 02:00:00:00:02:12 [op=REPLY, sender=02:00:00:00:02:12|192.168.0.10, target=02:00:00:00:02:11|192.168.0.20]"
],
[
"3",
"02:00:00:00:02:11",
"02:00:00:00:02:12",
"",
"Netzzugang",
"0x800"
],
[
"3",
"192.168.0.20",
"192.168.0.10",
"IP",
"Vermittlung",
"Protokoll: 17, TTL: 64"
],
[
"3",
"192.168.0.20:52467",
"192.168.0.10:53",
"UDP",
"Transport",
""
],
[
"3",
"192.168.0.20:52467",
"192.168.0.10:53",
"DNS",
"Anwendung",
"ID=30279 QR=0 RCODE=0 QDCOUNT=1 ANCOUNT=0 NSCOUNT=0 ARCOUNT=0 \ngibtsnicht.filius.de. A IN\n"
],
[
"4",
"02:00:00:00:02:12",
"02:00:00:00:02:11",
"",
"Netzzugang",
"0x800"
],
[
"4",
"192.168.0.10",
"192.168.0.20",
"IP",
"Vermittlung",
"Protokoll: 17, TTL: 64"
],
[
"4",
"192.168.0.10:53",
"192.168.0.20:52467",
"UDP",
"Transport",
""
],
[
"4",
"192.168.0.10:53",
"192.168.0.20:52467",
"DNS",
"Anwendung",
"ID=30279 QR=1 RCODE=0 QDCOUNT=0 ANCOUNT=0 NSCOUNT=0 ARCOUNT=0 \n"
]
],
"00000000-0000-0000-0000-000000000202": [
[
"1",
"02:00:00:00:02:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.10",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.10 [op=REQUEST, sender=02:00:00:00:02:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.10]"
],
[
"2",
"02:00:00:00:02:12",
"02:00:00:00:02:11",
"",
"Netzzugang",
"0x806"
],
[
"2",
"192.168.0.10",
"192.168.0.20",
"ARP",
"Vermittlung",
"MAC ist 02:00:00:00:02:12 [op=REPLY, sender=02:00:00:00:02:12|192.168.0.10, target=02:00:00:00:02:11|192.168.0.20]"
],
[
"3",
"02:00:00:00:02:11",
"02:00:00:00:02:12",
"",
"Netzzugang",
"0x800"
],
[
"3",
"192.168.0.20",
"192.168.0.10",
"IP",
"Vermittlung",
"Protokoll: 17, TTL: 64"
],
[
"3",
"192.168.0.20:52467",
"192.168.0.10:53",
"UDP",
"Transport",
""
],
[
"3",
"192.168.0.20:52467",
"192.168.0.10:53",
"DNS",
"Anwendung",
"ID=30279 QR=0 RCODE=0 QDCOUNT=1 ANCOUNT=0 NSCOUNT=0 ARCOUNT=0 \ngibtsnicht.filius.de. A IN\n"
],
[
"4",
"02:00:00:00:02:12",
"02:00:00:00:02:11",
"",
"Netzzugang",
"0x800"
],
[
"4",
"192.168.0.10",
"192.168.0.20",
"IP",
"Vermittlung",
"Protokoll: 17, TTL: 64"
],
[
"4",
"192.168.0.10:53",
"192.168.0.20:52467",
"UDP",
"Transport",
""
],
[
"4",
"192.168.0.10:53",
"192.168.0.20:52467",
"DNS",
"Anwendung",
"ID=30279 QR=1 RCODE=0 QDCOUNT=0 ANCOUNT=0 NSCOUNT=0 ARCOUNT=0 \n"
]
],
"00000000-0000-0000-0000-000000000203": [
[
"1",
"02:00:00:00:02:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.10",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.10 [op=REQUEST, sender=02:00:00:00:02:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.10]"
]
],
"00000000-0000-0000-0000-000000000204": [
[
"1",
"02:00:00:00:02:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.10",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.10 [op=REQUEST, sender=02:00:00:00:02:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.10]"
]
]
},
"layeredRows": {
"00000000-0000-0000-0000-000000000201": [
[
"1",
"02:00:00:00:02:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.10",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.10 [op=REQUEST, sender=02:00:00:00:02:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.10]"
],
[
"2",
"02:00:00:00:02:12",
"02:00:00:00:02:11",
"",
"Netzzugang",
"0x806"
],
[
"2",
"192.168.0.10",
"192.168.0.20",
"ARP",
"Vermittlung",
"MAC ist 02:00:00:00:02:12 [op=REPLY, sender=02:00:00:00:02:12|192.168.0.10, target=02:00:00:00:02:11|192.168.0.20]"
],
[
"3",
"02:00:00:00:02:11",
"02:00:00:00:02:12",
"",
"Netzzugang",
"0x800"
],
[
"3",
"192.168.0.20",
"192.168.0.10",
"IP",
"Vermittlung",
"Protokoll: 17, TTL: 64"
],
[
"3",
"52467",
"53",
"UDP",
"Transport",
""
],
[
"3",
null,
null,
"DNS",
"Anwendung",
"ID=30279 QR=0 RCODE=0 QDCOUNT=1 ANCOUNT=0 NSCOUNT=0 ARCOUNT=0 \ngibtsnicht.filius.de. A IN\n"
],
[
"4",
"02:00:00:00:02:12",
"02:00:00:00:02:11",
"",
"Netzzugang",
"0x800"
],
[
"4",
"192.168.0.10",
"192.168.0.20",
"IP",
"Vermittlung",
"Protokoll: 17, TTL: 64"
],
[
"4",
"53",
"52467",
"UDP",
"Transport",
""
],
[
"4",
null,
null,
"DNS",
"Anwendung",
"ID=30279 QR=1 RCODE=0 QDCOUNT=0 ANCOUNT=0 NSCOUNT=0 ARCOUNT=0 \n"
]
],
"00000000-0000-0000-0000-000000000202": [
[
"1",
"02:00:00:00:02:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.10",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.10 [op=REQUEST, sender=02:00:00:00:02:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.10]"
],
[
"2",
"02:00:00:00:02:12",
"02:00:00:00:02:11",
"",
"Netzzugang",
"0x806"
],
[
"2",
"192.168.0.10",
"192.168.0.20",
"ARP",
"Vermittlung",
"MAC ist 02:00:00:00:02:12 [op=REPLY, sender=02:00:00:00:02:12|192.168.0.10, target=02:00:00:00:02:11|192.168.0.20]"
],
[
"3",
"02:00:00:00:02:11",
"02:00:00:00:02:12",
"",
"Netzzugang",
"0x800"
],
[
"3",
"192.168.0.20",
"192.168.0.10",
"IP",
"Vermittlung",
"Protokoll: 17, TTL: 64"
],
[
"3",
"52467",
"53",
"UDP",
"Transport",
""
],
[
"3",
null,
null,
"DNS",
"Anwendung",
"ID=30279 QR=0 RCODE=0 QDCOUNT=1 ANCOUNT=0 NSCOUNT=0 ARCOUNT=0 \ngibtsnicht.filius.de. A IN\n"
],
[
"4",
"02:00:00:00:02:12",
"02:00:00:00:02:11",
"",
"Netzzugang",
"0x800"
],
[
"4",
"192.168.0.10",
"192.168.0.20",
"IP",
"Vermittlung",
"Protokoll: 17, TTL: 64"
],
[
"4",
"53",
"52467",
"UDP",
"Transport",
""
],
[
"4",
null,
null,
"DNS",
"Anwendung",
"ID=30279 QR=1 RCODE=0 QDCOUNT=0 ANCOUNT=0 NSCOUNT=0 ARCOUNT=0 \n"
]
],
"00000000-0000-0000-0000-000000000203": [
[
"1",
"02:00:00:00:02:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.10",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.10 [op=REQUEST, sender=02:00:00:00:02:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.10]"
]
],
"00000000-0000-0000-0000-000000000204": [
[
"1",
"02:00:00:00:02:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.10",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.10 [op=REQUEST, sender=02:00:00:00:02:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.10]"
]
]
},
"events": [
[
284,
"client",
"null"
],
[
304,
"server",
"null"
],
[
317,
"otherServer",
"null"
],
[
500,
"client",
"call versendeEmail bob@filius.de | | "
],
[
508,
"client",
"----"
],
[
1069,
"client",
"filius.exception.VerbindungsException: Ziel konnte nicht bestimmt werden!"
]
],
"inbox": [],
"sent": [],
"accounts": [
[
"anna",
[
[
"From: Bob Beispiel <bob@filius.de>\nTo: <anna@filius.de>\nSubject: Hallo Anna\n\nErste Zeile\nZweite Zeile",
"true",
"false"
],
[
"From: Carla <carla@andere.de>\nTo: Anna Muster <anna@filius.de>, <bob@filius.de>\nCc: <chef@filius.de>\nSubject: Termin\n\nMorgen um 10?",
"true",
"false"
]
]
],
[
"bob",
[]
]
],
"otherAccounts": [
[
"carla",
[]
]
]
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -106,11 +106,13 @@
},
"00000000-0000-0000-0000-000000000203": {
"ip": "192.168.0.30",
"mask": "255.255.255.0"
"mask": "255.255.255.0",
"dns": "192.168.0.10"
},
"00000000-0000-0000-0000-000000000204": {
"ip": "192.168.0.31",
"mask": "255.255.255.0"
"mask": "255.255.255.0",
"dns": "192.168.0.10"
}
},
"dnsServers": {
@@ -119,7 +121,11 @@
"www.filius.de A 192.168.0.30",
"seite.filius.de A 192.168.0.30",
"andere.filius.de A 192.168.0.30",
"leer.filius.de A 192.168.0.31"
"leer.filius.de A 192.168.0.31",
"filius.de MX mail.filius.de",
"mail.filius.de A 192.168.0.30",
"andere.de MX mail.andere.de",
"mail.andere.de A 192.168.0.31"
]
}
}