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

SMTP on port 25 stores mails for the domain's accounts as Java reads
them anew and tells a local sender of unknown recipients; POP3 on port
110 hands them out. The mailboxes are in the snapshot and go back into
the project. EmailRun plays four stories over the network; MailCases
feeds Java's workers without one (26 cases, de and en). 18 cases name
Java bugs that the core does not take.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 18:58:26 +00:00
co-authored by Claude Opus 5.5
parent 8438b42018
commit 015635926f
26 changed files with 45188 additions and 14 deletions
+148
View File
@@ -0,0 +1,148 @@
// Runs a story of FILIUS' e-mail server (EmailServer with its SMTPServer and
// POP3Server) on a core scenario's network in Java FILIUS and records what
// really happens: every frame each card saw, every line the server wrote
// into its window, every line of the generic client that talks to it, and
// the server's accounts at the end (each message as Email.toString, whether
// it is new, whether it is marked for deletion).
//
// EmailRun <scenario.json> <story> <milliseconds> <out.json>
//
// The stories use mail-lan (client on 192.168.0.20, e-mail server on .30,
// domain filius.de). The server's accounts come from /mailserver/konten.txt
// as below (anna with two messages, from bob and from carla; bob without),
// switched on.
// FILIUS' generic client (ClientBaustein) on .20 sends the commands as they
// are, one message each, after a pause for the answer.
// smtp to anna, then QUIT
// smtpErrors commands out of order, then a mail to a recipient the
// domain does not know (a notice goes to the local sender)
// pop3 log in, STAT, LIST, RETR and DELE both messages, QUIT;
// again: log in, STAT, QUIT
// pop3Errors unknown user, PASS before USER, wrong password, NOOP before
// and after log-in, USER after log-in
// Java's errors in these dialogues are measured without a network
// (MailCases), so the stories keep to what Java does right.
import filius.software.clientserver.ClientBaustein;
import filius.software.email.Email;
import filius.software.email.EmailKonto;
import filius.software.email.EmailServer;
import filius.software.system.Datei;
import filius.software.system.Dateisystem;
import java.util.ArrayList;
import java.util.List;
public class EmailRun {
static final String CLIENT = "00000000-0000-0000-0000-000000000201";
static final String SERVER = "00000000-0000-0000-0000-000000000203";
/** The server's accounts before the run, in EmailServer's file format. */
static final String ACCOUNTS = "anna;filius.de;geheim;Muster;Anna;#Bob Beispiel <bob@filius.de>$<anna@filius.de>$$$$Hallo Anna$Erste Zeile|Zweite Zeile"
+ "#Carla <carla@andere.de>$Anna Muster <anna@filius.de>,<bob@filius.de>$<chef@filius.de>$$$Termin$Morgen um 10?\n"
+ "bob;filius.de;passwort;Beispiel;Bob;\n";
static ScenarioNetwork network;
static ClientBaustein client;
static final List<Object[]> events = new ArrayList<>();
static synchronized void event(String who, String what) {
events.add(new Object[] { System.currentTimeMillis() - network.start, who, what });
}
static void sleepUntil(long milliseconds) throws InterruptedException {
long rest = network.start + milliseconds - System.currentTimeMillis();
if (rest > 0) Thread.sleep(rest);
}
static long at;
static void connect(int port) throws InterruptedException {
sleepUntil(at);
client.setZielIPAdresse("192.168.0.30");
client.setZielPort(port);
event("client", "call verbinden " + port);
client.verbinden();
at += 2500;
}
/** Each command after the answer to the one before. */
static void send(String... messages) throws InterruptedException {
for (String message : messages) {
sleepUntil(at);
event("client", "call senden " + message);
client.senden(message);
at += 1500;
}
}
public static void main(String[] args) throws Exception {
network = ScenarioNetwork.read(args[0]);
String story = args[1];
long end = Long.parseLong(args[2]);
network.system(SERVER).installApp("filius.software.email.EmailServer", false);
EmailServer server = (EmailServer) network.system(SERVER).holeSoftware("filius.software.email.EmailServer");
Dateisystem files = network.system(SERVER).getDateisystem();
files.speicherDatei("/mailserver", new Datei("konten.txt", "txt", ACCOUNTS));
server.setMailDomain("filius.de");
server.setAktiv(true);
server.hinzuBeobachter((observable, data) -> event("server", String.valueOf(data)));
network.system(CLIENT).installApp("filius.software.clientserver.ClientBaustein", false);
client = (ClientBaustein) network.system(CLIENT).holeSoftware("filius.software.clientserver.ClientBaustein");
client.hinzuBeobachter((observable, data) -> event("client", String.valueOf(data)));
network.start();
at = 500;
switch (story) {
case "smtp":
connect(25);
send("HELO 192.168.0.20", "MAIL FROM: <bob@filius.de>", "RCPT TO:<anna@filius.de>", "DATA",
"From: Bob Beispiel <bob@filius.de>\nTo: <anna@filius.de>\nSubject: Gruß\n\nHallo Anna,\nwie geht's?\r\n.\r\n", "QUIT");
break;
case "smtpErrors":
connect(25);
send("NOOP", "EHLO", "DATA", "MAIL FROM: bob@filius.de", "RCPT TO:<niemand@filius.de>", "DATA",
"MAIL FROM:<bob@filius.de>", "RCPT TO: anna", "NOOP", "DATA",
"From: <bob@filius.de>\nTo: <niemand@filius.de>\nSubject: Wer?\n\nIst da jemand?\n.\n", "QUIT");
break;
case "pop3":
connect(110);
send("USER anna", "PASS geheim", "STAT", "LIST", "RETR 0", "RETR 1", "DELE 0", "DELE 1", "QUIT");
at += 1000;
connect(110);
send("USER anna", "PASS geheim", "STAT", "QUIT");
break;
case "pop3Errors":
connect(110);
send("NOOP", "USER niemand", "PASS x", "USER anna", "PASS falsch", "PASS geheim", "NOOP", "USER bob", "QUIT");
break;
default:
throw new IllegalArgumentException("unknown story " + story);
}
sleepUntil(Math.max(end, at + 3000));
ScenarioNetwork.Json out = new ScenarioNetwork.Json();
out.open('{');
network.writeFrames(out);
synchronized (EmailRun.class) {
out.key("events").open('[');
for (Object[] each : events) out.open('[').number((Long) each[0]).string((String) each[1]).string((String) each[2]).close(']');
out.close(']');
}
out.key("accounts").open('[');
for (EmailKonto account : server.getListeBenutzerkonten()) {
out.open('[').string(account.getBenutzername());
for (Email email : account.getNachrichten()) {
out.open('[').string(email.toString()).string(String.valueOf(email.getNeu())).string(String.valueOf(email.getDelete())).close(']');
}
out.close(']');
}
out.close(']');
out.key("file").string(files.holeDatei("/mailserver/konten.txt").getDateiInhalt());
out.close('}');
network.stop();
ScenarioNetwork.write(args[3], out);
System.exit(0);
}
}