feat: F4.6g1 the echo server and the generic client windows

Oracle EchoWindowRun uses Java's echo server and client windows as a
person does; SaveAfterRunRun echo saves what Java keeps after the run.
The room takes setActive with a port, connect/disconnect/send, sends
both views and writes the server's port and the client's target back.
The editor's windows show Java's buttons, fields and lines; a port that
is no port is marked and not handed over (Java's button throws).
Core fix: an empty message (one empty segment with the push, as Java's
erstelleSegmente makes) counted as a repeat and never arrived.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 20:04:33 +00:00
co-authored by Claude Opus 5.5
parent 3dc2713f81
commit e31da62ae0
24 changed files with 1481 additions and 18 deletions
+11 -1
View File
@@ -2799,11 +2799,21 @@ export class NetworkRuntimeEngine {
}
}
/**
* A data segment (TCPSocket's receive loop): new data goes into the
* message, a push ends it. An empty message is one empty segment with the
* push (TCPSocket.erstelleSegmente makes at least one; an empty segment
* comes here only with it). Java ends a message with every push, also with
* a repeated segment's (an ACK lost: an empty message more); here only
* with a new one's — but an empty segment repeated cannot be told from a
* new one by its number, which it does not count on, and is an empty
* message again (named).
*/
private receiveTcpPayload(socketId: Id, segment: TcpSegment): void {
const session = this.stateValue.tcpSessionsById.get(socketId);
if (session === undefined) return;
const nextRemoteSequence = nextTcpSequenceNumber(segment);
if (nextRemoteSequence > session.remoteSequenceNumber) {
if (nextRemoteSequence > session.remoteSequenceNumber || segment.payload.length === 0) {
session.remoteSequenceNumber = nextRemoteSequence;
session.receiveAssembly = concatBytes(session.receiveAssembly, segment.payload);
if ((segment.flags & TCP_FLAG.push) !== 0) {
+16
View File
@@ -225,4 +225,20 @@ describe('the echo server and the generic client', () => {
);
expect(received).toEqual(['zuletzt', 'notConnected']);
});
// Measured with EchoWindowRun (packages/sim/test/echoWindow.java.json): Java's echo answers an empty message.
it('echoes an empty message: one empty segment with the push, once, and the next message after it', () => {
const engine = startedEngine(network());
const client = engine.simpleClient(CLIENT)!;
client.connect('10.0.0.1', 55555);
advance(engine, 6_000);
client.send('');
advance(engine, 10_000);
client.send('danach');
advance(engine, 14_000);
const server = engine.echoServer(SERVER)!;
expect(server.log.all.filter((line) => line.kind === 'received' || line.kind === 'sent').map((line) => `${line.kind}:${'text' in line ? line.text : ''}`)).toEqual(['received:', 'sent:', 'received:danach', 'sent:danach']);
expect(client.log.all.filter((line) => line.kind === 'received').map((line) => ('text' in line ? line.text : ''))).toEqual(['', 'danach']);
});
});