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