Files
2026-08-27 11:22:54 -06:00

800 lines
16 KiB
C#

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
namespace Crysome.Common.Network;
public sealed class RudpChannel : IDisposable
{
private sealed class Pending
{
public uint Seq;
public byte[][] Datagrams;
public DateTime SentAt;
public DateTime LastSent;
public int Retries;
}
private sealed class FragBuf
{
public byte[][] Frags;
public int RecvCount;
public DateTime Created = DateTime.UtcNow;
public bool IsComplete
{
get
{
if (Frags != null)
{
return RecvCount >= Frags.Length;
}
return false;
}
}
}
public const int HeaderSize = 19;
private const int WireMtuSafePayload = 1200;
public const byte F_RELIABLE = 1;
public const byte F_UNRELIABLE = 2;
public const byte F_ACK = 4;
public const byte F_SYN = 8;
public const byte F_SYNACK = 16;
public const byte F_FIN = 32;
public const byte F_KEEPALIVE = 64;
private const int InitRto = 500;
private const int MaxRto = 16000;
private const int MaxRetries = 15;
private const int KeepaliveMs = 5000;
private const int TimeoutMs = 30000;
private const int MaxInFlight = 64;
private readonly UdpClient _socket;
private readonly bool _ownsSocket;
private uint _sendSeq;
private uint _recvSeq;
private uint _unrelSendSeq;
private uint _unrelLastSeq;
private readonly object _seqLock = new object();
private readonly ConcurrentDictionary<uint, Pending> _pending = new ConcurrentDictionary<uint, Pending>();
private readonly SortedDictionary<uint, byte[]> _ooo = new SortedDictionary<uint, byte[]>();
private readonly object _oooLock = new object();
private readonly ConcurrentDictionary<uint, FragBuf> _fragBufs = new ConcurrentDictionary<uint, FragBuf>();
private readonly ConcurrentDictionary<uint, FragBuf> _relFragBufs = new ConcurrentDictionary<uint, FragBuf>();
private int _rtoMs = 500;
private bool _rtoDoubledThisCycle;
private DateTime _lastActivity = DateTime.UtcNow;
private CancellationTokenSource _cts;
private bool _disposed;
private readonly SemaphoreSlim _sendWindow = new SemaphoreSlim(64, 64);
private readonly object _statsLock = new object();
private double _srtt = 100.0;
private double _rttvar = 50.0;
public uint SessionId { get; private set; }
public IPEndPoint Remote { get; private set; }
public bool IsAlive
{
get
{
if (!_disposed)
{
return (DateTime.UtcNow - _lastActivity).TotalMilliseconds < 30000.0;
}
return false;
}
}
public double RttMs { get; private set; } = 100.0;
public double LossRate { get; private set; }
public long BytesSent { get; private set; }
public long BytesRecv { get; private set; }
public long PktsSent { get; private set; }
public long PktsLost { get; private set; }
public event Action<byte[]> ReliableReceived;
public event Action<byte, byte[]> UnreliableFrameReceived;
public event Action Disconnected;
public event Action<double> CongestionChanged;
public RudpChannel(uint sessionId, IPEndPoint remote, int localPort = 0)
{
SessionId = sessionId;
Remote = remote;
_socket = new UdpClient(localPort);
_socket.Client.ReceiveBufferSize = 8388608;
_socket.Client.SendBufferSize = 8388608;
_ownsSocket = true;
}
public RudpChannel(uint sessionId, IPEndPoint remote, UdpClient sharedSocket)
{
SessionId = sessionId;
Remote = remote;
_socket = sharedSocket;
_ownsSocket = false;
}
public void Start()
{
_cts = new CancellationTokenSource();
if (_ownsSocket)
{
Task.Run(delegate
{
OwnedReceiveLoop(_cts.Token);
});
}
Task.Run(delegate
{
RetransmitLoop(_cts.Token);
});
Task.Run(delegate
{
KeepaliveLoop(_cts.Token);
});
}
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
try
{
_cts?.Cancel();
}
catch
{
}
if (_ownsSocket)
{
try
{
_socket?.Close();
}
catch
{
}
}
int count = _pending.Count;
_pending.Clear();
for (int i = 0; i < count + 64; i++)
{
try
{
_sendWindow.Release();
}
catch
{
}
}
}
public void SendReliable(byte[] payload)
{
if (_disposed)
{
return;
}
_sendWindow.Wait();
if (_disposed)
{
_sendWindow.Release();
return;
}
int num = Math.Max(1, (payload.Length + 1200 - 1) / 1200);
uint num2;
lock (_seqLock)
{
num2 = ++_sendSeq;
}
byte[][] array = new byte[num][];
for (ushort num3 = 0; num3 < num; num3++)
{
int num4 = num3 * 1200;
int payLen = Math.Min(1200, payload.Length - num4);
array[num3] = BuildDatagram(1, num2, 0u, num3, (ushort)num, 0, payload, num4, payLen);
}
Pending value = new Pending
{
Seq = num2,
Datagrams = array,
SentAt = DateTime.UtcNow,
LastSent = DateTime.UtcNow
};
_pending[num2] = value;
bool flag = num > 4;
byte[][] array2 = array;
foreach (byte[] dg in array2)
{
RawSend(dg);
if (flag)
{
Thread.Sleep(2);
}
}
PktsSent++;
}
public void SendUnreliable(byte packetTypeId, byte[] payload)
{
if (_disposed)
{
return;
}
uint seqNum;
lock (_seqLock)
{
seqNum = ++_unrelSendSeq;
}
int num = Math.Max(1, (payload.Length + 1200 - 1) / 1200);
for (ushort num2 = 0; num2 < num; num2++)
{
int num3 = num2 * 1200;
int payLen = Math.Min(1200, payload.Length - num3);
byte[] dg = BuildDatagram(2, seqNum, 0u, num2, (ushort)num, packetTypeId, payload, num3, payLen);
RawSend(dg);
if (num > 16 && (num2 & 0xF) == 15 && num2 < num - 1)
{
Thread.Sleep(1);
}
}
}
public void SendSyn()
{
byte[] dg = BuildDatagram(8, 0u, 0u, 0, 1, 0, new byte[0], 0, 0);
RawSend(dg);
}
public void SendSynAck()
{
byte[] dg = BuildDatagram(16, SessionId, 0u, 0, 1, 0, new byte[0], 0, 0);
RawSend(dg);
}
public void SendFin()
{
byte[] dg = BuildDatagram(32, 0u, 0u, 0, 1, 0, new byte[0], 0, 0);
try
{
RawSend(dg);
}
catch
{
}
}
public void FeedDatagram(byte[] datagram)
{
ProcessDatagram(datagram);
}
private async void OwnedReceiveLoop(CancellationToken ct)
{
while (!ct.IsCancellationRequested && !_disposed)
{
try
{
ProcessDatagram((await _socket.ReceiveAsync()).Buffer);
}
catch (ObjectDisposedException)
{
break;
}
catch
{
}
}
}
private void ProcessDatagram(byte[] dg)
{
if (dg == null || dg.Length < 19)
{
return;
}
_lastActivity = DateTime.UtcNow;
BytesRecv += dg.Length;
BitConverter.ToUInt32(dg, 0);
byte b = dg[4];
uint seq = BitConverter.ToUInt32(dg, 5);
uint ackSeq = BitConverter.ToUInt32(dg, 9);
ushort fragIdx = BitConverter.ToUInt16(dg, 13);
ushort fragCount = BitConverter.ToUInt16(dg, 15);
byte typeId = dg[17];
int payLen = dg.Length - 19;
if ((b & 4) != 0)
{
HandleAck(ackSeq);
}
else if ((b & 0x40) == 0)
{
if ((b & 0x20) != 0)
{
TriggerDisconnect();
}
else if ((b & 1) != 0)
{
HandleReliableData(seq, fragIdx, fragCount, dg, payLen);
}
else if ((b & 2) != 0)
{
HandleUnreliableData(seq, typeId, fragIdx, fragCount, dg, payLen);
}
}
}
private void HandleAck(uint ackSeq)
{
if (_pending.TryRemove(ackSeq, out var value))
{
double totalMilliseconds = (DateTime.UtcNow - value.SentAt).TotalMilliseconds;
UpdateRtt(totalMilliseconds);
try
{
_sendWindow.Release();
}
catch
{
}
}
}
private void HandleReliableData(uint seq, ushort fragIdx, ushort fragCount, byte[] dg, int payLen)
{
int num = 19;
if (payLen < 0 || num + payLen > dg.Length)
{
return;
}
byte[] array = new byte[payLen];
if (payLen > 0)
{
Buffer.BlockCopy(dg, num, array, 0, payLen);
}
if (fragCount > 1)
{
FragBuf orAdd = _relFragBufs.GetOrAdd(seq, (uint _) => new FragBuf
{
Frags = new byte[fragCount][]
});
if (fragIdx >= orAdd.Frags.Length || orAdd.Frags[fragIdx] != null)
{
return;
}
orAdd.Frags[fragIdx] = array;
Interlocked.Increment(ref orAdd.RecvCount);
if (!orAdd.IsComplete)
{
return;
}
_relFragBufs.TryRemove(seq, out var _);
SendAck(seq);
int num2 = 0;
byte[][] frags = orAdd.Frags;
foreach (byte[] array2 in frags)
{
if (array2 != null)
{
num2 += array2.Length;
}
}
byte[] array3 = new byte[num2];
int num4 = 0;
frags = orAdd.Frags;
foreach (byte[] array4 in frags)
{
if (array4 != null)
{
Buffer.BlockCopy(array4, 0, array3, num4, array4.Length);
num4 += array4.Length;
}
}
array = array3;
}
else
{
SendAck(seq);
}
lock (_oooLock)
{
uint num5 = _recvSeq + 1;
if (seq == num5 || _recvSeq == 0)
{
_recvSeq = seq;
DispatchReliable(array);
while (true)
{
uint num6 = _recvSeq + 1;
if (_ooo.TryGetValue(num6, out var value2))
{
_ooo.Remove(num6);
_recvSeq = num6;
DispatchReliable(value2);
continue;
}
break;
}
}
else if ((int)(seq - num5) > 0 && !_ooo.ContainsKey(seq))
{
_ooo[seq] = array;
}
}
}
private void HandleUnreliableData(uint seq, byte typeId, ushort fragIdx, ushort fragCount, byte[] dg, int payLen)
{
lock (_seqLock)
{
if (_unrelLastSeq != 0 && (int)(seq - _unrelLastSeq) <= 0)
{
return;
}
}
int num = 19;
if (payLen < 0 || num + payLen > dg.Length)
{
return;
}
byte[] array = new byte[payLen];
if (payLen > 0)
{
Buffer.BlockCopy(dg, num, array, 0, payLen);
}
if (fragCount <= 1)
{
lock (_seqLock)
{
_unrelLastSeq = seq;
}
DispatchUnreliable(typeId, array);
return;
}
FragBuf orAdd = _fragBufs.GetOrAdd(seq, (uint _) => new FragBuf
{
Frags = new byte[fragCount][]
});
if (fragIdx >= orAdd.Frags.Length || orAdd.Frags[fragIdx] != null)
{
return;
}
orAdd.Frags[fragIdx] = array;
Interlocked.Increment(ref orAdd.RecvCount);
if (!orAdd.IsComplete)
{
return;
}
_fragBufs.TryRemove(seq, out var _);
lock (_seqLock)
{
if (_unrelLastSeq != 0 && (int)(seq - _unrelLastSeq) <= 0)
{
return;
}
_unrelLastSeq = seq;
}
int num2 = 0;
byte[][] frags = orAdd.Frags;
foreach (byte[] array2 in frags)
{
if (array2 != null)
{
num2 += array2.Length;
}
}
byte[] array3 = new byte[num2];
int num4 = 0;
frags = orAdd.Frags;
foreach (byte[] array4 in frags)
{
if (array4 != null)
{
Buffer.BlockCopy(array4, 0, array3, num4, array4.Length);
num4 += array4.Length;
}
}
DispatchUnreliable(typeId, array3);
}
private void DispatchReliable(byte[] data)
{
Task.Run(delegate
{
try
{
this.ReliableReceived?.Invoke(data);
}
catch
{
}
});
}
private void DispatchUnreliable(byte typeId, byte[] data)
{
Task.Run(delegate
{
try
{
this.UnreliableFrameReceived?.Invoke(typeId, data);
}
catch
{
}
});
}
private async void RetransmitLoop(CancellationToken ct)
{
while (!ct.IsCancellationRequested && !_disposed)
{
await Task.Delay(50, ct).ContinueWith(delegate
{
});
DateTime utcNow = DateTime.UtcNow;
FragBuf value;
foreach (KeyValuePair<uint, FragBuf> fragBuf in _fragBufs)
{
if ((utcNow - fragBuf.Value.Created).TotalSeconds > 10.0)
{
_fragBufs.TryRemove(fragBuf.Key, out value);
}
}
foreach (KeyValuePair<uint, FragBuf> relFragBuf in _relFragBufs)
{
if ((utcNow - relFragBuf.Value.Created).TotalSeconds > 60.0)
{
_relFragBufs.TryRemove(relFragBuf.Key, out value);
}
}
_rtoDoubledThisCycle = false;
foreach (KeyValuePair<uint, Pending> item in _pending)
{
Pending value2 = item.Value;
if ((utcNow - value2.LastSent).TotalMilliseconds < (double)_rtoMs)
{
continue;
}
if (value2.Retries >= 15)
{
if (_pending.TryRemove(value2.Seq, out var _))
{
PktsLost++;
UpdateLossRate();
try
{
_sendWindow.Release();
}
catch
{
}
}
continue;
}
value2.Retries++;
value2.LastSent = utcNow;
if (!_rtoDoubledThisCycle)
{
_rtoMs = Math.Min(_rtoMs * 2, 16000);
_rtoDoubledThisCycle = true;
}
bool flag = value2.Datagrams.Length > 4;
byte[][] datagrams = value2.Datagrams;
foreach (byte[] dg in datagrams)
{
RawSend(dg);
if (flag)
{
Thread.Sleep(1);
}
}
}
UpdateCongestionMetric();
if ((utcNow - _lastActivity).TotalMilliseconds > 30000.0)
{
TriggerDisconnect();
}
}
}
private async void KeepaliveLoop(CancellationToken ct)
{
while (!ct.IsCancellationRequested && !_disposed)
{
await Task.Delay(5000, ct).ContinueWith(delegate
{
});
if (!_disposed)
{
try
{
byte[] dg = BuildDatagram(64, 0u, 0u, 0, 1, 0, new byte[0], 0, 0);
RawSend(dg);
}
catch
{
}
continue;
}
break;
}
}
private void SendAck(uint seq)
{
byte[] dg = BuildDatagram(4, 0u, seq, 0, 1, 0, new byte[0], 0, 0);
try
{
RawSend(dg);
}
catch
{
}
}
private void UpdateRtt(double sample)
{
lock (_statsLock)
{
_rttvar = 0.75 * _rttvar + 0.25 * Math.Abs(_srtt - sample);
_srtt = 0.875 * _srtt + 0.125 * sample;
RttMs = _srtt;
_rtoMs = Math.Max(200, Math.Min(16000, (int)(_srtt + 4.0 * _rttvar)));
}
}
private void UpdateLossRate()
{
double val = ((PktsSent > 0) ? ((double)PktsLost / (double)PktsSent) : 0.0);
LossRate = Math.Min(1.0, val);
}
private void UpdateCongestionMetric()
{
UpdateLossRate();
double num = Math.Min(1.0, RttMs / 2000.0);
double lossRate = LossRate;
double obj = Math.Min(1.0, num * 0.5 + lossRate * 0.5);
try
{
this.CongestionChanged?.Invoke(obj);
}
catch
{
}
}
private void TriggerDisconnect()
{
if (_disposed)
{
return;
}
Dispose();
try
{
this.Disconnected?.Invoke();
}
catch
{
}
}
private byte[] BuildDatagram(byte flags, uint seqNum, uint ackNum, ushort fragIdx, ushort fragCount, byte packetTypeId, byte[] payload, int payOffset, int payLen)
{
byte[] array = new byte[19 + payLen];
Buffer.BlockCopy(BitConverter.GetBytes(SessionId), 0, array, 0, 4);
array[4] = flags;
Buffer.BlockCopy(BitConverter.GetBytes(seqNum), 0, array, 5, 4);
Buffer.BlockCopy(BitConverter.GetBytes(ackNum), 0, array, 9, 4);
Buffer.BlockCopy(BitConverter.GetBytes(fragIdx), 0, array, 13, 2);
Buffer.BlockCopy(BitConverter.GetBytes(fragCount), 0, array, 15, 2);
array[17] = packetTypeId;
array[18] = 0;
if (payLen > 0)
{
Buffer.BlockCopy(payload, payOffset, array, 19, payLen);
}
return array;
}
private void RawSend(byte[] dg)
{
if (_disposed || dg == null)
{
return;
}
try
{
_socket.Send(dg, dg.Length, Remote);
BytesSent += dg.Length;
}
catch
{
}
}
public static bool ParseHeader(byte[] dg, int len, out uint sessionId, out byte flags, out uint seqNum, out uint ackNum, out ushort fragIdx, out ushort fragCount, out byte packetTypeId)
{
sessionId = 0u;
flags = 0;
seqNum = 0u;
ackNum = 0u;
fragIdx = 0;
fragCount = 0;
packetTypeId = 0;
if (dg == null || len < 19)
{
return false;
}
sessionId = BitConverter.ToUInt32(dg, 0);
flags = dg[4];
seqNum = BitConverter.ToUInt32(dg, 5);
ackNum = BitConverter.ToUInt32(dg, 9);
fragIdx = BitConverter.ToUInt16(dg, 13);
fragCount = BitConverter.ToUInt16(dg, 15);
packetTypeId = dg[17];
return true;
}
}