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:
@@ -116,6 +116,7 @@ import { RuntimeSpeed } from './speed.js';
|
||||
import { ApplicationThreads, waitUntil, type Steps, type Wait } from './threads.js';
|
||||
import { EchoServer, SimpleClient } from './apps/echo.js';
|
||||
import { WebBrowser, WebServer, type WebServerPage } from './apps/web.js';
|
||||
import { EmailClient, type EmailClientFolders } from './apps/emailClient.js';
|
||||
import { EmailServer } from './apps/emailServer.js';
|
||||
import { firewallLogPage, forwardingTablePage, ripTablePage, routerIndexPage } from './apps/routerPages.js';
|
||||
import { applyFirewallSettings, firewallSettingsPage } from './apps/firewallSettings.js';
|
||||
@@ -913,6 +914,7 @@ export class NetworkRuntimeEngine {
|
||||
private readonly webServersByNodeId = new Map<Id, WebServer>();
|
||||
private readonly webBrowsersByNodeId = new Map<Id, WebBrowser>();
|
||||
private readonly emailServersByNodeId = new Map<Id, EmailServer>();
|
||||
private readonly emailClientsByNodeId = new Map<Id, EmailClient>();
|
||||
/** The time of day a program writes into a mail (Java's new Date()). */
|
||||
private readonly wallClock: () => number;
|
||||
/** Each node's firewall log; it outlives a stop and a new start, as Java's log page does. */
|
||||
@@ -1043,6 +1045,7 @@ export class NetworkRuntimeEngine {
|
||||
this.webServersByNodeId.clear();
|
||||
this.webBrowsersByNodeId.clear();
|
||||
this.emailServersByNodeId.clear();
|
||||
this.emailClientsByNodeId.clear();
|
||||
this.arpLookups = [];
|
||||
this.protocolThreads.clear();
|
||||
this.dnsQueriesBySocketId.clear();
|
||||
@@ -1133,6 +1136,20 @@ export class NetworkRuntimeEngine {
|
||||
return this.emailServersByNodeId.get(nodeId);
|
||||
}
|
||||
|
||||
/** The node's e-mail program while the simulation runs, if it has one installed. */
|
||||
emailClient(nodeId: Id): EmailClient | undefined {
|
||||
return this.emailClientsByNodeId.get(nodeId);
|
||||
}
|
||||
|
||||
/** An e-mail program's folders as a run changed them, into the snapshot (and so into the project saved from it). */
|
||||
private setEmailClientFolders(nodeId: Id, folders: EmailClientFolders): void {
|
||||
const clients = new Map(this.stateValue.topologySnapshot.emailClientsByNodeId);
|
||||
const client = clients.get(nodeId);
|
||||
if (client === undefined) return;
|
||||
clients.set(nodeId, { ...client, ...folders });
|
||||
this.stateValue.topologySnapshot = withSnapshot(this.stateValue.topologySnapshot, { emailClientsByNodeId: clients });
|
||||
}
|
||||
|
||||
/** An e-mail server's accounts as a run changed them, into the snapshot (and so into the project saved from it). */
|
||||
private setEmailServerAccounts(nodeId: Id, accounts: readonly EmailServerAccount[]): void {
|
||||
const servers = new Map(this.stateValue.topologySnapshot.emailServersByNodeId);
|
||||
@@ -1153,6 +1170,7 @@ export class NetworkRuntimeEngine {
|
||||
this.webServersByNodeId.clear();
|
||||
this.webBrowsersByNodeId.clear();
|
||||
this.emailServersByNodeId.clear();
|
||||
this.emailClientsByNodeId.clear();
|
||||
for (const node of snapshot.nodes) {
|
||||
const echoServer = snapshot.echoServersByNodeId.get(node.id);
|
||||
if (echoServer !== undefined) this.echoServersByNodeId.set(node.id, new EchoServer(this, node.id, echoServer));
|
||||
@@ -1178,6 +1196,11 @@ export class NetworkRuntimeEngine {
|
||||
),
|
||||
);
|
||||
}
|
||||
const emailClient = snapshot.emailClientsByNodeId.get(node.id);
|
||||
if (emailClient !== undefined) {
|
||||
const nodeId = node.id;
|
||||
this.emailClientsByNodeId.set(nodeId, new EmailClient(this, nodeId, emailClient, (folders) => this.setEmailClientFolders(nodeId, folders)));
|
||||
}
|
||||
if ((node.kind === 'router' || node.kind === 'gateway') && !this.swiftRouters) this.startRouterWebServer(node.id, node.kind);
|
||||
}
|
||||
}
|
||||
@@ -1961,6 +1984,22 @@ export class NetworkRuntimeEngine {
|
||||
return { type: 'resolved', address: exchange === undefined ? undefined : findDnsRecords(exchange.data, 'A', resolution.records)[0]?.data };
|
||||
}
|
||||
|
||||
/** Resolver.holeIPAdresseMailServer in a program's thread (the e-mail server sending a mail on). */
|
||||
*resolveDnsMailServerSteps(nodeId: Id, domain: string): Steps<DnsAddressResolution> {
|
||||
const domainName = domain.endsWith('.') ? domain : `${domain}.`;
|
||||
let lookup: DnsLookup | undefined;
|
||||
const server = this.stateValue.topologySnapshot.deviceConfigurations.get(nodeId)?.dnsServer;
|
||||
this.beginDnsResolution(nodeId, domainName, 'MX', server, (result) => {
|
||||
lookup = result;
|
||||
});
|
||||
yield* waitUntil(() => lookup !== undefined || this.stateValue.phase !== 'running');
|
||||
const result = lookup as DnsLookup | undefined;
|
||||
if (result === undefined || result.type === 'timedOut') return { type: 'timedOut' };
|
||||
const records = [...result.answers, ...result.authority];
|
||||
const exchange = findDnsRecords(domainName, 'MX', records)[0];
|
||||
return { type: 'resolved', address: exchange === undefined ? undefined : findDnsRecords(exchange.data, 'A', records)[0]?.data };
|
||||
}
|
||||
|
||||
/**
|
||||
* DNSQueryAgent.query: one question from a fresh socket; `done` with the
|
||||
* first datagram back, or undefined after five round trips.
|
||||
|
||||
Reference in New Issue
Block a user