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 OnComplete; public Action 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 list = new List(); 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"); } } }