feat(core): TCP in the simulation core
engine.ts ports Swift's "Java-compatible TCP sockets" in one piece, since half a state machine could not be checked: server and client sockets (a free port from Java's random numbers on their own seed), the three-way handshake with its result (connected, invalid socket, unreachable, timed out), segments of 1460 bytes, messages up to the push, a timeout of 3 × 2.5 s with at most three attempts, closing through every Java state including TIME_WAIT (2.5 s), sequence numbers modulo 2^32. Three scenarios match the Swift engine trace for trace: a LAN with data over three segments, closing from both ends, a closed port and an unreachable host; two networks through a router with a firewall rule, data lost until the connection closes, and an accepted socket that does not hold the port of its closed listener; CLOSING, reached by a lost FIN. The engine delivers a FIN within the same close, so both ends close at once only with packet loss; an empty message does not arrive in Swift (it does not advance the sequence number), nor here. Swift's four TCP engine tests are ported, with tests for stopping, a foreign SYN+ACK at a listener and the flag names. 17 mutation probes, 16 caught; the other (delivered, not waited, not connected gives unreachable) cannot be reached through Swift's engine, which never sends a reset and always acknowledges correctly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,8 +21,12 @@ npm test # vergleicht mit den geschriebenen *.swift.json
|
||||
`sat`, `speed`; UDP (seit F2): `bindUdp` (Knoten, optional Port, lokale
|
||||
Adresse, Gegenstelle; Ergebnis die Socket-ID), `sendUdp` (Socket, Text,
|
||||
optional Ziel und Port), `receiveUdp` (Socket, optional Wartezeit),
|
||||
`closeSocket`. Das Ergebnis enthält die Sockets mit der Länge ihrer
|
||||
Warteschlange.
|
||||
`closeSocket`; TCP: `openTcpServer`, `openTcpClient`, `connectTcp`
|
||||
(Ergebnis `connected`, `invalidSocket`, `unreachable`, `timedOut`),
|
||||
`sendTcp` (mit `repeatCount` für lange Nachrichten), `receiveTcp`,
|
||||
`acceptedTcp`, `peerTcp`, `cleanTcp`; `closeSocket` schließt auch TCP.
|
||||
Das Ergebnis enthält die Sockets mit der Länge ihrer Warteschlange, dem
|
||||
TCP-Zustand, dem annehmenden Socket und den Sitzungswerten.
|
||||
|
||||
## Korpus
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ struct ScenarioStep: Decodable {
|
||||
// UDP: a socket is named by the id bindUdp returned.
|
||||
let port: UInt16?; let localIp: String?; let remoteIp: String?; let remotePort: UInt16?
|
||||
let socket: String?; let payload: String?; let timeout: UInt64?
|
||||
// TCP: the payload repeated this many times, for messages longer than a segment.
|
||||
let repeatCount: Int?; let localPort: UInt16?
|
||||
}
|
||||
struct Scenario: Decodable {
|
||||
let seed: UInt64
|
||||
@@ -245,6 +247,28 @@ func run(_ scenario: Scenario) -> JSON {
|
||||
"payload": String(decoding: item.datagram.payload, as: UTF8.self), "at": item.receivedAtMilliseconds,
|
||||
]
|
||||
} ?? NSNull())
|
||||
case "openTcpServer":
|
||||
results.append(engine.openTCPServerSocket(
|
||||
nodeID: uuid(step.node!), localPort: step.port!, localIPAddress: step.localIp
|
||||
)?.uuidString ?? NSNull())
|
||||
case "openTcpClient":
|
||||
results.append(engine.openTCPClientSocket(
|
||||
nodeID: uuid(step.node!), remoteIPAddress: step.target!, remotePort: step.port!,
|
||||
localIPAddress: step.localIp, localPort: step.localPort
|
||||
)?.uuidString ?? NSNull())
|
||||
case "connectTcp":
|
||||
results.append(String(describing: engine.connectTCPWithResult(socketID: uuid(step.socket!))))
|
||||
case "sendTcp":
|
||||
let text = String(repeating: step.payload ?? "", count: step.repeatCount ?? 1)
|
||||
results.append(engine.sendTCP(socketID: uuid(step.socket!), payload: Data(text.utf8)))
|
||||
case "receiveTcp":
|
||||
results.append(engine.receiveTCP(socketID: uuid(step.socket!)).map { String(decoding: $0, as: UTF8.self) } ?? NSNull())
|
||||
case "acceptedTcp":
|
||||
results.append((engine.state.tcpAcceptedSocketIDsByListenerID[uuid(step.socket!)] ?? []).map(\.uuidString))
|
||||
case "peerTcp":
|
||||
results.append(engine.peerTCPSocketID(socketID: uuid(step.socket!))?.uuidString ?? NSNull())
|
||||
case "cleanTcp":
|
||||
results.append(engine.cleanClosedTCPSocket(socketID: uuid(step.socket!)))
|
||||
case "closeSocket":
|
||||
engine.closeSocket(socketID: uuid(step.socket!))
|
||||
results.append(engine.state.socketsByID[uuid(step.socket!)] == nil)
|
||||
@@ -280,6 +304,15 @@ func run(_ scenario: Scenario) -> JSON {
|
||||
"localIp": socket.localIPAddress ?? NSNull(), "localPort": socket.localPort,
|
||||
"remoteIp": socket.remoteIPAddress ?? NSNull(), "remotePort": socket.remotePort.map { $0 as Any } ?? NSNull(),
|
||||
"queued": state.udpReceiveQueuesBySocketID[socket.id]?.count ?? 0,
|
||||
"tcpState": socket.tcpState?.rawValue ?? NSNull(),
|
||||
"parent": socket.parentListenerSocketID?.uuidString ?? NSNull(),
|
||||
"session": state.tcpSessionsByID[socket.id].map { session -> JSON in
|
||||
[
|
||||
"attempts": session.sendAttempts, "next": session.nextSendSequenceNumber,
|
||||
"remote": session.remoteSequenceNumber, "pending": session.lastSentSegment != nil,
|
||||
"assembly": session.receiveAssembly.count, "messages": session.receivedMessages.count,
|
||||
]
|
||||
} ?? NSNull(),
|
||||
]
|
||||
},
|
||||
"firewallDecisions": state.firewallDecisions.map { decision -> JSON in
|
||||
|
||||
Reference in New Issue
Block a user