stream ciphers do not work with udp FOOL

goon
This commit is contained in:
Malloc of Kuzkycyziklistan 2017-09-07 16:00:03 -05:00
parent ccc7ea5b25
commit b49acd08d4
17 changed files with 295 additions and 113 deletions

View file

@ -322,13 +322,32 @@ Communication between the master server and clients will be done over a WebSocke
</thead> </thead>
<tr> <tr>
<td class="center">1</td> <td class="center">1</td>
<td>Succeeded</td> <td>Server Count (<i>n</i>)</td>
<td>Boolean</td> <td>Packed Unsigned Short</td>
</tr> </tr>
<tr> <tr>
<td class="center">2</td> <td><i>r</i> > 1</td>
<td>Message</td> <td colspan="2">Iterated over <i>n</i> (0 &leq; <i>i</i> &leq; <i>n - 1</i>)</td>
<td>String</td> </tr>
<tr>
<td class="center">2 + 4<i>i</i></td>
<td>Server Id</td>
<td>Packed Unsigned Short</td>
</tr>
<tr>
<td class="center">3 + 4<i>i</i></td>
<td>User Count</td>
<td>Packed Unsigned Short</td>
</tr>
<tr>
<td class="center">4 + 4<i>i</i></td>
<td>IP Address</td>
<td>IPv4 String</td>
</tr>
<tr>
<td class="center">5 + 4<i>i</i></td>
<td>Port</td>
<td>Packed Unsigned Short</td>
</tr> </tr>
</table> </table>

View file

@ -21,6 +21,7 @@ namespace SockScape {
"Master Port", "Master Port",
"Master Addr", "Master Addr",
"Master Secret", "Master Secret",
"Master IV",
"Max Users" "Max Users"
} }
}, },

View file

@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Glove;
namespace SockScape.Encryption {
class BlockCipher {
public const int KeySize = 192;
public const int KeySizeBytes = KeySize / 8;
private readonly byte[] Key = new byte[KeySizeBytes];
private readonly byte[] IV;
public BlockCipher(BigInteger key) {
Key = Key.Select(x => (byte)0).ToArray();
Array.Copy(key.ToByteArray(), Key, Key.Length);
IV = Configuration.General["Master IV"].Str.HexStringToBytes()
?? new byte[] { 0x0b, 0xfc, 0xd7, 0x2d, 0x23, 0xb7, 0x83, 0xb2 };
}
public byte[] Encrypt(byte[] data) {
try {
var ms = new MemoryStream();
var cs = new CryptoStream(ms,
new TripleDESCryptoServiceProvider().CreateEncryptor(Key, IV),
CryptoStreamMode.Write);
cs.Write(data, 0, data.Length);
cs.FlushFinalBlock();
byte[] ret = ms.ToArray();
cs.Close();
ms.Close();
return ret;
} catch(Exception e) {
Console.WriteLine($"TDES ENCRYPT ERROR: {e.Message}");
return null;
}
}
public byte[] Decrypt(byte[] data) {
try {
var ms = new MemoryStream(data);
var cs = new CryptoStream(ms,
new TripleDESCryptoServiceProvider().CreateEncryptor(Key, IV),
CryptoStreamMode.Read);
byte[] ret = new byte[data.Length];
cs.Read(ret, 0, data.Length);
cs.Close();
ms.Close();
return ret;
} catch(Exception e) {
Console.WriteLine($"TDES DECRYPT ERROR: {e.Message}");
return null;
}
}
}
}

View file

@ -6,10 +6,14 @@ using System.Threading.Tasks;
using System.Numerics; using System.Numerics;
using Glove; using Glove;
using System.Globalization; using System.Globalization;
using System.Security.Cryptography;
namespace SockScape.Encryption { namespace SockScape.Encryption {
class Key { class Key {
private static readonly BigInteger Secret = RNG.NextPrime(512 / 8); public const int KeySize = 512;
public const int KeySizeBytes = KeySize / 8;
private static readonly BigInteger Secret = RNG.NextPrime(KeySizeBytes);
public BigInteger Generator { get; } = 2; public BigInteger Generator { get; } = 2;
public BigInteger Modulus { get; } public BigInteger Modulus { get; }
public BigInteger PrivateKey { get; private set; } = BigInteger.Zero; public BigInteger PrivateKey { get; private set; } = BigInteger.Zero;
@ -17,7 +21,7 @@ namespace SockScape.Encryption {
=> !PrivateKey.IsZero; => !PrivateKey.IsZero;
public Key() { public Key() {
Modulus = RNG.NextPrime(512 / 8); Modulus = RNG.NextPrime(KeySizeBytes);
} }
public Packet GenerateRequestPacket() { public Packet GenerateRequestPacket() {

View file

@ -7,11 +7,11 @@ using System.Numerics;
using Glove; using Glove;
namespace SockScape.Encryption { namespace SockScape.Encryption {
class Cipher { class StreamCipher {
private readonly byte[] Key = new byte[512 / 8]; private readonly byte[] Key = new byte[Encryption.Key.KeySizeBytes];
private readonly byte[] State = new byte[256]; private readonly byte[] State = new byte[256];
public Cipher(BigInteger key) { public StreamCipher(BigInteger key) {
int i = 0, j = 0; int i = 0, j = 0;
State = State.Select(x => (byte)i++).ToArray(); State = State.Select(x => (byte)i++).ToArray();
Key = Key.Select(x => (byte)0).ToArray(); Key = Key.Select(x => (byte)0).ToArray();

View file

@ -14,10 +14,20 @@ using MySql.Data.Entity;
namespace SockScape { namespace SockScape {
static class ServerContext { static class ServerContext {
public static Dictionary<int, Server<PlayerConnection>> Servers { get; } public static Dictionary<ushort, Server<PlayerConnection>> Servers { get; }
= new Dictionary<int, Server<PlayerConnection>>(); = new Dictionary<ushort, Server<PlayerConnection>>();
public static Dictionary<int, Pool<PlayerConnection>> Pools { get; } public static Dictionary<ushort, Pool<PlayerConnection>> Pools { get; }
= new Dictionary<int, Pool<PlayerConnection>>(); = new Dictionary<ushort, Pool<PlayerConnection>>();
public static Packet StatusUpdatePacket {
get {
var packet = new Packet(kIntraSlaveId.StatusUpdate, (byte)Servers.Count);
foreach(var pool in Pools)
packet.AddRegions(pool.Key.Pack(), ((ushort)pool.Value.ConnectionCount).Pack(), Servers[pool.Key].Port.Pack());
return packet;
}
}
} }
class Entrypoint { class Entrypoint {
@ -33,8 +43,8 @@ namespace SockScape {
var serverHandle = new Server<PlayerConnection>((ushort)server["Port"], pool, server); var serverHandle = new Server<PlayerConnection>((ushort)server["Port"], pool, server);
ServerContext.Pools.Add(server["Id"], pool); ServerContext.Pools.Add((ushort)server["Id"], pool);
ServerContext.Servers.Add(server["Id"], serverHandle); ServerContext.Servers.Add((ushort)server["Id"], serverHandle);
serverHandle.Start(); serverHandle.Start();
} }

View file

@ -12,24 +12,29 @@ namespace Glove.INI {
Raw = raw; Raw = raw;
} }
public static implicit operator string(Value value) { public string Str
return value.Raw; => this;
}
public static implicit operator bool(Value value) { public Int32 Int
return Boolean.TryParse(value.Raw, out bool retval) && retval; => this;
}
public static implicit operator Int32(Value value) { public double Dbl
return Int32.TryParse(value.Raw, out Int32 retval) => this;
public static implicit operator string(Value value)
=> value.Raw;
public static implicit operator bool(Value value)
=> Boolean.TryParse(value.Raw, out bool retval) && retval;
public static implicit operator Int32(Value value)
=> Int32.TryParse(value.Raw, out Int32 retval)
? retval ? retval
: 0; : 0;
}
public static implicit operator double(Value value) { public static implicit operator double(Value value)
return Double.TryParse(value.Raw, out double retval) => Double.TryParse(value.Raw, out double retval)
? retval ? retval
: 0; : 0;
} }
}
} }

View file

@ -60,5 +60,23 @@ namespace Glove {
public static BigInteger HexStringToBigInt(this string value) public static BigInteger HexStringToBigInt(this string value)
=> BigInteger.Parse(value, NumberStyles.HexNumber); => BigInteger.Parse(value, NumberStyles.HexNumber);
public static byte[] HexStringToBytes(this string value) {
if(value.Length % 2 == 1)
return null;
var bytes = new byte[value.Length / 2];
for(var i = 0; i < bytes.Length; ++i) {
char upperNibble = value[2 * i],
lowerNibble = value[2 * i + 1];
if(!upperNibble.IsHex() || !lowerNibble.IsHex())
return null;
bytes[i] = (byte)((upperNibble.HexValue() << 4) | lowerNibble.HexValue());
}
return bytes;
}
} }
} }

View file

@ -5,6 +5,18 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Glove { namespace Glove {
public static class CharExtensions {
public static bool IsHex(this char c) {
return (c >= '0' && c <= '9')
|| (c >= 'A' && c <= 'F')
|| (c >= 'a' && c <= 'f');
}
public static byte HexValue(this char c) {
return (byte)(!c.IsHex() ? 0 : c - (c < 0x3A ? 0x30 : (c < 0x61 ? 0x37 : 0x57)));
}
}
public static class StringExtensions { public static class StringExtensions {
public static byte[] GetBytes(this string str, bool isUtf8 = true) public static byte[] GetBytes(this string str, bool isUtf8 = true)
=> isUtf8 ? Encoding.UTF8.GetBytes(str) => isUtf8 ? Encoding.UTF8.GetBytes(str)

View file

@ -29,7 +29,7 @@ namespace Kneesocks {
Service_Unavailable = 503, Service_Unavailable = 503,
Gateway_Timeout = 504 Gateway_Timeout = 504
} }
public kStatusCode? StatusCode { get; private set; } = null; public kStatusCode? StatusCode { get; } = null;
protected string StatusCodeText protected string StatusCodeText
=> Enum.GetName(typeof(kStatusCode), StatusCode).Replace('_', ' '); => Enum.GetName(typeof(kStatusCode), StatusCode).Replace('_', ' ');

View file

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Glove;
namespace SockScape {
static class MasterServerList {
public static Dictionary<UInt16, Server> Servers { get; }
= new Dictionary<UInt16, Server>();
public static void Write(Server server) {
lock(Servers) {
if(HasId(server.Id) && !Servers[server.Id].Address.Equals(server.Address))
Console.WriteLine($"{DateTime.Now.ToShortTimeString()} - Server {server.Id} has changed IP addresses.");
Servers[server.Id] = server;
}
}
public static Packet ReportPacket {
get {
lock(Servers) {
var packet = new Packet(kInterMasterId.ServerListing, ((ushort)Servers.Count).Pack());
foreach(var server in Servers)
// TODO change this to support IPv6
packet.AddRegions(server.Key.Pack(), server.Value.UserCount.Pack(),
server.Value.Address.MapToIPv4().ToString(), server.Value.Port.Pack());
return packet;
}
}
}
public static bool HasId(UInt16 id)
=> Servers.ContainsKey(id);
public static void Clear()
=> Servers.Clear();
}
class Server {
public ushort Id { get; set; }
public ushort UserCount { get; set; }
public IPAddress Address { get; set; }
public ushort Port { get; set; }
}
}

View file

@ -1,56 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace SockScape {
static class ServerList {
private static ServerDictionary Servers { get; }
= new ServerDictionary();
private static Dictionary<UInt16, Server> List
=> Servers.List;
public static void Write(Server server) {
lock(Servers) {
if(HasId(server.Id) && !List[server.Id].Address.Equals(server.Address))
Console.WriteLine($"{DateTime.Now.ToShortTimeString()} - Server {server.Id} has changed IP addresses.");
List[server.Id] = server;
}
}
public static Packet ReportPacket {
get {
var packet = new Packet(kInterMasterId.);
return packet;
}
}
public static bool HasId(UInt16 id)
=> List.ContainsKey(id);
public static void Clear()
=> List.Clear();
public class ServerDictionary {
public Dictionary<UInt16, Server> List { get; }
= new Dictionary<UInt16, Server>();
public Server this[UInt16 i] {
get => List.ContainsKey(i) ? List[i] : null;
set => List[i] = value;
}
}
}
class Server {
public ushort Id { get; set; }
public ushort UserCount { get; set; }
public IPAddress Address { get; set; }
public ushort Port { get; set; }
}
}

View file

@ -94,17 +94,18 @@
<Compile Include="DAL\ScapeDb.cs" /> <Compile Include="DAL\ScapeDb.cs" />
<Compile Include="DAL\Session.cs" /> <Compile Include="DAL\Session.cs" />
<Compile Include="DAL\User.cs" /> <Compile Include="DAL\User.cs" />
<Compile Include="Encryption\BlockCipher.cs" />
<Compile Include="Encryption\KeyExchange.cs" /> <Compile Include="Encryption\KeyExchange.cs" />
<Compile Include="Encryption\Cipher.cs" /> <Compile Include="Encryption\StreamCipher.cs" />
<Compile Include="Migrations\201708251600325_Initial.cs" /> <Compile Include="Migrations\201708251600325_Initial.cs" />
<Compile Include="Migrations\201708251600325_Initial.Designer.cs"> <Compile Include="Migrations\201708251600325_Initial.Designer.cs">
<DependentUpon>201708251600325_Initial.cs</DependentUpon> <DependentUpon>201708251600325_Initial.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Migrations\Configuration.cs" /> <Compile Include="Migrations\Configuration.cs" />
<Compile Include="ServerList.cs" /> <Compile Include="MasterServerList.cs" />
<Compile Include="Socks\MasterConnection.cs" /> <Compile Include="Socks\MasterConnection.cs" />
<Compile Include="Socks\MasterUdpClient.cs" /> <Compile Include="Socks\MasterIntraClient.cs" />
<Compile Include="Socks\MasterUdpServer.cs" /> <Compile Include="Socks\MasterIntraServer.cs" />
<Compile Include="Socks\Protocols\ClientServerIds.cs" /> <Compile Include="Socks\Protocols\ClientServerIds.cs" />
<Compile Include="Socks\Protocols\InterMasterIds.cs" /> <Compile Include="Socks\Protocols\InterMasterIds.cs" />
<Compile Include="Socks\Protocols\IntraMasterIds.cs" /> <Compile Include="Socks\Protocols\IntraMasterIds.cs" />

View file

@ -10,7 +10,7 @@ using SockScape.Encryption;
namespace SockScape { namespace SockScape {
class MasterConnection : Connection { class MasterConnection : Connection {
private Key Key; private Key Key;
public Cipher Encryptor { get; private set; } public StreamCipher Encryptor { get; private set; }
protected override void OnOpen() { protected override void OnOpen() {
Key = new Key(); Key = new Key();
@ -39,7 +39,7 @@ namespace SockScape {
return; return;
} }
Encryptor = new Cipher(Key.PrivateKey); Encryptor = new StreamCipher(Key.PrivateKey);
break; break;
case kInterMasterId.LoginAttempt: case kInterMasterId.LoginAttempt:

View file

@ -10,9 +10,8 @@ using Glove;
using SockScape.Encryption; using SockScape.Encryption;
namespace SockScape { namespace SockScape {
static class MasterUdpClient { static class MasterIntraClient {
private static Key Key; private static Key Key;
private static Cipher Encryptor;
private static UdpClient Sock; private static UdpClient Sock;
private static Thread ListeningThread; private static Thread ListeningThread;
@ -55,10 +54,9 @@ namespace SockScape {
switch((kIntraMasterId)packet.Id) { switch((kIntraMasterId)packet.Id) {
case kIntraMasterId.KeyExchange: case kIntraMasterId.KeyExchange:
var responsePacket = Key.ParseRequestPacket(packet); var responsePacket = Key.ParseRequestPacket(packet);
Encryptor = new Cipher(Key.PrivateKey); Encryptor = new StreamCipher(Key.PrivateKey);
if(responsePacket != null) if(responsePacket != null)
Send(responsePacket); Send(responsePacket);
else else
LastMessageIn = new DateTime(0); LastMessageIn = new DateTime(0);
break; break;
@ -72,6 +70,8 @@ namespace SockScape {
break; break;
case kIntraMasterId.EncryptionError: case kIntraMasterId.EncryptionError:
NextSendId = NextRecvId = 0;
Buffer.Clear();
Key = new Key(); Key = new Key();
Encryptor = null; Encryptor = null;
LastMessageIn = new DateTime(0); LastMessageIn = new DateTime(0);
@ -79,9 +79,9 @@ namespace SockScape {
} }
} }
if (LastMessageIn.Ticks != 0) { if(LastMessageIn.Ticks != 0) {
if(DeltaLastOut.TotalSeconds > 2) if(DeltaLastOut.TotalSeconds > 2)
Send(new Packet()); Send(Encryptor.Parse(ServerContext.StatusUpdatePacket.GetBytes()));
} else } else
if(DeltaLastOut.TotalSeconds > 10) if(DeltaLastOut.TotalSeconds > 10)
Send(new Packet(kIntraSlaveId.InitiationAttempt, Configuration.General["Master Secret"])); Send(new Packet(kIntraSlaveId.InitiationAttempt, Configuration.General["Master Secret"]));
@ -91,9 +91,14 @@ namespace SockScape {
} }
public static void Send(Packet packet) { public static void Send(Packet packet) {
var message = packet.GetBytes(); Send(packet.GetBytes());
Sock.Send(message, message.Length); }
public static void Send(byte[] bytes) {
Sock.Send(bytes, bytes.Length);
LastMessageOut = DateTime.Now; LastMessageOut = DateTime.Now;
Buffer.Add(NextSendId, bytes);
++NextSendId;
} }
public static void Close() { public static void Close() {

View file

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -23,7 +24,7 @@ namespace SockScape {
if(IsOpen || ListeningThread != null) if(IsOpen || ListeningThread != null)
return; return;
ServerList.Clear(); MasterServerList.Clear();
Prospects = new Dictionary<string, Client>(); Prospects = new Dictionary<string, Client>();
Clients = new Dictionary<string, Client>(); Clients = new Dictionary<string, Client>();
@ -49,8 +50,11 @@ namespace SockScape {
encryptor == null ? Packet.FromBytes(data) encryptor == null ? Packet.FromBytes(data)
: Packet.FromBytes(encryptor.Parse(data)); : Packet.FromBytes(encryptor.Parse(data));
if(packet == null) if(packet == null) {
if(encryptor != null)
EncryptionError(endPoint);
continue; continue;
}
Clients[client].LastReceive = DateTime.Now; Clients[client].LastReceive = DateTime.Now;
switch((kIntraSlaveId)packet.Id) { switch((kIntraSlaveId)packet.Id) {
@ -76,7 +80,8 @@ namespace SockScape {
var privateKey = Prospects[client].Key.ParseResponsePacket(packet); var privateKey = Prospects[client].Key.ParseResponsePacket(packet);
if(privateKey != -1) { if(privateKey != -1) {
Prospects[client].Encryptor = new Cipher(privateKey); Prospects[client].LastReceive = DateTime.Now;
Prospects[client].Encryptor = new StreamCipher(privateKey);
Clients[client] = Prospects[client]; Clients[client] = Prospects[client];
Prospects.Remove(client); Prospects.Remove(client);
} else } else
@ -88,13 +93,13 @@ namespace SockScape {
break; break;
if(packet.CheckRegions(0, 1)) { if(packet.CheckRegions(0, 1)) {
NegativeAck(endPoint, kIntraSlaveId.StatusUpdate, "Server count is malformed."); NegativeAck(endPoint, encryptor, kIntraSlaveId.StatusUpdate, "Server count is malformed.");
break; break;
} }
byte serverCount = packet[0].Raw[0]; byte serverCount = packet[0].Raw[0];
if(packet.RegionCount != 1 + 3 * serverCount) { if(packet.RegionCount != 1 + 3 * serverCount) {
NegativeAck(endPoint, kIntraSlaveId.StatusUpdate, "Region count does not match server count"); NegativeAck(endPoint, encryptor, kIntraSlaveId.StatusUpdate, "Region count does not match server count");
break; break;
} }
@ -102,7 +107,7 @@ namespace SockScape {
if(!packet.CheckRegions(2 + 3 * i, 2, 2, 2)) if(!packet.CheckRegions(2 + 3 * i, 2, 2, 2))
continue; continue;
ServerList.Write(new Server { MasterServerList.Write(new Server {
Id = packet[2 + 3 * i].Raw.UnpackUInt16(), Id = packet[2 + 3 * i].Raw.UnpackUInt16(),
UserCount = packet[3 + 3 * i].Raw.UnpackUInt16(), UserCount = packet[3 + 3 * i].Raw.UnpackUInt16(),
Address = endPoint.Address, Address = endPoint.Address,
@ -110,7 +115,7 @@ namespace SockScape {
}); });
} }
PositiveAck(endPoint, kIntraSlaveId.StatusUpdate); PositiveAck(endPoint, encryptor, kIntraSlaveId.StatusUpdate);
break; break;
} }
} }
@ -122,8 +127,11 @@ namespace SockScape {
} }
private static void Send(Packet packet, IPEndPoint client) { private static void Send(Packet packet, IPEndPoint client) {
var message = packet.GetBytes(); Send(packet.GetBytes(), client);
Sock.Send(message, message.Length, client); }
private static void Send(byte[] bytes, IPEndPoint client) {
Sock.Send(bytes, bytes.Length, client);
} }
public static void Close() { public static void Close() {
@ -141,12 +149,12 @@ namespace SockScape {
return Clients.ContainsKey(client); return Clients.ContainsKey(client);
} }
private static void PositiveAck(IPEndPoint endPoint, kIntraSlaveId id) { private static void PositiveAck(IPEndPoint endPoint, StreamCipher cipher, kIntraSlaveId id) {
Send(new Packet(kIntraMasterId.PositiveAck, id), endPoint); Send(cipher.Parse(new Packet(kIntraMasterId.PositiveAck, id).GetBytes()), endPoint);
} }
private static void NegativeAck(IPEndPoint endPoint, kIntraSlaveId id, string message = "An error occurred while parsing a packet.") { private static void NegativeAck(IPEndPoint endPoint, StreamCipher cipher, kIntraSlaveId id, string message = "An error occurred while parsing a packet.") {
Send(new Packet(kIntraMasterId.NegativeAck, id, message), endPoint); Send(cipher.Parse(new Packet(kIntraMasterId.NegativeAck, id, message).GetBytes()), endPoint);
} }
private static void EncryptionError(IPEndPoint endPoint, string message = "A general encryption error has occurred. Renegotiation required.") { private static void EncryptionError(IPEndPoint endPoint, string message = "A general encryption error has occurred. Renegotiation required.") {
@ -157,7 +165,7 @@ namespace SockScape {
public IPEndPoint Address { get; set; } public IPEndPoint Address { get; set; }
public DateTime LastReceive { get; set; } public DateTime LastReceive { get; set; }
public TimeSpan ReceiveDelta => DateTime.Now - LastReceive; public TimeSpan ReceiveDelta => DateTime.Now - LastReceive;
public Cipher Encryptor { get; set; } public StreamCipher Encryptor { get; set; }
public Key Key { get; set; } public Key Key { get; set; }
} }
} }

View file

@ -92,6 +92,13 @@ namespace SockScape {
return this; return this;
} }
public Packet AddRegions(params object[] regions) {
foreach(var region in regions)
AddRegion(region);
return this;
}
public bool CheckRegions(int startIndex, params int[] lengths) { public bool CheckRegions(int startIndex, params int[] lengths) {
if(startIndex + lengths.Length > RegionCount) if(startIndex + lengths.Length > RegionCount)
return false; return false;
@ -150,4 +157,32 @@ namespace SockScape {
=> this; => this;
} }
} }
public class PacketBuffer {
private short MaxSize;
private Dictionary<uint, byte[]> Buffer
= new Dictionary<uint, byte[]>();
public PacketBuffer(short maxSize = 10) {
MaxSize = maxSize;
}
public void Add(uint id, byte[] packet) {
Buffer[id] = packet;
if(Buffer.Count > 10)
Buffer =
Buffer.Where(x => x.Key >= id - 10)
.ToDictionary(x => x.Key, x => x.Value);
}
public byte[] this[uint i]
=> Buffer[i];
public bool HasId(uint id)
=> Buffer.ContainsKey(id);
public void Clear()
=> Buffer.Clear();
}
} }