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 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 }); } }