initial commit
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
namespace Crysome.Common.Network.FileTransfer;
|
||||
|
||||
internal static class Crc32
|
||||
{
|
||||
private static readonly uint[] _table = Build();
|
||||
|
||||
private static uint[] Build()
|
||||
{
|
||||
uint[] array = new uint[256];
|
||||
for (uint num = 0u; num < 256; num++)
|
||||
{
|
||||
uint num2 = num;
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
num2 = (((num2 & 1) != 0) ? (0xEDB88320u ^ (num2 >> 1)) : (num2 >> 1));
|
||||
}
|
||||
array[num] = num2;
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
internal static uint Compute(byte[] data, int offset, int len)
|
||||
{
|
||||
uint num = uint.MaxValue;
|
||||
int num2 = offset + len;
|
||||
for (int i = offset; i < num2; i++)
|
||||
{
|
||||
num = (num >> 8) ^ _table[(num ^ data[i]) & 0xFF];
|
||||
}
|
||||
return num ^ 0xFFFFFFFFu;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.IO;
|
||||
using Crysome.Common.Network.Packets;
|
||||
|
||||
namespace Crysome.Common.Network.FileTransfer;
|
||||
|
||||
public sealed class FtAbortPacket : IPacket
|
||||
{
|
||||
public uint TransferId;
|
||||
|
||||
public string Reason;
|
||||
|
||||
public void Write(BinaryWriter w)
|
||||
{
|
||||
w.Write(TransferId);
|
||||
w.Write(Reason ?? "");
|
||||
}
|
||||
|
||||
public void Read(BinaryReader r)
|
||||
{
|
||||
TransferId = r.ReadUInt32();
|
||||
Reason = r.ReadString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Crysome.Common.Network.Packets;
|
||||
|
||||
namespace Crysome.Common.Network.FileTransfer;
|
||||
|
||||
public sealed class FtChunkPacket : IPacket
|
||||
{
|
||||
public uint TransferId;
|
||||
|
||||
public int ChunkIdx;
|
||||
|
||||
public uint Crc32;
|
||||
|
||||
public byte[] Data;
|
||||
|
||||
public void Write(BinaryWriter w)
|
||||
{
|
||||
w.Write(TransferId);
|
||||
w.Write(ChunkIdx);
|
||||
w.Write(Crc32);
|
||||
int num = ((Data != null) ? Data.Length : 0);
|
||||
w.Write(num);
|
||||
if (num > 0)
|
||||
{
|
||||
w.Write(Data);
|
||||
}
|
||||
}
|
||||
|
||||
public void Read(BinaryReader r)
|
||||
{
|
||||
TransferId = r.ReadUInt32();
|
||||
ChunkIdx = r.ReadInt32();
|
||||
Crc32 = r.ReadUInt32();
|
||||
int num = r.ReadInt32();
|
||||
Data = ((num > 0) ? r.ReadBytes(num) : Array.Empty<byte>());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System.IO;
|
||||
using Crysome.Common.Network.Packets;
|
||||
|
||||
namespace Crysome.Common.Network.FileTransfer;
|
||||
|
||||
public sealed class FtDonePacket : IPacket
|
||||
{
|
||||
public uint TransferId;
|
||||
|
||||
public bool Success;
|
||||
|
||||
public string Message;
|
||||
|
||||
public void Write(BinaryWriter w)
|
||||
{
|
||||
w.Write(TransferId);
|
||||
w.Write(Success);
|
||||
w.Write(Message ?? "");
|
||||
}
|
||||
|
||||
public void Read(BinaryReader r)
|
||||
{
|
||||
TransferId = r.ReadUInt32();
|
||||
Success = r.ReadBoolean();
|
||||
Message = r.ReadString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Crysome.Common.Network.Packets;
|
||||
|
||||
namespace Crysome.Common.Network.FileTransfer;
|
||||
|
||||
public sealed class FtSackPacket : IPacket
|
||||
{
|
||||
public uint TransferId;
|
||||
|
||||
public int HighestContiguous;
|
||||
|
||||
public int[] NackList;
|
||||
|
||||
public void Write(BinaryWriter w)
|
||||
{
|
||||
w.Write(TransferId);
|
||||
w.Write(HighestContiguous);
|
||||
short num = (short)((NackList != null) ? Math.Min(NackList.Length, 128) : 0);
|
||||
w.Write(num);
|
||||
for (int i = 0; i < num; i++)
|
||||
{
|
||||
w.Write(NackList[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public void Read(BinaryReader r)
|
||||
{
|
||||
TransferId = r.ReadUInt32();
|
||||
HighestContiguous = r.ReadInt32();
|
||||
int num = r.ReadInt16();
|
||||
NackList = new int[num];
|
||||
for (int i = 0; i < num; i++)
|
||||
{
|
||||
NackList[i] = r.ReadInt32();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.IO;
|
||||
using Crysome.Common.Network.Packets;
|
||||
|
||||
namespace Crysome.Common.Network.FileTransfer;
|
||||
|
||||
public sealed class FtStartPacket : IPacket
|
||||
{
|
||||
public uint TransferId;
|
||||
|
||||
public string FileName;
|
||||
|
||||
public int TotalChunks;
|
||||
|
||||
public int ChunkSize;
|
||||
|
||||
public long TotalBytes;
|
||||
|
||||
public byte[] Sha256;
|
||||
|
||||
public void Write(BinaryWriter w)
|
||||
{
|
||||
w.Write(TransferId);
|
||||
w.Write(FileName ?? "");
|
||||
w.Write(TotalChunks);
|
||||
w.Write(ChunkSize);
|
||||
w.Write(TotalBytes);
|
||||
w.Write(Sha256 ?? new byte[32]);
|
||||
}
|
||||
|
||||
public void Read(BinaryReader r)
|
||||
{
|
||||
TransferId = r.ReadUInt32();
|
||||
FileName = r.ReadString();
|
||||
TotalChunks = r.ReadInt32();
|
||||
ChunkSize = r.ReadInt32();
|
||||
TotalBytes = r.ReadInt64();
|
||||
Sha256 = r.ReadBytes(32);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user