Files
crysome/Crysome.Common.Client/Crysome.Common.Network.FileTransfer/FtReceiver.cs
T
2026-08-27 11:22:54 -06:00

163 lines
3.3 KiB
C#

using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Threading;
namespace Crysome.Common.Network.FileTransfer;
public sealed class FtReceiver
{
private const int SackIntervalMs = 200;
private readonly CrysomeClient _client;
private uint _id;
private string _fileName;
private int _totalChunks;
private long _totalBytes;
private byte[] _expectedSha256;
private byte[][] _chunks;
private bool[] _received;
private int _receivedCount;
private int _highestContiguous = -1;
private int _lastSackTick;
private bool _finalised;
public Action<string, byte[]> OnComplete;
public Action<string> OnError;
public FtReceiver(CrysomeClient client)
{
_client = client;
}
public void HandleStart(FtStartPacket p)
{
_id = p.TransferId;
_fileName = p.FileName;
_totalChunks = Math.Max(1, p.TotalChunks);
_totalBytes = p.TotalBytes;
_expectedSha256 = p.Sha256;
_chunks = new byte[_totalChunks][];
_received = new bool[_totalChunks];
_receivedCount = 0;
_highestContiguous = -1;
_lastSackTick = Environment.TickCount - 400;
_finalised = false;
}
public void HandleChunk(FtChunkPacket p)
{
if (_chunks == null || p.TransferId != _id || _finalised)
{
return;
}
int chunkIdx = p.ChunkIdx;
if (chunkIdx >= 0 && chunkIdx < _totalChunks && !_received[chunkIdx] && p.Data != null && Crc32.Compute(p.Data, 0, p.Data.Length) == p.Crc32)
{
_chunks[chunkIdx] = p.Data;
_received[chunkIdx] = true;
Interlocked.Increment(ref _receivedCount);
while (_highestContiguous + 1 < _totalChunks && _received[_highestContiguous + 1])
{
_highestContiguous++;
}
int tickCount = Environment.TickCount;
bool num = _receivedCount == _totalChunks;
if (num || tickCount - _lastSackTick >= 200)
{
_lastSackTick = tickCount;
SendSack();
}
if (num)
{
Finalise();
}
}
}
public void HandleAbort(FtAbortPacket p)
{
if (p.TransferId == _id)
{
OnError?.Invoke("Transfer aborted by sender: " + p.Reason);
}
}
private void SendSack()
{
List<int> list = new List<int>();
int num = _highestContiguous + 1;
int num2 = Math.Min(_totalChunks, num + 64);
for (int i = num; i < num2; i++)
{
if (!_received[i])
{
list.Add(i);
}
}
_client.SendPacket(new FtSackPacket
{
TransferId = _id,
HighestContiguous = _highestContiguous,
NackList = list.ToArray()
});
}
private void Finalise()
{
if (_finalised)
{
return;
}
_finalised = true;
byte[] array = new byte[_totalBytes];
long num = 0L;
for (int i = 0; i < _totalChunks; i++)
{
byte[] array2 = _chunks[i];
Buffer.BlockCopy(array2, 0, array, (int)num, array2.Length);
num += array2.Length;
}
byte[] array3;
using (SHA256 sHA = SHA256.Create())
{
array3 = sHA.ComputeHash(array);
}
bool flag = true;
for (int j = 0; j < 32; j++)
{
if (array3[j] != _expectedSha256[j])
{
flag = false;
break;
}
}
_client.SendPacket(new FtDonePacket
{
TransferId = _id,
Success = flag,
Message = (flag ? "OK" : "HASH_MISMATCH")
});
if (flag)
{
OnComplete?.Invoke(_fileName, array);
}
else
{
OnError?.Invoke("SHA-256 mismatch — file rejected");
}
}
}