namespace XFESystem.Network; /// /// Bounded active-open TCP byte stream. One outstanding outgoing segment, /// immediate cumulative ACKs, 64 KiB receive window, no SACK/window scaling. /// Out-of-order data is ACKed and retransmitted by the peer, never fabricated. /// internal sealed class TcpConnection : Stream { readonly Ipv4Network network; readonly uint destination; readonly ushort sourcePort, destinationPort; readonly byte[] received = new byte[65536], pending = new byte[1460]; uint sendUnacknowledged, sendNext, receiveNext; uint sendWindowSequence, sendWindowAcknowledgement; int receiveHead, receiveCount, peerMss = 536; ushort peerWindow = 65535; bool established, remoteFin, disposed; string? failure; int readTimeout, writeTimeout; internal TcpConnection(Ipv4Network network, uint destination, ushort sourcePort, ushort destinationPort, uint initialSequence, int timeout) { this.network = network; this.destination = destination; this.sourcePort = sourcePort; this.destinationPort = destinationPort; sendUnacknowledged = initialSequence; sendNext = unchecked(initialSequence + 1); readTimeout = writeTimeout = timeout; } static bool Before(uint first, uint second) => unchecked((int)(first - second)) < 0; ushort Window => (ushort)Math.Min(65535, received.Length - receiveCount); void CheckOpen() { ObjectDisposedException.ThrowIf(disposed, this); if (failure != null) throw new IOException(failure); } internal void Connect(long deadline) { long nextSend = 0; int attempts = 0; while (!established) { CheckOpen(); Ipv4Network.CheckTimeout(network.Now, deadline, "TCP connect"); if (network.Now >= nextSend) { if (attempts++ >= 7) throw new IOException("TCP connect exceeded retransmission limit."); network.SendTcp(destination, sourcePort, destinationPort, sendUnacknowledged, 0, 2, Window, default, deadline, syn: true); nextSend = network.Now + Math.Min(2000, 300 << Math.Min(attempts - 1, 3)); } network.Poll(deadline); if (!established) network.Wait(); } network.Trace("XFE_NETWORK_TCP|Address|" + NetworkPackets.Format(destination) + "|Port|" + destinationPort + "|Established|1\n"); } void Ack(long deadline) => network.SendTcp(destination, sourcePort, destinationPort, sendNext, receiveNext, 0x10, Window, default, deadline); internal void Receive(uint source, ReadOnlySpan segment, long deadline) { if (disposed || source != destination || NetworkPackets.Read16(segment, 0) != destinationPort || NetworkPackets.Read16(segment, 2) != sourcePort) return; int header = (segment[12] >> 4) * 4; if (header < 20 || header > segment.Length || (segment[12] & 15) != 0) return; byte flags = segment[13]; uint sequence = NetworkPackets.Read32(segment, 4), acknowledgement = NetworkPackets.Read32(segment, 8); if (!established) { // A refusal must acknowledge the actual SYN, not a guessed tuple. if ((flags & 0x14) == 0x14 && acknowledgement == sendNext) { failure = "TCP connection refused."; return; } if ((flags & 0x16) != 0x12 || acknowledgement != sendNext) return; for (int at = 20; at < header;) { int kind = segment[at]; if (kind == 0) break; if (kind == 1) { at++; continue; } if (at + 2 > header || segment[at + 1] < 2 || at + segment[at + 1] > header) return; if (kind == 2 && segment[at + 1] == 4) { int mss = NetworkPackets.Read16(segment, at + 2); if (mss < 64) return; peerMss = Math.Min(1460, mss); } at += segment[at + 1]; } receiveNext = unchecked(sequence + 1); sendUnacknowledged = acknowledgement; peerWindow = NetworkPackets.Read16(segment, 14); sendWindowSequence = sequence; sendWindowAcknowledgement = acknowledgement; established = true; Ack(deadline); return; } ReadOnlySpan data = segment[header..]; // RFC 5961 exact-sequence RST acceptance; other RSTs only elicit ACK. if ((flags & 4) != 0) { if (sequence == receiveNext) failure = "TCP peer reset the connection."; else Ack(deadline); return; } if ((flags & 0x12) != 0x10) { Ack(deadline); return; } // RFC 9293 receive acceptability precedes all send-side state changes. // A same-tuple ACK with a sequence outside our actual receive window // cannot acknowledge bytes or replace the peer's advertised window. uint segmentLength = (uint)data.Length + ((flags & 1) != 0 ? 1u : 0u); uint window = Window; bool firstInWindow = unchecked(sequence - receiveNext) < window; bool lastInWindow = segmentLength != 0 && unchecked(sequence + segmentLength - 1 - receiveNext) < window; bool acceptable = segmentLength == 0 ? (window == 0 ? sequence == receiveNext : firstInWindow) : window != 0 && (firstInWindow || lastInWindow); if (!acceptable) { Ack(deadline); return; } if (Before(sendNext, acknowledgement)) { Ack(deadline); return; } if (!Before(acknowledgement, sendUnacknowledged)) { if (Before(sendUnacknowledged, acknowledgement)) sendUnacknowledged = acknowledgement; // SND.WL1/SND.WL2 prevent an older acceptable packet from undoing a // later window update. Stale ACKs do not update the window either. if (Before(sendWindowSequence, sequence) || sendWindowSequence == sequence && !Before(acknowledgement, sendWindowAcknowledgement)) { peerWindow = NetworkPackets.Read16(segment, 14); sendWindowSequence = sequence; sendWindowAcknowledgement = acknowledgement; } } if (remoteFin) { if (data.Length != 0 || (flags & 1) != 0) Ack(deadline); return; } if (Before(receiveNext, sequence)) { Ack(deadline); return; } uint skipped = unchecked(receiveNext - sequence); if (skipped > (uint)data.Length) { if (data.Length != 0 || (flags & 1) != 0) Ack(deadline); return; } data = data[(int)skipped..]; if (data.Length > received.Length - receiveCount) { Ack(deadline); return; } for (int i = 0; i < data.Length; i++) received[(receiveHead + receiveCount + i) & 65535] = data[i]; receiveCount += data.Length; receiveNext = unchecked(receiveNext + (uint)data.Length); bool fin = (flags & 1) != 0; if (fin) { receiveNext = unchecked(receiveNext + 1); remoteFin = true; } if (data.Length != 0 || fin) Ack(deadline); } public override int Read(byte[] buffer, int offset, int count) { ArgumentNullException.ThrowIfNull(buffer); return Read(buffer.AsSpan(offset, count)); } public override int Read(Span buffer) { CheckOpen(); if (buffer.Length == 0) return 0; long deadline = network.Deadline(readTimeout); while (receiveCount == 0) { CheckOpen(); if (remoteFin) return 0; Ipv4Network.CheckTimeout(network.Now, deadline, "TCP read"); network.Poll(deadline); if (receiveCount == 0 && !remoteFin) network.Wait(); } int count = Math.Min(buffer.Length, receiveCount); bool reopenWindow = receiveCount > 32768; for (int i = 0; i < count; i++) buffer[i] = received[(receiveHead + i) & 65535]; receiveHead = (receiveHead + count) & 65535; receiveCount -= count; if (reopenWindow) Ack(deadline); return count; } public override void Write(byte[] buffer, int offset, int count) { ArgumentNullException.ThrowIfNull(buffer); Write(buffer.AsSpan(offset, count)); } public override void Write(ReadOnlySpan buffer) { CheckOpen(); long deadline = network.Deadline(writeTimeout); while (buffer.Length != 0) { if (remoteFin) throw new IOException("TCP peer has closed the receive stream."); while (peerWindow == 0) { CheckOpen(); Ipv4Network.CheckTimeout(network.Now, deadline, "TCP window"); network.Poll(deadline); network.Wait(); } int count = Math.Min(buffer.Length, Math.Min(peerMss, peerWindow)); buffer[..count].CopyTo(pending); uint sequence = sendNext; sendNext = unchecked(sendNext + (uint)count); long nextSend = 0; int attempts = 0; while (Before(sendUnacknowledged, sendNext)) { CheckOpen(); Ipv4Network.CheckTimeout(network.Now, deadline, "TCP write"); if (network.Now >= nextSend) { if (attempts++ >= 7) throw new IOException("TCP data exceeded retransmission limit."); network.SendTcp(destination, sourcePort, destinationPort, sequence, receiveNext, 0x18, Window, pending.AsSpan(0, count), deadline); nextSend = network.Now + Math.Min(2000, 300 << Math.Min(attempts - 1, 3)); } network.Poll(deadline); if (Before(sendUnacknowledged, sendNext)) network.Wait(); } buffer = buffer[count..]; } } internal void Abort(long deadline) { if (disposed) return; try { if (established && network.Now < deadline) network.SendTcp(destination, sourcePort, destinationPort, sendNext, receiveNext, 0x14, 0, default, deadline); } catch (IOException) { } finally { disposed = true; network.Released(this); } } protected override void Dispose(bool disposing) { if (!disposed) { try { if (established && failure == null) { // Close owns one budget, including TX completion and ACK // polling. It never adds a second hidden 500 ms TX timeout. long deadline = network.Now + Math.Min(250, Math.Min(readTimeout, writeTimeout)); uint finSequence = sendNext; sendNext = unchecked(sendNext + 1); network.SendTcp(destination, sourcePort, destinationPort, finSequence, receiveNext, 0x11, Window, default, deadline); while (Before(sendUnacknowledged, sendNext) && failure == null && network.Now < deadline) { network.Poll(deadline); network.Wait(); } } } catch (IOException) { } finally { disposed = true; network.Released(this); } } base.Dispose(disposing); } public override bool CanRead => !disposed; public override bool CanWrite => !disposed; public override bool CanSeek => false; public override bool CanTimeout => true; public override int ReadTimeout { get => readTimeout; set { if (value is < 1 or > 120000) throw new ArgumentOutOfRangeException(nameof(value)); readTimeout = value; } } public override int WriteTimeout { get => writeTimeout; set { if (value is < 1 or > 120000) throw new ArgumentOutOfRangeException(nameof(value)); writeTimeout = value; } } public override long Length => throw new NotSupportedException(); public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } public override void Flush() => CheckOpen(); public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); public override void SetLength(long value) => throw new NotSupportedException(); }