namespace XFESystem.Network;
/// <summary>
/// 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.
/// </summary>
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<byte> 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<byte> 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<byte> 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<byte> 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();
}
namespace XFESystem.Network;
/// <summary>
/// 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.
/// </summary>
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<byte> 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<byte> 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<byte> 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<byte> 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();
}