173 lines
3.7 KiB
C#
173 lines
3.7 KiB
C#
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
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|