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
+161
View File
@@ -0,0 +1,161 @@
// Feeds FILIUS' SMTP and POP3 workers (SMTPMitarbeiter, POP3Mitarbeiter of
// an EmailServer) with messages from a client, one after the other, without
// a network: the worker's socket only writes down what it is to send. After
// each message it writes what the worker sent, the lines the e-mail server
// wrote into its window, whether the socket was closed, and the accounts
// (each message as Email.toString, new, marked for deletion). The reference
// for the core's e-mail server (packages/core/src/runtime/apps/emailServer.ts).
// Each case starts from a new server with the accounts of EmailRun (domain
// filius.de, its address 192.168.0.30, the client 192.168.0.20). The cases
// file is ASCII, with Java-style unicode escapes (backslash, u, four hex
// digits) for anything else, a message per line:
//
// case <name> smtp|pop3
// <message>
//
// Usage: java -cp filius.jar:lib/*:classes MailCases <de|en> <cases txt> <output json>
import filius.hardware.knoten.Rechner;
import filius.rahmenprogramm.Information;
import filius.software.email.Email;
import filius.software.email.EmailKonto;
import filius.software.email.EmailServer;
import filius.software.email.POP3Mitarbeiter;
import filius.software.email.POP3Server;
import filius.software.email.SMTPMitarbeiter;
import filius.software.email.SMTPServer;
import filius.software.system.Datei;
import filius.software.system.InternetKnotenBetriebssystem;
import filius.software.transportschicht.TCPSocket;
import java.io.FileOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public class MailCases {
/** A socket that only writes down what is sent, and whether it was closed. */
static class Recorder extends TCPSocket {
final List<String> sent = new ArrayList<>();
boolean closed = false;
Recorder(InternetKnotenBetriebssystem system) throws Exception {
super(system, "192.168.0.20", 50000);
}
@Override
public synchronized void senden(String nachricht) {
sent.add(nachricht);
}
@Override
public void schliessen() {
closed = true;
}
@Override
public void beenden() {
closed = true;
}
@Override
public boolean istVerbunden() {
return !closed;
}
@Override
public String holeZielIPAdresse() {
return "192.168.0.20";
}
}
public static void main(String[] args) throws Exception {
// Before any class reads the message bundle (I18n.messages is read once).
Information.getInformation().setLocale(args[0].equals("en") ? new Locale("en", "GB") : Locale.GERMANY);
List<Object> cases = new ArrayList<>();
List<Object> steps = null;
EmailServer server = null;
Recorder socket = null;
Object worker = null;
Method process = null;
List<String> lines = new ArrayList<>();
for (String line : Files.readAllLines(Path.of(args[1]), StandardCharsets.US_ASCII)) {
if (line.isEmpty() || line.startsWith("#")) continue;
if (line.startsWith("case ")) {
String[] words = line.split(" ");
Map<String, Object> current = new LinkedHashMap<>();
steps = new ArrayList<>();
current.put("name", words[1]);
current.put("protocol", words[2]);
current.put("steps", steps);
cases.add(current);
Rechner host = new Rechner();
InternetKnotenBetriebssystem system = (InternetKnotenBetriebssystem) host.getSystemSoftware();
system.setzeIPAdresse("192.168.0.30");
system.installApp("filius.software.email.EmailServer", false);
server = (EmailServer) system.holeSoftware("filius.software.email.EmailServer");
system.getDateisystem().speicherDatei("/mailserver", new Datei("konten.txt", "txt", EmailRun.ACCOUNTS));
server.setMailDomain("filius.de");
server.kontenLaden();
lines.clear();
List<String> window = lines;
server.hinzuBeobachter((observable, data) -> {
if (data != null) window.add(String.valueOf(data));
});
socket = new Recorder(system);
if (words[2].equals("smtp")) {
worker = new SMTPMitarbeiter(socket, new SMTPServer(25, server));
process = SMTPMitarbeiter.class.getDeclaredMethod("verarbeiteNachricht", String.class);
} else {
worker = new POP3Mitarbeiter(socket, new POP3Server(110, server));
process = POP3Mitarbeiter.class.getDeclaredMethod("verarbeiteNachricht", String.class);
}
process.setAccessible(true);
// The greeting the worker sends when it begins.
steps.add(step(null, socket, lines, server, null));
continue;
}
String message = IpCheckCases.unescape(line);
String thrown = null;
try {
process.invoke(worker, message);
} catch (InvocationTargetException error) {
thrown = error.getCause().getClass().getSimpleName();
}
steps.add(step(message, socket, lines, server, thrown));
}
try (FileOutputStream out = new FileOutputStream(args[2])) {
out.write((FlsOracle.Json.write(cases, "") + "\n").getBytes(StandardCharsets.UTF_8));
}
System.out.println("mail cases (" + args[0] + "): " + cases.size());
System.exit(0);
}
static Map<String, Object> step(String message, Recorder socket, List<String> lines, EmailServer server, String thrown) {
Map<String, Object> step = new LinkedHashMap<>();
step.put("message", message);
step.put("sent", new ArrayList<>(socket.sent));
step.put("lines", new ArrayList<>(lines));
step.put("closed", socket.closed);
step.put("thrown", thrown);
List<Object> accounts = new ArrayList<>();
for (EmailKonto account : server.getListeBenutzerkonten()) {
List<Object> messages = new ArrayList<>();
for (Email email : account.getNachrichten()) messages.add(Arrays.asList(email.toString(), email.getNeu(), email.getDelete()));
accounts.add(Arrays.asList(account.getBenutzername(), messages));
}
step.put("accounts", accounts);
socket.sent.clear();
lines.clear();
return step;
}
}