From 3337cfa4e2c7f1ec5674c9f6ddf5cd0b1a73b9a4 Mon Sep 17 00:00:00 2001
From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com>
Date: Sat, 24 Oct 2020 21:49:28 -0700
Subject: [PATCH] Adds PacketDecoder & Makes NetState testable (#286)
---
Projects/Server/Buffers/SpanWriter.cs | 12 +-
Projects/Server/Network/IPacketEncoder.cs | 25 ----
Projects/Server/Network/NetState/NetState.cs | 113 ++++++++++++++++--
Projects/Server/Network/NetworkCompression.cs | 61 ++++++++++
Projects/Server/Network/PacketHandlers.cs | 1 +
5 files changed, 177 insertions(+), 35 deletions(-)
delete mode 100644 Projects/Server/Network/IPacketEncoder.cs
diff --git a/Projects/Server/Buffers/SpanWriter.cs b/Projects/Server/Buffers/SpanWriter.cs
index cae1a5068..d61b2d352 100644
--- a/Projects/Server/Buffers/SpanWriter.cs
+++ b/Projects/Server/Buffers/SpanWriter.cs
@@ -161,7 +161,17 @@ namespace Server.Buffers
throw new OutOfMemoryException();
}
- Position += encoding.GetBytes(src, _buffer.Slice(Position));
+ var bytesWritten = encoding.GetBytes(src, _buffer.Slice(Position));
+ Position += bytesWritten;
+
+ if (fixedLength > -1)
+ {
+ var extra = fixedLength * sizeT - bytesWritten;
+ if (extra > 0)
+ {
+ Clear(extra);
+ }
+ }
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
diff --git a/Projects/Server/Network/IPacketEncoder.cs b/Projects/Server/Network/IPacketEncoder.cs
deleted file mode 100644
index cecbb220d..000000000
--- a/Projects/Server/Network/IPacketEncoder.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-/*************************************************************************
- * ModernUO *
- * Copyright 2019-2020 - ModernUO Development Team *
- * Email: hi@modernuo.com *
- * File: IPacketEncoder.cs *
- * *
- * This program is free software: you can redistribute it and/or modify *
- * it under the terms of the GNU General Public License as published by *
- * the Free Software Foundation, either version 3 of the License, or *
- * (at your option) any later version. *
- * *
- * You should have received a copy of the GNU General Public License *
- * along with this program. If not, see . *
- *************************************************************************/
-
-using System;
-
-namespace Server.Network
-{
- public interface IPacketEncoder
- {
- void EncodeOutgoingPacket(NetState to, ref Memory seq);
- void DecodeIncomingPacket(NetState from, ref Memory seq);
- }
-}
diff --git a/Projects/Server/Network/NetState/NetState.cs b/Projects/Server/Network/NetState/NetState.cs
index 18ef0b071..0f3f2f681 100644
--- a/Projects/Server/Network/NetState/NetState.cs
+++ b/Projects/Server/Network/NetState/NetState.cs
@@ -33,6 +33,8 @@ namespace Server.Network
{
public delegate void NetStateCreatedCallback(NetState ns);
+ public delegate void EncodePacket(ReadOnlySpan inputBuffer, CircularBufferWriter outputBuffer);
+
public partial class NetState : IComparable
{
private static int IncomingPipeSize = 1024 * 64;
@@ -55,6 +57,9 @@ namespace Server.Network
private Pipe m_OutgoingPipe;
private long m_NextCheckActivity;
private volatile bool m_Running;
+ private Thread _sendThread;
+ private volatile EncodePacket _packetDecoder;
+ private volatile EncodePacket _packetEncoder;
internal int m_AuthID;
internal int m_Seed;
@@ -74,7 +79,7 @@ namespace Server.Network
Timer.DelayCall(checkAliveDuration, checkAliveDuration, CheckAllAlive);
}
- public NetState(Socket connection)
+ public NetState(Socket connection, Thread sendThread = null)
{
m_Running = false;
Connection = connection;
@@ -88,6 +93,7 @@ namespace Server.Network
m_OutgoingBuffer = new byte[OutgoingPipeSize];
m_OutgoingPipe = new Pipe(m_OutgoingBuffer);
m_NextCheckActivity = Core.TickCount + 30000;
+ _sendThread = sendThread ?? Core.Thread;
try
{
@@ -114,7 +120,17 @@ namespace Server.Network
public IPAddress Address { get; }
- public IPacketEncoder PacketEncoder { get; set; }
+ public EncodePacket PacketDecoder
+ {
+ get => _packetDecoder;
+ set => _packetDecoder = value;
+ }
+
+ public EncodePacket PacketEncoder
+ {
+ get => _packetEncoder;
+ set => _packetEncoder = value;
+ }
public bool SentFirstPacket { get; set; }
@@ -354,6 +370,53 @@ namespace Server.Network
NetworkState.Resume(ref m_NetworkState);
}
+ public virtual void Send(Span buffer)
+ {
+ if (Connection == null || BlockAllPackets || buffer.Length == 0)
+ {
+ return;
+ }
+
+ var currentThread = Thread.CurrentThread;
+
+ if (currentThread != _sendThread)
+ {
+ Console.Error.WriteLine("Core: Attempted to send packet outside core thread! [{0}]", currentThread.ManagedThreadId);
+#if DEBUG
+ throw new InvalidThreadException(nameof(Send));
+#endif
+ }
+
+ var writer = m_OutgoingPipe.Writer;
+
+ try
+ {
+ var result = writer.GetAvailable();
+ int length;
+ if (PacketEncoder != null)
+ {
+ var bufferWriter = new CircularBufferWriter(result.Buffer);
+ PacketEncoder?.Invoke(buffer, bufferWriter);
+ length = bufferWriter.Position;
+ }
+ else
+ {
+ result.CopyFrom(buffer);
+ length = buffer.Length;
+ }
+
+ writer.Advance((uint)length);
+ }
+ catch (Exception ex)
+ {
+#if DEBUG
+ Console.WriteLine(ex);
+ TraceException(ex);
+#endif
+ Dispose();
+ }
+ }
+
public virtual void Send(Packet p)
{
if (Connection == null || BlockAllPackets)
@@ -364,9 +427,9 @@ namespace Server.Network
var currentThread = Thread.CurrentThread;
- if (currentThread != Core.Thread)
+ if (currentThread != _sendThread)
{
- Console.Error.WriteLine("Core: Attempted to send packet outside core thread! [{0}]", currentThread.ManagedThreadId);
+ Console.Error.WriteLine("Core: Attempted to send packet outside send thread! [{0}]", currentThread.ManagedThreadId);
#if DEBUG
throw new InvalidThreadException(nameof(Send));
#endif
@@ -464,11 +527,20 @@ namespace Server.Network
}
}
+ private int DecodePacket(ReadOnlySpan input, ArraySegment[] output)
+ {
+ var writer = new CircularBufferWriter(output);
+ PacketDecoder(input, writer);
+ return writer.Position;
+ }
+
private async void RecvTask(object state)
{
var socket = Connection;
var writer = m_IncomingPipe.Writer;
+ byte[] encodingBuffer = null;
+
try
{
while (m_Running)
@@ -485,13 +557,32 @@ namespace Server.Network
continue;
}
- var buffer = result.Buffer;
+ int bytesWritten;
- var bytesWritten = await socket.ReceiveAsync(buffer, SocketFlags.None);
-
- if (bytesWritten <= 0)
+ if (PacketDecoder != null)
{
- break;
+ encodingBuffer ??= ArrayPool.Shared.Rent(0x10000);
+ bytesWritten = await socket.ReceiveAsync(encodingBuffer, SocketFlags.None);
+ if (bytesWritten <= 0)
+ {
+ break;
+ }
+ bytesWritten = DecodePacket(encodingBuffer.AsSpan(0, bytesWritten), result.Buffer);
+ }
+ else
+ {
+ if (encodingBuffer != null)
+ {
+ var returnBuffer = encodingBuffer;
+ encodingBuffer = null;
+ ArrayPool.Shared.Return(returnBuffer);
+ }
+
+ bytesWritten = await socket.ReceiveAsync(result.Buffer, SocketFlags.None);
+ if (bytesWritten <= 0)
+ {
+ break;
+ }
}
writer.Advance((uint)bytesWritten);
@@ -509,6 +600,10 @@ namespace Server.Network
}
finally
{
+ if (encodingBuffer != null)
+ {
+ ArrayPool.Shared.Return(encodingBuffer);
+ }
Dispose();
}
}
diff --git a/Projects/Server/Network/NetworkCompression.cs b/Projects/Server/Network/NetworkCompression.cs
index 4b2776765..946df2b4e 100644
--- a/Projects/Server/Network/NetworkCompression.cs
+++ b/Projects/Server/Network/NetworkCompression.cs
@@ -1,4 +1,5 @@
using System;
+using System.Buffers;
namespace Server.Network
{
@@ -60,6 +61,66 @@ namespace Server.Network
0x4, 0x00D
};
+ public static void Compress(ReadOnlySpan input, CircularBufferWriter output)
+ {
+ int inputCapacity = input.Length;
+
+ if (inputCapacity > DefiniteOverflow)
+ {
+ return;
+ }
+
+ int bitCount = 0;
+ int bitValue = 0;
+
+ int inputIdx = 0;
+
+ while (inputIdx < inputCapacity)
+ {
+ int i = input[inputIdx++] << 1;
+
+ bitCount += _huffmanTable[i];
+ bitValue <<= _huffmanTable[i];
+ bitValue |= _huffmanTable[i + 1];
+
+ while (bitCount >= 8)
+ {
+ bitCount -= 8;
+
+ if (output.Length < output.Position + 1)
+ {
+ return;
+ }
+
+ output.Write((byte)(bitValue >> bitCount));
+ }
+ }
+
+ // terminal code
+ bitCount += _huffmanTable[0x200];
+ bitValue <<= _huffmanTable[0x200];
+ bitValue |= _huffmanTable[0x201];
+
+ // align on byte boundary
+ if ((bitCount & 7) != 0)
+ {
+ bitValue <<= (8 - (bitCount & 7));
+ bitCount += (8 - (bitCount & 7));
+ }
+
+ while (bitCount >= 8)
+ {
+ bitCount -= 8;
+
+ if (output.Length < output.Position + 1)
+ {
+ return;
+ }
+
+ output.Write((byte)(bitValue >> bitCount));
+ }
+ }
+
public static unsafe void Compress(
ReadOnlySpan input, int offset, int count, Span output, out int length
)
diff --git a/Projects/Server/Network/PacketHandlers.cs b/Projects/Server/Network/PacketHandlers.cs
index 5d06fba56..e1386bdde 100644
--- a/Projects/Server/Network/PacketHandlers.cs
+++ b/Projects/Server/Network/PacketHandlers.cs
@@ -2549,6 +2549,7 @@ namespace Server.Network
{
state.CityInfo = e.CityInfo;
state.CompressionEnabled = true;
+ state.PacketEncoder = NetworkCompression.Compress;
state.Send(SupportedFeatures.Instantiate(state));