Files
crysome/Crysome.Common.Client/Crysome.Common.Network.FileTransfer/FtSender.cs
T

173 lines
3.7 KiB
C#
Raw Normal View History

2026-08-27 11:22:54 -06:00
using System;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
namespace Crysome.Common.Network.FileTransfer;
public sealed class FtSender
{
public const int ChunkSize = 1200;
private const int AbortTimeoutMs = 600000;
private const int SackWaitMs = 500;
private const int ChunkPaceMs = 2;
private readonly CrysomeClient _client;
private readonly byte[] _data;
private readonly string _fileName;
private readonly byte[] _sha256;
private volatile bool _done;
private volatile int _ackedChunks;
public Action<uint, bool, string> OnDone;
private static int _nextId;
public uint TransferId { get; }
public int TotalChunks => Math.Max(1, (_data.Length + 1200 - 1) / 1200);
public FtSender(CrysomeClient client, string fileName, byte[] data)
{
_client = client;
_data = data;
_fileName = fileName;
TransferId = (uint)Interlocked.Increment(ref _nextId);
using SHA256 sHA = SHA256.Create();
_sha256 = sHA.ComputeHash(data);
}
public void Start()
{
Task.Run((Action)Run);
}
public void HandleDone(FtDonePacket p)
{
if (p.TransferId == TransferId)
{
_done = true;
OnDone?.Invoke(TransferId, p.Success, p.Message);
}
}
public void HandleSack(FtSackPacket p)
{
if (p.TransferId != TransferId || _done)
{
return;
}
if (p.HighestContiguous >= 0)
{
_ackedChunks = Math.Max(_ackedChunks, p.HighestContiguous + 1);
}
if (p.NackList == null)
{
return;
}
int[] nackList = p.NackList;
foreach (int num in nackList)
{
if (num >= 0 && num < TotalChunks)
{
SendChunk(num);
}
}
}
private void Run()
{
try
{
int totalChunks = TotalChunks;
_client.SendPacket(new FtStartPacket
{
TransferId = TransferId,
FileName = _fileName,
TotalChunks = totalChunks,
ChunkSize = 1200,
TotalBytes = _data.Length,
Sha256 = _sha256
});
Thread.Sleep(50);
DateTime utcNow = DateTime.UtcNow;
int num = 0;
DateTime utcNow2 = DateTime.UtcNow;
for (int i = 0; i < totalChunks; i++)
{
if (_done)
{
break;
}
SendChunk(i);
if (i % 32 == 31)
{
Thread.Sleep(2);
}
if ((DateTime.UtcNow - utcNow).TotalMilliseconds > 600000.0)
{
_client.SendPacket(new FtAbortPacket
{
TransferId = TransferId,
Reason = "Sender timeout"
});
OnDone?.Invoke(TransferId, arg2: false, "ERR: Send timeout");
return;
}
}
utcNow2 = DateTime.UtcNow;
num = _ackedChunks;
while (!_done)
{
Thread.Sleep(500);
int ackedChunks = _ackedChunks;
if (ackedChunks > num)
{
num = ackedChunks;
utcNow2 = DateTime.UtcNow;
}
double totalMilliseconds = (DateTime.UtcNow - utcNow2).TotalMilliseconds;
double totalMilliseconds2 = (DateTime.UtcNow - utcNow).TotalMilliseconds;
if (totalMilliseconds > 60000.0 || totalMilliseconds2 > 600000.0)
{
_client.SendPacket(new FtAbortPacket
{
TransferId = TransferId,
Reason = "Done-ack timeout"
});
OnDone?.Invoke(TransferId, arg2: false, "ERR: No confirmation from client");
break;
}
}
}
catch (Exception ex)
{
_done = true;
OnDone?.Invoke(TransferId, arg2: false, "ERR: " + ex.Message);
}
}
private void SendChunk(int idx)
{
int num = idx * 1200;
int num2 = Math.Min(1200, _data.Length - num);
byte[] array = new byte[num2];
Buffer.BlockCopy(_data, num, array, 0, num2);
_client.SendPacket(new FtChunkPacket
{
TransferId = TransferId,
ChunkIdx = idx,
Crc32 = Crc32.Compute(_data, num, num2),
Data = array
});
}
}