Replacing Networking (#271)

- [X] Removing Kestrel & Libuv
- [X] Cleaning up NetState
- [X] Removing System.IO.Pipelines
- [X] Cleaning up packet reading
- [X] Adds a maximum of 5000 sockets (configurable) to prevent OOM
- [X] Replaces the AsyncState with a thread-safe wrapped boolean called NetworkState
- [X] Removes Parallel.ForEach (no perf gain)
- [X] Removes custom houses compression on another thread
- [X] Test high load scenarios

Bumps release version
This commit is contained in:
Kamron Batman 2020-10-20 20:55:19 -07:00 committed by GitHub
parent 8603e31023
commit 369a27b800
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
47 changed files with 1780 additions and 1569 deletions

View file

@ -18,9 +18,9 @@ namespace Server.Network
: new Point3D(m_Reader.ReadInt16(), m_Reader.ReadInt16(), m_Reader.ReadByte());
public string ReadUnicodeStringSafe() =>
m_Reader.ReadByte() != 2 ? string.Empty : m_Reader.ReadUnicodeStringSafe(m_Reader.ReadUInt16());
m_Reader.ReadByte() != 2 ? string.Empty : m_Reader.ReadBigUniSafe(m_Reader.ReadUInt16());
public string ReadUnicodeString() =>
m_Reader.ReadByte() != 2 ? string.Empty : m_Reader.ReadUnicodeString(m_Reader.ReadUInt16());
m_Reader.ReadByte() != 2 ? string.Empty : m_Reader.ReadBigUni(m_Reader.ReadUInt16());
}
}

View file

@ -1,69 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2020 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: MessagePumpService.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 <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Buffers;
using System.Collections.Concurrent;
namespace Server.Network
{
public interface IMessagePumpService
{
void QueueWork(NetState ns, IMemoryOwner<byte> memOwner, int length, OnPacketReceive onReceive);
void DoWork();
}
public class MessagePumpService : IMessagePumpService
{
private readonly ConcurrentQueue<Work> m_WorkQueue = new ConcurrentQueue<Work>();
public void QueueWork(NetState ns, IMemoryOwner<byte> memOwner, int length, OnPacketReceive onReceive)
{
m_WorkQueue.Enqueue(new Work(ns, memOwner, length, onReceive));
Core.Set();
}
public void DoWork()
{
var count = 0;
while (!m_WorkQueue.IsEmpty && count++ < 250)
{
if (!m_WorkQueue.TryDequeue(out var work))
{
break;
}
var seq = new ReadOnlySequence<byte>(work.MemoryOwner.Memory.Slice(0, work.Length));
work.OnReceive(work.State, new PacketReader(seq));
work.MemoryOwner.Dispose();
}
}
private class Work
{
public readonly int Length;
public readonly IMemoryOwner<byte> MemoryOwner;
public readonly OnPacketReceive OnReceive;
public readonly NetState State;
public Work(NetState ns, IMemoryOwner<byte> memOwner, int length, OnPacketReceive onReceive)
{
State = ns;
MemoryOwner = memOwner;
OnReceive = onReceive;
Length = length;
}
}
}
}

View file

@ -1,13 +1,28 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2020 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: NetState.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 <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Server.Accounting;
using Server.Exceptions;
using Server.Gumps;
using Server.HuePickers;
using Server.Items;
@ -17,41 +32,66 @@ namespace Server.Network
{
public delegate void NetStateCreatedCallback(NetState ns);
public class AsyncState
{
public AsyncState(bool paused) => Paused = paused;
public bool Paused { get; set; }
}
public partial class NetState : IComparable<NetState>
{
private static readonly AsyncState m_PauseState = new AsyncState(true);
private static readonly AsyncState m_ResumeState = new AsyncState(false);
private static AsyncState m_AsyncState = m_ResumeState;
private static int IncomingPipeSize = 1024 * 64;
private static int OutgoingPipeSize = 1024 * 256;
private static int GumpCap = 512;
private static int HuePickerCap = 512;
private static int MenuCap = 512;
private static readonly ConcurrentQueue<NetState> m_Disposed = new ConcurrentQueue<NetState>();
private static NetworkState m_NetworkState = NetworkState.ResumeState;
public static NetStateCreatedCallback CreatedCallback { get; set; }
private readonly string m_ToString;
internal int m_AuthID;
private int m_Disposing;
internal int m_Seed;
private ClientVersion m_Version;
private byte[] m_IncomingBuffer;
private Pipe<byte> m_IncomingPipe;
private byte[] m_OutgoingBuffer;
private Pipe<byte> m_OutgoingPipe;
private long m_NextCheckActivity;
private volatile bool m_Running;
public NetState(ConnectionContext connection)
internal int m_AuthID;
internal int m_Seed;
public static void Configure()
{
IncomingPipeSize = ServerConfiguration.GetOrUpdateSetting("netstate.incomingPipeSize", IncomingPipeSize);
OutgoingPipeSize = ServerConfiguration.GetOrUpdateSetting("netstate.outgoingPipeSize", OutgoingPipeSize);
GumpCap = ServerConfiguration.GetOrUpdateSetting("netstate.gumpCap", GumpCap);
HuePickerCap = ServerConfiguration.GetOrUpdateSetting("netstate.huePickerCap", HuePickerCap);
MenuCap = ServerConfiguration.GetOrUpdateSetting("netstate.menuCap", MenuCap);
}
public static void Initialize()
{
var checkAliveDuration = TimeSpan.FromMinutes(1.5);
Timer.DelayCall(checkAliveDuration, checkAliveDuration, CheckAllAlive);
}
public NetState(Socket connection)
{
m_Running = false;
Connection = connection;
Seeded = false;
Gumps = new List<Gump>();
HuePickers = new List<HuePicker>();
Menus = new List<IMenu>();
Trades = new List<SecureTrade>();
m_IncomingBuffer = new byte[IncomingPipeSize];
m_IncomingPipe = new Pipe<byte>(m_IncomingBuffer);
m_OutgoingBuffer = new byte[OutgoingPipeSize];
m_OutgoingPipe = new Pipe<byte>(m_OutgoingBuffer);
m_NextCheckActivity = Core.TickCount + 30000;
try
{
Address = Utility.Intern(((IPEndPoint)Connection.RemoteEndPoint).Address);
m_ToString = Address.ToString();
Address = Utility.Intern((Connection?.RemoteEndPoint as IPEndPoint)?.Address);
m_ToString = Address?.ToString() ?? "(error)";
}
catch (Exception ex)
{
@ -62,14 +102,6 @@ namespace Server.Network
ConnectedOn = DateTime.UtcNow;
connection.ConnectionClosed.Register(
() =>
{
TcpServer.Instances.Remove(this);
Dispose();
}
);
CreatedCallback?.Invoke(this);
}
@ -80,21 +112,20 @@ namespace Server.Network
public DateTime ThrottledUntil { get; set; }
public IPAddress Address { get; }
public static AsyncState AsyncState => m_AsyncState;
public IPacketEncoder PacketEncoder { get; set; }
public static NetStateCreatedCallback CreatedCallback { get; set; }
public bool SentFirstPacket { get; set; }
public bool BlockAllPackets { get; set; }
public List<SecureTrade> Trades { get; }
public bool Running => m_Running;
public bool Seeded { get; set; }
public ConnectionContext Connection { get; private set; }
public Socket Connection { get; private set; }
public bool CompressionEnabled { get; set; }
@ -106,12 +137,6 @@ namespace Server.Network
public List<IMenu> Menus { get; private set; }
public static int GumpCap { get; set; } = 512;
public static int HuePickerCap { get; set; } = 512;
public static int MenuCap { get; set; } = 512;
public CityInfo[] CityInfo { get; set; }
public Mobile Mobile { get; set; }
@ -208,11 +233,13 @@ namespace Server.Network
return newTrade.From.Container;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteConsole(string text)
{
Console.WriteLine("Client: {0}: {1}", this, text);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteConsole(string format, params object[] args)
{
WriteConsole(string.Format(format, args));
@ -318,15 +345,15 @@ namespace Server.Network
public static void Pause()
{
m_AsyncState = Interlocked.Exchange(ref m_AsyncState, m_PauseState);
NetworkState.Pause(ref m_NetworkState);
}
public static void Resume()
{
m_AsyncState = Interlocked.Exchange(ref m_AsyncState, m_ResumeState);
NetworkState.Resume(ref m_NetworkState);
}
public virtual async void Send(Packet p)
public virtual void Send(Packet p)
{
if (Connection == null || BlockAllPackets)
{
@ -334,83 +361,101 @@ namespace Server.Network
return;
}
var outPipe = Connection.Transport.Output;
var currentThread = Thread.CurrentThread;
if (currentThread != Core.Thread)
{
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
{
// TODO: Rented memory
ReadOnlyMemory<byte> buffer = p.Compile(CompressionEnabled, out var length);
byte[] buffer = p.Compile(CompressionEnabled, out var length);
if (buffer.Length > 0 && length > 0)
{
var result = await outPipe.WriteAsync(buffer.Slice(0, length));
var result = writer.GetAvailable();
if (result.IsCanceled || result.IsCompleted)
if (result.Length >= length)
{
Dispose();
return;
result.CopyFrom(buffer.AsSpan(0, length));
writer.Advance((uint)length);
// Flush at the end of the game loop
}
else
{
WriteConsole("Too much data pending, disconnecting...");
Dispose();
}
}
else
{
WriteConsole("Didn't write anything!");
}
p.OnSend();
}
catch (SocketException ex)
{
Console.WriteLine(ex);
TraceException(ex);
Dispose();
}
catch (Exception ex)
{
#if DEBUG
Console.WriteLine(ex);
TraceException(ex);
#endif
Dispose();
}
}
public async Task ProcessIncoming(IMessagePumpService messagePumpService)
internal void Start()
{
var inPipe = Connection.Transport.Input;
if (Connection == null || m_Running)
{
return;
}
m_Running = true;
ThreadPool.UnsafeQueueUserWorkItem(RecvTask, null);
ThreadPool.UnsafeQueueUserWorkItem(SendTask, null);
}
private async void SendTask(object state)
{
var reader = m_OutgoingPipe.Reader;
try
{
while (true)
while (m_Running)
{
if (AsyncState.Paused)
var result = await reader.Read();
if (result.Length <= 0)
{
continue;
}
var result = await inPipe.ReadAsync();
if (result.IsCanceled || result.IsCompleted)
var buffer = result.Buffer;
var bytesWritten = await Connection.SendAsync(buffer, SocketFlags.None);
if (bytesWritten > 0)
{
return;
m_NextCheckActivity = Core.TickCount + 90000;
reader.Advance((uint)bytesWritten);
}
var seq = result.Buffer;
if (seq.IsEmpty)
{
break;
}
var pos = PacketHandlers.ProcessPacket(messagePumpService, this, seq);
if (pos <= 0)
{
break;
}
inPipe.AdvanceTo(seq.Slice(0, pos).End);
}
}
catch (SocketException ex)
catch (Exception ex)
{
#if DEBUG
Console.WriteLine(ex);
TraceException(ex);
}
catch
{
// Console.WriteLine(ex);
#endif
}
finally
{
@ -418,12 +463,165 @@ namespace Server.Network
}
}
private async void RecvTask(object state)
{
var socket = Connection;
var writer = m_IncomingPipe.Writer;
try
{
while (m_Running)
{
if (m_NetworkState == NetworkState.PauseState)
{
continue;
}
var result = writer.GetAvailable();
if (result.Length <= 0)
{
continue;
}
var buffer = result.Buffer;
var bytesWritten = await socket.ReceiveAsync(buffer, SocketFlags.None);
if (bytesWritten <= 0)
{
break;
}
writer.Advance((uint)bytesWritten);
m_NextCheckActivity = Core.TickCount + 90000;
// No need to flush
}
}
catch (Exception ex)
{
#if DEBUG
Console.WriteLine(ex);
TraceException(ex);
#endif
}
finally
{
Dispose();
}
}
public static void HandleAllReceives()
{
var clients = TcpServer.Instances;
for (int i = 0; i < clients.Count; ++i)
{
clients[i].HandleReceive();
}
}
public void HandleReceive()
{
if (Connection == null)
{
return;
}
try
{
var reader = m_IncomingPipe.Reader;
// Process as many packets as we can synchronously
while (true)
{
var result = reader.TryRead();
if (result.Length <= 0)
{
return;
}
var bytesProcessed = PacketHandlers.ProcessPacket(this, result.Buffer);
if (bytesProcessed <= 0)
{
// Error
// TODO: Throw exception instead?
if (bytesProcessed < 0)
{
Dispose();
}
return;
}
reader.Advance((uint)bytesProcessed);
}
}
catch (Exception ex)
{
#if DEBUG
Console.WriteLine(ex);
TraceException(ex);
#endif
Dispose();
}
}
public void Flush()
{
if (Connection != null)
{
m_OutgoingPipe.Writer.Flush();
}
}
public static void FlushAll()
{
var clients = TcpServer.Instances;
for (int i = 0; i < clients.Count; ++i)
{
clients[i].Flush();
}
}
public void CheckAlive(long curTicks)
{
if (Connection != null && m_NextCheckActivity - curTicks < 0)
{
WriteConsole("Disconnecting due to inactivity...");
Dispose();
}
}
public static void CheckAllAlive()
{
try
{
long curTicks = Core.TickCount;
var clients = TcpServer.Instances;
for (int i = 0; i < clients.Count; ++i)
{
clients[i].CheckAlive(curTicks);
}
}
catch (Exception ex)
{
TraceException(ex);
}
}
public bool CheckEncrypted(int packetID)
{
if (!SentFirstPacket && packetID != 0xF0 && packetID != 0xF1 && packetID != 0xCF && packetID != 0x80 &&
packetID != 0x91 && packetID != 0xA4 && packetID != 0xEF)
{
Console.WriteLine("Client: {0}: Encrypted client detected, disconnecting", this);
WriteConsole("Encrypted client detected, disconnecting");
Dispose();
return true;
}
@ -456,26 +654,25 @@ namespace Server.Network
public virtual void Dispose()
{
var disposing = Interlocked.Exchange(ref m_Disposing, 1);
if (disposing == 1)
if (Connection == null || Interlocked.Exchange(ref m_Disposing, 1) != 0)
{
return;
}
m_OutgoingPipe.Writer.Close();
try
{
Connection.Transport.Input.Complete();
Connection.Transport.Output.Complete();
Connection.Abort();
Task.Run(Connection.DisposeAsync).Wait();
Connection.Shutdown(SocketShutdown.Both);
}
catch (Exception ex)
{
TraceException(ex);
}
Connection = null;
m_Disposed.Enqueue(this);
finally
{
m_Disposed.Enqueue(this);
}
}
public static void ProcessDisposedQueue()
@ -498,6 +695,12 @@ namespace Server.Network
ns.Mobile = null;
}
ns.m_Running = false;
ns.Connection = null;
ns.m_IncomingBuffer = null;
ns.m_IncomingPipe = null;
ns.m_OutgoingBuffer = null;
ns.m_OutgoingPipe = null;
ns.Gumps.Clear();
ns.Menus.Clear();
ns.HuePickers.Clear();
@ -505,6 +708,8 @@ namespace Server.Network
ns.ServerInfo = null;
ns.CityInfo = null;
TcpServer.Instances.Remove(ns);
if (a != null)
{
ns.WriteConsole("Disconnected. [{0} Online] [{1}]", TcpServer.Instances.Count, a);

View file

@ -2,7 +2,7 @@
* ModernUO *
* Copyright 2019-2020 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: ServerStartup.cs *
* File: NetworkState.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 *
@ -13,25 +13,26 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using System.Runtime.CompilerServices;
using System.Threading;
namespace Server.Network
{
public class ServerStartup
public class NetworkState
{
private readonly IMessagePumpService _messagePumpService;
public ServerStartup(IMessagePumpService messagePumpService) => _messagePumpService = messagePumpService;
public static readonly NetworkState PauseState = new NetworkState(true);
public static readonly NetworkState ResumeState = new NetworkState(false);
public void ConfigureServices(IServiceCollection services)
{
}
public bool Paused { get; }
public void Configure(IApplicationBuilder app)
{
// Run async?
Task.Run(() => Core.RunEventLoop(_messagePumpService));
}
internal NetworkState(bool paused) => Paused = paused;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Pause(ref NetworkState state) =>
Interlocked.Exchange(ref state, PauseState)?.Paused != true;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Resume(ref NetworkState state) =>
Interlocked.Exchange(ref state, ResumeState)?.Paused == true;
}
}

View file

@ -193,9 +193,8 @@ namespace Server.Network
if (compress)
{
var buffer = ArrayPool<byte>.Shared.Rent(CompressorBufferSize);
NetworkCompression.Compress(m_CompiledBuffer, 0, length, buffer, out length);
var compressorBuffer = ArrayPool<byte>.Shared.Rent(CompressorBufferSize);
NetworkCompression.Compress(m_CompiledBuffer, 0, length, compressorBuffer, out var compressedLength);
if (length <= 0)
{
@ -214,28 +213,23 @@ namespace Server.Network
length
);
op.WriteLine(new StackTrace());
ArrayPool<byte>.Shared.Return(compressorBuffer);
}
else
{
m_CompiledLength = length;
if ((m_State & State.Static) != 0)
{
m_CompiledBuffer = new byte[length];
Buffer.BlockCopy(buffer, 0, m_CompiledBuffer, 0, length);
ArrayPool<byte>.Shared.Return(buffer);
}
else
{
m_CompiledBuffer = buffer;
m_State |= State.Buffered;
}
m_CompiledBuffer = compressorBuffer;
m_CompiledLength = compressedLength;
}
}
else if (length > 0)
else
{
m_CompiledLength = length;
}
if (length > 0)
{
var old = m_CompiledBuffer;
m_CompiledLength = length;
if ((m_State & State.Static) != 0)
{
@ -243,11 +237,17 @@ namespace Server.Network
}
else
{
// Release it later using Release()
m_CompiledBuffer = ArrayPool<byte>.Shared.Rent(length);
m_State |= State.Buffered;
}
Buffer.BlockCopy(old, 0, m_CompiledBuffer, 0, length);
if (compress)
{
ArrayPool<byte>.Shared.Return(old);
}
}
PacketWriter.ReleaseInstance(Stream);

View file

@ -1,5 +1,19 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2020 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: PacketHandlers.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 <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using Server.ContextMenus;
@ -34,9 +48,6 @@ namespace Server.Network
{
public delegate void PlayCharCallback(NetState state, bool val);
private const int BadFood = unchecked((int)0xBAADF00D);
private const int BadUOTD = unchecked((int)0xFFCEFFCE);
private const int m_AuthIDWindowSize = 128;
private static readonly PacketHandler[] m_6017Handlers = new PacketHandler[0x100];
@ -55,8 +66,6 @@ namespace Server.Network
private static readonly Dictionary<int, AuthIDPersistence> m_AuthIDWindow =
new Dictionary<int, AuthIDPersistence>(m_AuthIDWindowSize);
private static readonly MemoryPool<byte> _memoryPool = SlabMemoryPoolFactory.Create();
static PacketHandlers()
{
Register(0x00, 104, false, CreateCharacter);
@ -272,15 +281,11 @@ namespace Server.Network
}
}
public static int ProcessPacket(IMessagePumpService pump, NetState ns, in ReadOnlySequence<byte> seq)
public static int ProcessPacket(NetState ns, ArraySegment<byte>[] segments)
{
var r = new PacketReader(seq);
var reader = new PacketReader(segments);
if (!r.TryReadByte(out var packetId))
{
ns.Dispose();
return -1;
}
var packetId = reader.ReadByte();
if (!ns.Seeded)
{
@ -292,12 +297,11 @@ namespace Server.Network
}
else
{
var seed = (packetId << 24) | (r.ReadByte() << 16) | (r.ReadByte() << 8) | r.ReadByte();
var seed = (packetId << 24) | (reader.ReadByte() << 16) | (reader.ReadByte() << 8) | reader.ReadByte();
if (seed == 0)
{
Console.WriteLine("Login: {0}: Invalid client detected, disconnecting", ns);
ns.Dispose();
ns.WriteConsole("Invalid client detected, disconnecting");
return -1;
}
@ -310,7 +314,6 @@ namespace Server.Network
if (ns.CheckEncrypted(packetId))
{
ns.Dispose();
return -1;
}
@ -319,34 +322,29 @@ namespace Server.Network
if (handler == null)
{
r.Trace(ns);
reader.Trace(ns);
return -1;
}
var packetLength = handler.Length;
if (handler.Length <= 0 && r.Length >= 3)
if (handler.Length <= 0 && reader.Length >= 3)
{
packetLength = r.ReadUInt16();
packetLength = reader.ReadUInt16();
if (packetLength < 3)
{
ns.Dispose();
return -1;
}
}
if (r.Length < packetLength)
// Not enough data, let's wait for more to come in
if (reader.Length < packetLength)
{
return 0;
}
if (handler.Ingame && ns.Mobile?.Deleted != false)
{
Console.WriteLine(
"Client: {0}: Sent ingame packet (0x{1:X2}) without being attached to a valid mobile.",
ns,
packetId
);
ns.Dispose();
ns.WriteConsole("Sent ingame packet (0x{1:X2}) without being attached to a valid mobile.", ns, packetId);
return -1;
}
@ -357,14 +355,7 @@ namespace Server.Network
ns.ThrottledUntil = DateTime.UtcNow + throttled;
}
var packet = seq.Slice(r.Position);
var length = (int)packet.Length;
var memOwner = _memoryPool.Rent(length);
// TODO: This is slow, find another way
packet.CopyTo(memOwner.Memory.Span);
pump.QueueWork(ns, memOwner, length, handler.OnReceive);
handler.OnReceive(ns, reader);
return packetLength;
}
@ -403,9 +394,8 @@ namespace Server.Network
{
if (ph.Ingame && state.Mobile == null)
{
Console.WriteLine(
"Client: {0}: Sent ingame packet (0xD7x{1:X2}) before having been attached to a mobile",
state,
state.WriteConsole(
"Sent ingame packet (0xD7x{0:X2}) before having been attached to a mobile",
packetId
);
state.Dispose();
@ -432,7 +422,7 @@ namespace Server.Network
if (targ != null)
{
EventSink.InvokeRenameRequest(from, targ, reader.ReadStringSafe());
EventSink.InvokeRenameRequest(from, targ, reader.ReadAsciiSafe());
}
}
@ -526,7 +516,7 @@ namespace Server.Network
if (flag == 0x02)
{
var msgSize = (int)reader.Remaining;
var msgSize = reader.Remaining;
if (msgSize / 7 > 100)
{
@ -616,7 +606,7 @@ namespace Server.Network
Serial serial = reader.ReadUInt32();
int unk = reader.ReadByte();
var lang = reader.ReadString(3);
var lang = reader.ReadAscii(3);
if (serial.IsItem)
{
@ -692,10 +682,10 @@ namespace Server.Network
int v1 = reader.ReadByte();
int v2 = reader.ReadUInt16();
int v3 = reader.ReadByte();
var s1 = reader.ReadString(32);
var s2 = reader.ReadString(32);
var s3 = reader.ReadString(32);
var s4 = reader.ReadString(32);
var s1 = reader.ReadAscii(32);
var s2 = reader.ReadAscii(32);
var s3 = reader.ReadAscii(32);
var s4 = reader.ReadAscii(32);
int v4 = reader.ReadUInt16();
int v5 = reader.ReadUInt16();
var v6 = reader.ReadInt32();
@ -710,7 +700,7 @@ namespace Server.Network
public static void TextCommand(NetState state, PacketReader reader)
{
int type = reader.ReadByte();
var command = reader.ReadString();
var command = reader.ReadAscii();
var m = state.Mobile;
@ -794,7 +784,7 @@ namespace Server.Network
}
default:
{
Console.WriteLine("Client: {0}: Unknown text-command type 0x{1:X2}: {2}", state, type, command);
state.WriteConsole("Unknown text-command type 0x{0:X2}: {1}", state, type, command);
break;
}
}
@ -805,7 +795,7 @@ namespace Server.Network
var serial = reader.ReadUInt32();
var prompt = reader.ReadInt32();
var type = reader.ReadInt32();
var text = reader.ReadStringSafe();
var text = reader.ReadAsciiSafe();
if (text.Length > 128)
{
@ -835,8 +825,8 @@ namespace Server.Network
var serial = reader.ReadUInt32();
var prompt = reader.ReadInt32();
var type = reader.ReadInt32();
var lang = reader.ReadString(4);
var text = reader.ReadUnicodeStringLESafe();
var lang = reader.ReadAscii(4);
var text = reader.ReadLittleUniSafe();
if (text.Length > 128)
{
@ -922,7 +912,7 @@ namespace Server.Network
return;
}
var text = reader.ReadUnicodeString(length);
var text = reader.ReadBigUni(length);
EventSink.InvokeChangeProfileRequest(beholder, beheld, text);
@ -1259,7 +1249,7 @@ namespace Server.Network
return;
}
var text = reader.ReadUnicodeStringSafe(textLength);
var text = reader.ReadBigUniSafe(textLength);
textEntries[j] = new TextRelay(entryID, text);
}
@ -1336,7 +1326,7 @@ namespace Server.Network
var type = (MessageType)reader.ReadByte();
int hue = reader.ReadInt16();
reader.ReadInt16(); // font
var text = reader.ReadStringSafe().Trim();
var text = reader.ReadAsciiSafe().Trim();
if (text.Length <= 0 || text.Length > 128)
{
@ -1358,7 +1348,7 @@ namespace Server.Network
var type = (MessageType)reader.ReadByte();
int hue = reader.ReadInt16();
reader.ReadInt16(); // font
var lang = reader.ReadString(4);
var lang = reader.ReadAscii(4);
string text;
var isEncoded = (type & MessageType.Encoded) != 0;
@ -1401,13 +1391,13 @@ namespace Server.Network
}
}
text = reader.ReadUTF8StringSafe();
text = reader.ReadUTF8Safe();
keywords = keyList.ToArray();
}
else
{
text = reader.ReadUnicodeStringSafe();
text = reader.ReadBigUniSafe();
keywords = m_EmptyInts;
}
@ -1603,9 +1593,8 @@ namespace Server.Network
{
if (state.Mobile == null)
{
Console.WriteLine(
"Client: {0}: Sent in-game packet (0xBFx{1:X2}) before having been attached to a mobile",
state,
state.WriteConsole(
"Sent in-game packet (0xBFx{0:X2}) before having been attached to a mobile",
packetID
);
}
@ -1804,13 +1793,13 @@ namespace Server.Network
PartyCommands.Handler?.OnPrivateMessage(
state.Mobile,
World.FindMobile(reader.ReadUInt32()),
reader.ReadUnicodeStringSafe()
reader.ReadBigUniSafe()
);
}
public static void PartyMessage_PublicMessage(NetState state, PacketReader reader)
{
PartyCommands.Handler?.OnPublicMessage(state.Mobile, reader.ReadUnicodeStringSafe());
PartyCommands.Handler?.OnPublicMessage(state.Mobile, reader.ReadBigUniSafe());
}
public static void PartyMessage_SetCanLoot(NetState state, PacketReader reader)
@ -1980,7 +1969,7 @@ namespace Server.Network
public static void Language(NetState state, PacketReader reader)
{
var lang = reader.ReadString(4);
var lang = reader.ReadAscii(4);
if (state.Mobile != null)
{
@ -1991,12 +1980,12 @@ namespace Server.Network
public static void AssistVersion(NetState state, PacketReader reader)
{
var unk = reader.ReadInt32();
var av = reader.ReadString();
var av = reader.ReadAscii();
}
public static void ClientVersion(NetState state, PacketReader reader)
{
var version = state.Version = new CV(reader.ReadString());
var version = state.Version = new CV(reader.ReadAscii());
EventSink.InvokeClientVersionReceived(state, version);
}
@ -2006,7 +1995,7 @@ namespace Server.Network
reader.ReadUInt16();
int type = reader.ReadUInt16();
var version = state.Version = new CV(reader.ReadString());
var version = state.Version = new CV(reader.ReadAscii());
EventSink.InvokeClientVersionReceived(state, version);
}
@ -2046,7 +2035,7 @@ namespace Server.Network
{
reader.ReadInt32(); // 0xEDEDEDED
var name = reader.ReadString(30);
var name = reader.ReadAscii(30);
reader.Seek(2, SeekOrigin.Current);
@ -2074,7 +2063,7 @@ namespace Server.Network
if (check != null && check.Map != Map.Internal && check != m)
{
Console.WriteLine("Login: {0}: Account in use", state);
state.WriteConsole("Account in use");
state.Send(new PopupMessage(PMMessage.CharInWorld));
return;
}
@ -2217,7 +2206,7 @@ namespace Server.Network
var unk1 = reader.ReadInt32();
var unk2 = reader.ReadInt32();
int unk3 = reader.ReadByte();
var name = reader.ReadString(30);
var name = reader.ReadAscii(30);
reader.Seek(2, SeekOrigin.Current);
var flags = reader.ReadInt32();
@ -2292,7 +2281,7 @@ namespace Server.Network
if (check != null && check.Map != Map.Internal)
{
Console.WriteLine("Login: {0}: Account in use", state);
state.WriteConsole("Account in use");
state.Send(new PopupMessage(PMMessage.CharInWorld));
return;
}
@ -2353,7 +2342,7 @@ namespace Server.Network
var unk1 = reader.ReadInt32();
var unk2 = reader.ReadInt32();
int unk3 = reader.ReadByte();
var name = reader.ReadString(30);
var name = reader.ReadAscii(30);
reader.Seek(2, SeekOrigin.Current);
var flags = reader.ReadInt32();
@ -2417,7 +2406,7 @@ namespace Server.Network
if (check != null && check.Map != Map.Internal)
{
Console.WriteLine("Login: {0}: Account in use", state);
state.WriteConsole("Account in use");
state.Send(new PopupMessage(PMMessage.CharInWorld));
return;
}
@ -2530,27 +2519,27 @@ namespace Server.Network
}
else if (ClientVerification)
{
Console.WriteLine("Login: {0}: Invalid client detected, disconnecting", state);
state.WriteConsole("Invalid client detected, disconnecting");
state.Dispose();
return;
}
if (state.m_AuthID != 0 && authID != state.m_AuthID)
{
Console.WriteLine("Login: {0}: Invalid client detected, disconnecting", state);
state.WriteConsole("Invalid client detected, disconnecting");
state.Dispose();
return;
}
if (state.m_AuthID == 0 && authID != state.m_Seed)
{
Console.WriteLine("Login: {0}: Invalid client detected, disconnecting", state);
state.WriteConsole("Invalid client detected, disconnecting");
state.Dispose();
return;
}
var username = reader.ReadString(30);
var password = reader.ReadString(30);
var username = reader.ReadAscii(30);
var password = reader.ReadAscii(30);
var e = new GameLoginEventArgs(state, username, password);
@ -2559,7 +2548,7 @@ namespace Server.Network
if (e.Accepted)
{
state.CityInfo = e.CityInfo;
state.CompressionEnabled = true;
// state.CompressionEnabled = true;
state.Send(SupportedFeatures.Instantiate(state));
@ -2606,7 +2595,7 @@ namespace Server.Network
if (state.m_Seed == 0)
{
Console.WriteLine("Login: {0}: Invalid client detected, disconnecting", state);
state.WriteConsole("Invalid client detected, disconnecting");
state.Dispose();
return;
}
@ -2631,15 +2620,15 @@ namespace Server.Network
var z = reader.ReadSByte();
var map = reader.ReadByte();
var account = reader.ReadString(32);
var character = reader.ReadString(32);
var ip = reader.ReadString(15);
var account = reader.ReadAscii(32);
var character = reader.ReadAscii(32);
var ip = reader.ReadAscii(15);
var unk1 = reader.ReadInt32();
var exception = reader.ReadInt32();
var process = reader.ReadString(100);
var report = reader.ReadString(100);
var process = reader.ReadAscii(100);
var report = reader.ReadAscii(100);
reader.ReadByte(); // 0x00
@ -2663,8 +2652,8 @@ namespace Server.Network
state.SentFirstPacket = true;
var username = reader.ReadString(30);
var password = reader.ReadString(30);
var username = reader.ReadAscii(30);
var password = reader.ReadAscii(30);
var e = new AccountLoginEventArgs(state, username, password);
@ -2707,7 +2696,7 @@ namespace Server.Network
state.Dispose();
}
public static void EquipMacro(NetState ns, PacketReader reader)
public static void EquipMacro(NetState state, PacketReader reader)
{
int count = reader.ReadByte();
var serialList = new List<Serial>(count);
@ -2716,10 +2705,10 @@ namespace Server.Network
serialList.Add(reader.ReadUInt32());
}
EventSink.InvokeEquipMacro(ns.Mobile, serialList);
EventSink.InvokeEquipMacro(state.Mobile, serialList);
}
public static void UnequipMacro(NetState ns, PacketReader reader)
public static void UnequipMacro(NetState state, PacketReader reader)
{
int count = reader.ReadByte();
var layers = new List<Layer>(count);
@ -2728,30 +2717,30 @@ namespace Server.Network
layers.Add((Layer)reader.ReadUInt16());
}
EventSink.InvokeUnequipMacro(ns.Mobile, layers);
EventSink.InvokeUnequipMacro(state.Mobile, layers);
}
public static void TargetedSpell(NetState ns, PacketReader reader)
public static void TargetedSpell(NetState state, PacketReader reader)
{
var spellId = (short)(reader.ReadInt16() - 1); // zero based;
EventSink.InvokeTargetedSpell(ns.Mobile, World.FindEntity(reader.ReadUInt32()), spellId);
EventSink.InvokeTargetedSpell(state.Mobile, World.FindEntity(reader.ReadUInt32()), spellId);
}
public static void TargetedSkillUse(NetState ns, PacketReader reader)
public static void TargetedSkillUse(NetState state, PacketReader reader)
{
var skillId = reader.ReadInt16();
EventSink.InvokeTargetedSkillUse(ns.Mobile, World.FindEntity(reader.ReadUInt32()), skillId);
EventSink.InvokeTargetedSkillUse(state.Mobile, World.FindEntity(reader.ReadUInt32()), skillId);
}
public static void TargetByResourceMacro(NetState ns, PacketReader reader)
public static void TargetByResourceMacro(NetState state, PacketReader reader)
{
Serial serial = reader.ReadUInt32();
if (serial.IsItem)
{
EventSink.InvokeTargetByResourceMacro(ns.Mobile, World.FindItem(serial), reader.ReadInt16());
EventSink.InvokeTargetByResourceMacro(state.Mobile, World.FindItem(serial), reader.ReadInt16());
}
}

View file

@ -1,39 +1,68 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2020 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: PacketReader.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 <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Data;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace Server.Network
{
public ref struct PacketReader
{
private SequenceReader<byte> m_Reader;
public Span<byte> First;
public Span<byte> Second;
public SequencePosition Position => m_Reader.Position;
public long Length => m_Reader.Length;
public long Consumed => m_Reader.Consumed;
public long Remaining => m_Reader.Remaining;
public int Length { get; }
public int Position { get; private set; }
public int Remaining => Length - Position;
public PacketReader(ReadOnlySequence<byte> seq) => m_Reader = new SequenceReader<byte>(seq);
public PacketReader(ArraySegment<byte>[] buffers)
{
First = buffers[0];
Second = buffers[1];
Position = 0;
Length = First.Length + Second.Length;
}
public byte Peek() => m_Reader.TryPeek(out var value) ? value : (byte)0;
public PacketReader(Span<byte> first, Span<byte> second)
{
First = first;
Second = second;
Position = 0;
Length = first.Length + second.Length;
}
public void Trace(NetState state)
{
// We don't have data, so nothing to trace
if (First.Length == 0)
{
return;
}
try
{
using var sw = new StreamWriter("Packets.log", true);
var buffer = m_Reader.Sequence.ToArray();
if (buffer.Length > 0)
{
sw.WriteLine("Client: {0}: Unhandled packet 0x{1:X2}", state, buffer[0]);
}
sw.WriteLine("Client: {0}: Unhandled packet 0x{1:X2}", state, First[0]);
using (var ms = new MemoryStream(buffer))
{
Utility.FormatBuffer(sw, ms, buffer.Length);
}
Utility.FormatBuffer(sw, First.ToArray(), new Memory<byte>(Second.ToArray()));
sw.WriteLine();
sw.WriteLine();
@ -44,319 +73,279 @@ namespace Server.Network
}
}
public SequencePosition Seek(long offset, SeekOrigin origin)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte ReadByte()
{
switch (origin)
if (Position < First.Length)
{
case SeekOrigin.Begin:
if (offset < m_Reader.Consumed)
{
m_Reader.Rewind(m_Reader.Consumed - Math.Max(offset, 0L));
}
else
{
m_Reader.Advance(offset - m_Reader.Consumed);
}
break;
case SeekOrigin.Current:
if (offset < 0)
{
m_Reader.Rewind(Math.Min(m_Reader.Consumed, offset * -1));
}
else
{
m_Reader.Advance(Math.Min(m_Reader.Remaining, offset));
}
break;
case SeekOrigin.End:
var count = m_Reader.Remaining - offset;
if (count < 0)
{
m_Reader.Rewind(count * -1);
}
else if (count > 0)
{
m_Reader.Advance(count);
}
break;
return First[Position++];
}
return m_Reader.Position;
if (Position < Length)
{
return Second[Position++ - First.Length];
}
throw new OutOfMemoryException();
}
public bool TryReadByte(out byte value) => m_Reader.TryRead(out value);
public int ReadInt32() => m_Reader.TryReadBigEndian(out int value) ? value : 0;
public short ReadInt16() => m_Reader.TryReadBigEndian(out short value) ? value : (short)0;
public byte ReadByte() => m_Reader.TryRead(out var value) ? value : (byte)0;
public uint ReadUInt32() => (uint)ReadInt32();
public ushort ReadUInt16() => (ushort)ReadInt16();
public sbyte ReadSByte() => (sbyte)ReadByte();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool ReadBoolean() => ReadByte() > 0;
public string ReadUnicodeStringLE()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public sbyte ReadSByte() => (sbyte)ReadByte();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public short ReadInt16()
{
var sb = new StringBuilder();
short value;
while (m_Reader.TryReadLittleEndian(out short c) && c != 0)
if (Position < First.Length)
{
sb.Append((char)c);
}
return sb.ToString();
}
public string ReadUnicodeStringLE(int fixedLength)
{
var sb = new StringBuilder();
while (fixedLength-- > 0 && m_Reader.TryReadLittleEndian(out short c) && c != 0)
{
sb.Append((char)c);
}
if (fixedLength > 0)
{
m_Reader.Advance(fixedLength);
}
return sb.ToString();
}
public string ReadUnicodeStringLESafe(int fixedLength)
{
var sb = new StringBuilder();
while (fixedLength-- > 0 && m_Reader.TryReadLittleEndian(out short c) && c != 0)
{
if (IsSafeChar(c))
if (!BinaryPrimitives.TryReadInt16BigEndian(First.Slice(Position), out value))
{
sb.Append((char)c);
// Not enough space. Split the spans
return (short)((ReadByte() >> 8) | ReadByte());
}
}
if (fixedLength > 0)
else if (!BinaryPrimitives.TryReadInt16BigEndian(Second.Slice(Position - First.Length), out value))
{
m_Reader.Advance(fixedLength * 2);
throw new OutOfMemoryException();
}
return sb.ToString();
Position += 2;
return value;
}
public string ReadUnicodeStringLESafe()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ushort ReadUInt16()
{
var sb = new StringBuilder();
ushort value;
while (m_Reader.TryReadLittleEndian(out short c) && c != 0)
if (Position < First.Length)
{
if (IsSafeChar(c))
if (!BinaryPrimitives.TryReadUInt16BigEndian(First.Slice(Position), out value))
{
sb.Append((char)c);
// Not enough space. Split the spans
return (ushort)((ReadByte() >> 8) | ReadByte());
}
}
else if (!BinaryPrimitives.TryReadUInt16BigEndian(Second.Slice(Position - First.Length), out value))
{
throw new OutOfMemoryException();
}
return sb.ToString();
Position += 2;
return value;
}
public string ReadUnicodeStringSafe()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReadInt32()
{
var sb = new StringBuilder();
int value;
while (m_Reader.TryReadBigEndian(out short c) && c != 0)
if (Position < First.Length)
{
if (IsSafeChar(c))
if (!BinaryPrimitives.TryReadInt32BigEndian(First.Slice(Position), out value))
{
sb.Append((char)c);
// Not enough space. Split the spans
return (ReadByte() >> 24) | (ReadByte() >> 16) | (ReadByte() >> 8) | ReadByte();
}
}
return sb.ToString();
}
public string ReadUnicodeString()
{
var sb = new StringBuilder();
while (m_Reader.TryReadBigEndian(out short c) && c != 0)
else if (!BinaryPrimitives.TryReadInt32BigEndian(Second.Slice(Position - First.Length), out value))
{
sb.Append((char)c);
throw new OutOfMemoryException();
}
return sb.ToString();
Position += 4;
return value;
}
private static bool IsSafeChar(int c) => c >= 0x20 && c < 0xFFFE;
public string ReadUTF8StringSafe(int fixedLength)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public uint ReadUInt32()
{
string s;
uint value;
if (m_Reader.TryReadTo(out ReadOnlySpan<byte> span, (byte)'\0'))
if (Position < First.Length)
{
s = Utility.UTF8.GetString(span.Length > fixedLength ? span.Slice(0, fixedLength) : span);
if (!BinaryPrimitives.TryReadUInt32BigEndian(First.Slice(Position), out value))
{
// Not enough space. Split the spans
return (uint)((ReadByte() >> 24) | (ReadByte() >> 16) | (ReadByte() >> 8) | ReadByte());
}
}
else if (!BinaryPrimitives.TryReadUInt32BigEndian(Second.Slice(Position - First.Length), out value))
{
throw new OutOfMemoryException();
}
Position += 4;
return value;
}
private static bool IsSafeChar(ushort c) => c >= 0x20 && c < 0xFFFE;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadString<T>(Encoding encoding, bool safeString = false, int fixedLength = -1) where T : struct, IEquatable<T>
{
int sizeT = Unsafe.SizeOf<T>();
if (sizeT > 2)
{
throw new InvalidConstraintException("ReadString only accepts byte, sbyte, char, short, and ushort as a constraint");
}
bool isFixedLength = fixedLength > -1;
var remaining = Remaining;
int size;
// Not fixed length
if (isFixedLength)
{
size = fixedLength * sizeT;
if (fixedLength > Remaining)
{
throw new OutOfMemoryException();
}
}
else
{
var size = Math.Min(m_Reader.Remaining, fixedLength);
s = Utility.UTF8.GetString(m_Reader.Sequence.Slice(m_Reader.Position, size).ToArray());
m_Reader.Advance(size);
size = remaining - (remaining & (sizeT - 1));
}
var sb = new StringBuilder(s.Length);
Span<byte> span;
int index;
for (var i = 0; i < s.Length; ++i)
if (Position < First.Length)
{
if (IsSafeChar(s[i]))
// Find terminator
index = MemoryMarshal
.Cast<byte, T>(First.Slice(Position, Math.Min(size, First.Length)))
.IndexOf(default(T)) * sizeT;
if (index < 0)
{
sb.Append(s[i]);
remaining = size - First.Length;
// We don't have a terminator, but a fixed size to the end of the first span, so stop there
if (remaining <= 0)
{
index = First.Length;
}
else
{
index = MemoryMarshal
.Cast<byte, T>(Second.Slice(0, remaining))
.IndexOf(default(T)) * sizeT;
int secondLength = index < 0 ? remaining : index;
int length = First.Length + secondLength;
// Assume no strings should be too long for the stack
Span<byte> bytes = stackalloc byte[length];
First.Slice(Position).CopyTo(bytes);
Second.Slice(0, secondLength).CopyTo(bytes.Slice(First.Length));
Position += length;
return GetString(bytes, encoding, safeString);
}
}
}
return sb.ToString();
}
public string ReadUTF8StringSafe()
{
string s;
if (m_Reader.TryReadTo(out ReadOnlySpan<byte> span, (byte)'\0'))
{
s = Utility.UTF8.GetString(span);
span = First.Slice(Position, index);
}
else
{
s = Utility.UTF8.GetString(m_Reader.Sequence.Slice(m_Reader.Position, m_Reader.Remaining).ToArray());
m_Reader.Advance(m_Reader.Remaining);
}
span = Second.Slice( Position - First.Length, Math.Min(remaining, size));
index = MemoryMarshal.Cast<byte, T>(span).IndexOf(default(T)) * sizeT;
var sb = new StringBuilder(s.Length);
for (var i = 0; i < s.Length; ++i)
{
if (IsSafeChar(s[i]))
if (index >= 0)
{
sb.Append(s[i]);
span = span.Slice(0, index);
}
}
return sb.ToString();
Position += isFixedLength ? size : index;
return GetString(span, encoding, safeString);
}
public string ReadUTF8String() =>
Utility.UTF8.GetString(
m_Reader.TryReadTo(out ReadOnlySpan<byte> span, (byte)'\0')
? span
: m_Reader.Sequence.Slice(m_Reader.Position, m_Reader.Remaining).ToArray()
);
public string ReadString()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static string GetString(Span<byte> span, Encoding encoding, bool safeString = false)
{
var sb = new StringBuilder();
string s = encoding.GetString(span);
while (m_Reader.TryRead(out var c))
if (!safeString)
{
sb.Append((char)c);
return s;
}
return sb.ToString();
}
ReadOnlySpan<char> chars = s.AsSpan();
public string ReadStringSafe()
{
var sb = new StringBuilder();
StringBuilder stringBuilder = null;
while (m_Reader.TryRead(out var c))
for (int i = 0, last = 0; i < chars.Length; i++)
{
if (IsSafeChar(c))
if (!IsSafeChar(chars[i]) || stringBuilder != null && i == chars.Length - 1)
{
sb.Append((char)c);
(stringBuilder ??= new StringBuilder()).Append(chars.Slice(last, i - last));
last = i + 1; // Skip the unsafe char
}
}
return sb.ToString();
return stringBuilder?.ToString() ?? s;
}
public string ReadUnicodeStringSafe(int fixedLength)
{
var sb = new StringBuilder();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadLittleUniSafe(int fixedLength) => ReadString<char>(Utility.UnicodeLE, true, fixedLength);
while (fixedLength-- > 0 && m_Reader.TryReadBigEndian(out short c) && c != 0)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadLittleUniSafe() => ReadString<char>(Utility.UnicodeLE, true);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadLittleUni(int fixedLength) => ReadString<char>(Utility.UnicodeLE, false, fixedLength);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadLittleUni() => ReadString<char>(Utility.UnicodeLE);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadBigUniSafe(int fixedLength) => ReadString<char>(Utility.Unicode, true, fixedLength);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadBigUniSafe() => ReadString<char>(Utility.Unicode, true);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadBigUni(int fixedLength) => ReadString<char>(Utility.Unicode, false, fixedLength);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadBigUni() => ReadString<char>(Utility.Unicode);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadUTF8Safe(int fixedLength) => ReadString<byte>(Utility.UTF8, true, fixedLength);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadUTF8Safe() => ReadString<byte>(Utility.UTF8, true);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadUTF8() => ReadString<byte>(Utility.UTF8);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadAsciiSafe(int fixedLength) => ReadString<byte>(Encoding.ASCII, true, fixedLength);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadAsciiSafe() => ReadString<byte>(Encoding.ASCII, true);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadAscii(int fixedLength) => ReadString<byte>(Encoding.ASCII, false, fixedLength);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadAscii() => ReadString<byte>(Encoding.ASCII);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int Seek(int offset, SeekOrigin origin) =>
Position = origin switch
{
if (IsSafeChar(c))
{
sb.Append((char)c);
}
}
if (fixedLength > 0)
{
m_Reader.Advance(fixedLength * 2);
}
return sb.ToString();
}
public string ReadUnicodeString(int fixedLength)
{
var sb = new StringBuilder();
while (fixedLength-- > 0 && m_Reader.TryReadBigEndian(out short c) && c != 0)
{
sb.Append((char)c);
}
if (fixedLength > 0)
{
m_Reader.Advance(fixedLength * 2);
}
return sb.ToString();
}
public string ReadStringSafe(int fixedLength)
{
var sb = new StringBuilder();
while (fixedLength-- > 0 && m_Reader.TryRead(out var c) && c != 0)
{
if (IsSafeChar(c))
{
sb.Append((char)c);
}
}
if (fixedLength > 0)
{
m_Reader.Advance(fixedLength);
}
return sb.ToString();
}
public string ReadString(int fixedLength)
{
var sb = new StringBuilder();
while (fixedLength-- > 0 && m_Reader.TryRead(out var c) && c != 0)
{
sb.Append((char)c);
}
if (fixedLength > 0)
{
m_Reader.Advance(fixedLength);
}
return sb.ToString();
}
SeekOrigin.Begin => offset,
SeekOrigin.End => Length - offset,
_ => Position + offset // Current
};
}
}

View file

@ -32,8 +32,7 @@ namespace Server.Network
private static readonly MessageLocalized[] m_Cache_CliLocCmp = new MessageLocalized[5000];
public MessageLocalized(
Serial serial, int graphic, MessageType type, int hue, int font, int number, string name,
string args
Serial serial, int graphic, MessageType type, int hue, int font, int number, string name, string args
) : base(0xC1)
{
name ??= "";

View file

@ -0,0 +1,356 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2020 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: Pipe.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 <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Runtime.CompilerServices;
using System.Threading;
namespace Server.Network
{
public interface IPipeTask<T> : INotifyCompletion
{
public IPipeTask<T> GetAwaiter();
public bool IsCompleted { get; }
public T GetResult();
public void OnCompleted(Action continuation);
}
public class Pipe<T>
{
public struct Result<T>
{
public ArraySegment<T>[] Buffer { get; }
public bool IsClosed { get; set; }
public int Length
{
get
{
var length = 0;
for (int i = 0; i < Buffer.Length; i++)
{
length += Buffer[i].Count;
}
return length;
}
}
public void CopyFrom(ReadOnlySpan<T> bytes)
{
var remaining = bytes.Length;
var offset = 0;
if (remaining == 0)
{
return;
}
for (int i = 0; i < Buffer.Length; i++)
{
var buffer = Buffer[i];
var sz = Math.Min(remaining, buffer.Count);
bytes.Slice(offset, sz).CopyTo(buffer);
remaining -= sz;
offset += sz;
if (remaining == 0)
{
return;
}
}
throw new OutOfMemoryException();
}
public Result(int segments)
{
IsClosed = false;
Buffer = new ArraySegment<T>[segments];
}
}
public class PipeWriter<T>
{
private readonly Pipe<T> _pipe;
public PipeWriter(Pipe<T> pipe) => _pipe = pipe;
public Result<T> GetAvailable()
{
var read = _pipe._readIdx;
var write = _pipe._writeIdx;
var result = new Result<T>(2) { IsClosed = _pipe._closed };
if (read <= write)
{
var readZero = read == 0;
var sz = _pipe.Size - write - (readZero ? 1 : 0);
result.Buffer[0] = sz == 0 ? ArraySegment<T>.Empty : new ArraySegment<T>(_pipe._buffer, (int)write, (int)sz);
result.Buffer[1] = readZero ? ArraySegment<T>.Empty : new ArraySegment<T>(_pipe._buffer, 0, (int)read - 1);
}
else
{
var sz = read - write - 1;
result.Buffer[0] = sz == 0 ? ArraySegment<T>.Empty : new ArraySegment<T>(_pipe._buffer, (int)write, (int)sz);
result.Buffer[1] = ArraySegment<T>.Empty;
}
return result;
}
public void Advance(uint bytes)
{
var read = _pipe._readIdx;
var write = _pipe._writeIdx;
if (bytes == 0)
{
return;
}
if (bytes > _pipe.Size - 1)
{
throw new InvalidOperationException();
}
if (read <= write)
{
if (bytes > read + _pipe.Size - write - 1)
{
throw new InvalidOperationException();
}
var sz = Math.Min(bytes, _pipe.Size - write);
write += sz;
if (write > _pipe.Size - 1)
{
write = 0;
}
bytes -= sz;
if (bytes > 0)
{
if (bytes >= read)
{
throw new InvalidOperationException();
}
write = bytes;
}
}
else
{
if (bytes > read - write - 1)
{
throw new InvalidOperationException();
}
write += bytes;
}
// It's never valid to advance the write pointer to become equal to
// the read pointer. Check that here.
if (write == read)
{
throw new InvalidOperationException("Write index equals read index after advance");
}
_pipe._writeIdx = write;
}
public void Close()
{
_pipe._closed = true;
Flush();
}
public void Flush()
{
var waiting = _pipe._awaitBeginning;
if (!waiting)
{
return;
}
Action continuation;
do
{
continuation = _pipe._readerContinuation;
} while (continuation == null);
_pipe._readerContinuation = null;
_pipe._awaitBeginning = false;
ThreadPool.UnsafeQueueUserWorkItem(state => continuation(), true);
}
}
public class PipeReader<T> : IPipeTask<Result<T>>
{
private readonly Pipe<T> _pipe;
internal PipeReader(Pipe<T> pipe) => _pipe = pipe;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public uint GetRemaining()
{
var read = _pipe._readIdx;
var write = _pipe._writeIdx;
if (read <= write)
{
return write - read;
}
return write + _pipe.Size - read;
}
public Result<T> TryRead()
{
var read = _pipe._readIdx;
var write = _pipe._writeIdx;
var result = new Result<T>(2) { IsClosed = _pipe._closed };
if (read <= write)
{
result.Buffer[0] = write - read == 0 ? ArraySegment<T>.Empty : new ArraySegment<T>(_pipe._buffer, (int)read, (int)(write - read));
result.Buffer[1] = ArraySegment<T>.Empty;
}
else
{
result.Buffer[0] = _pipe.Size - read == 0 ? ArraySegment<T>.Empty : new ArraySegment<T>(_pipe._buffer, (int)read, (int)(_pipe.Size - read));
result.Buffer[1] = write == 0 ? ArraySegment<T>.Empty : new ArraySegment<T>(_pipe._buffer, 0, (int)write);
}
return result;
}
public IPipeTask<Result<T>> Read()
{
if (_pipe._awaitBeginning)
{
throw new Exception("Double await on reader");
}
return this;
}
public void Advance(uint bytes)
{
var read = _pipe._readIdx;
var write = _pipe._writeIdx;
if (read <= write)
{
if (bytes > write - read)
{
throw new InvalidOperationException();
}
read += bytes;
}
else
{
var sz = Math.Min(bytes, _pipe.Size - read);
read += sz;
if (read > _pipe.Size - 1)
{
read = 0;
}
bytes -= sz;
if (bytes > 0)
{
if (bytes > write)
{
throw new InvalidOperationException();
}
read = bytes;
}
}
_pipe._readIdx = read;
}
#region Awaitable
// The following makes it possible to await the reader. Do not use any of this directly.
public IPipeTask<Result<T>> GetAwaiter() => this;
public bool IsCompleted
{
get
{
if (GetRemaining() > 0)
{
return true;
}
_pipe._awaitBeginning = true;
return false;
}
}
public Result<T> GetResult() => TryRead();
public void OnCompleted(Action continuation) => _pipe._readerContinuation = continuation;
#endregion
}
private readonly T[] _buffer;
private volatile uint _writeIdx;
private volatile uint _readIdx;
private bool _closed;
public PipeWriter<T> Writer { get; }
public PipeReader<T> Reader { get; }
public uint Size => (uint)_buffer.Length;
public Pipe(T[] buf)
{
_buffer = buf;
_writeIdx = 0;
_readIdx = 0;
_closed = false;
Writer = new PipeWriter<T>(this);
Reader = new PipeReader<T>(this);
}
#region Awaitable
private volatile bool _awaitBeginning;
private volatile Action _readerContinuation;
#endregion
}
}

View file

@ -1,91 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2020 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: ServerConnectionHandler.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 <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.Extensions.Logging;
namespace Server.Network
{
public class ServerConnectionHandler : ConnectionHandler
{
private readonly ILogger<ServerConnectionHandler> _logger;
private readonly IMessagePumpService _messagePumpService;
public ServerConnectionHandler(
IMessagePumpService messagePumpService,
ILogger<ServerConnectionHandler> logger
)
{
_messagePumpService = messagePumpService;
_logger = logger;
}
public override async Task OnConnectedAsync(ConnectionContext connection)
{
if (!VerifySocket(connection))
{
Release(connection);
return;
}
var ns = new NetState(connection);
TcpServer.Instances.Add(ns);
Console.WriteLine($"Client: {ns}: Connected. [{TcpServer.Instances.Count} Online]");
await ns.ProcessIncoming(_messagePumpService).ConfigureAwait(false);
}
private static bool VerifySocket(ConnectionContext connection)
{
try
{
var args = new SocketConnectEventArgs(connection);
EventSink.InvokeSocketConnect(args);
return args.AllowConnection;
}
catch (Exception ex)
{
NetState.TraceException(ex);
return false;
}
}
private static void Release(ConnectionContext connection)
{
try
{
connection.Abort(new ConnectionAbortedException("Failed socket verification."));
}
catch (Exception ex)
{
NetState.TraceException(ex);
}
try
{
// TODO: Is this needed?
connection.DisposeAsync();
}
catch (Exception ex)
{
NetState.TraceException(ex);
}
}
}
}

View file

@ -14,79 +14,211 @@
*************************************************************************/
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using System.Net.Sockets;
using Server.Exceptions;
namespace Server.Network
{
public static class TcpServer
{
private static IPAddress[] m_ListeningAddresses;
private static NetworkState m_NetworkState = NetworkState.ResumeState;
// Make this thread safe
public static List<NetState> Instances { get; } = new List<NetState>();
// Sanity. 256 * 1024 * 5000 = ~1.3GB of ram
public static int MaxConnections { get; set; } = 5000;
public static IWebHostBuilder CreateWebHostBuilder(string[] args = null) =>
WebHost.CreateDefaultBuilder(args)
.UseSetting(WebHostDefaults.SuppressStatusMessagesKey, "True")
.ConfigureServices(services => { services.AddSingleton<IMessagePumpService>(new MessagePumpService()); })
.UseKestrel(
options =>
{
foreach (var ipep in ServerConfiguration.Listeners)
{
options.Listen(ipep, builder => { builder.UseConnectionHandler<ServerConnectionHandler>(); });
m_ListeningAddresses = GetListeningAddresses(ipep);
DisplayListener(ipep);
}
private const long _listenerErrorMessageDelay = 10000; // 10 seconds
private static long _nextMaximumSocketsReachedMessage;
// Webservices here
}
)
.UseLibuv()
.UseStartup<ServerStartup>();
// AccountLoginReject BadComm
private static readonly byte[] socketRejected = { 0x82, 0xFF };
public static IPAddress[] GetListeningAddresses(IPEndPoint ipep)
public static IPEndPoint[] ListeningAddresses { get; private set; }
public static TcpListener[] Listeners { get; private set; }
public static List<NetState> Instances { get; } = new List<NetState>(128);
public static ConcurrentQueue<NetState> m_ConnectedQueue = new ConcurrentQueue<NetState>();
public static void Configure()
{
if (m_ListeningAddresses != null)
{
return m_ListeningAddresses;
}
var list = new List<IPAddress>();
foreach (var adapter in NetworkInterface.GetAllNetworkInterfaces())
{
var properties = adapter.GetIPProperties();
foreach (var unicast in properties.UnicastAddresses)
{
if (ipep.AddressFamily == unicast.Address.AddressFamily)
{
list.Add(unicast.Address);
}
}
}
return list.ToArray();
MaxConnections = ServerConfiguration.GetOrUpdateSetting("tcpServer.maxConnections", MaxConnections);
}
private static void DisplayListener(IPEndPoint ipep)
public static void Start()
{
if (ipep.Address.Equals(IPAddress.Any) || ipep.Address.Equals(IPAddress.IPv6Any))
HashSet<IPEndPoint> listeningAddresses = new HashSet<IPEndPoint>();
List<TcpListener> listeners = new List<TcpListener>();
foreach (var ipep in ServerConfiguration.Listeners)
{
foreach (var ip in m_ListeningAddresses)
var listener = CreateListener(ipep);
if (listener == null)
{
Console.WriteLine("Listening: {0}:{1}", ip, ipep.Port);
continue;
}
if (ipep.Address.Equals(IPAddress.Any) || ipep.Address.Equals(IPAddress.IPv6Any))
{
listeningAddresses.UnionWith(GetListeningAddresses(ipep));
}
else
{
listeningAddresses.Add(ipep);
}
listeners.Add(listener);
listener.BeginAcceptingSockets();
}
else
foreach (var ipep in listeningAddresses)
{
Console.WriteLine("Listening: {0}:{1}", ipep.Address, ipep.Port);
}
ListeningAddresses = listeningAddresses.ToArray();
Listeners = listeners.ToArray();
}
public static IEnumerable<IPEndPoint> GetListeningAddresses(IPEndPoint ipep) =>
NetworkInterface.GetAllNetworkInterfaces().SelectMany(adapter =>
adapter.GetIPProperties().UnicastAddresses
.Where(uip => ipep.AddressFamily == uip.Address.AddressFamily)
.Select(uip => new IPEndPoint(uip.Address, ipep.Port))
);
public static TcpListener CreateListener(IPEndPoint ipep)
{
var listener = new TcpListener(ipep);
listener.Server.ExclusiveAddressUse = false;
try
{
listener.Start(8);
return listener;
}
catch (SocketException se)
{
// WSAEADDRINUSE
if (se.ErrorCode == 10048)
{
Console.WriteLine("Listener: {0}:{1}: Failed (In Use)", ipep.Address, ipep.Port);
}
// WSAEADDRNOTAVAIL
else if (se.ErrorCode == 10049)
{
Console.WriteLine("Listener {0}:{1}: Failed (Unavailable)", ipep.Address, ipep.Port);
}
else
{
Console.WriteLine("Listener Exception:");
Console.WriteLine(se);
}
}
return null;
}
/**
* Pauses the TcpServer and stops accepting new sockets.
* This is thread-safe without using locks.
*/
public static void Pause()
{
NetworkState.Pause(ref m_NetworkState);
}
/**
* Resumes accepting sockets on the TcpServer.
* This is thread-safe using a lock on the listeners
*/
public static void Resume()
{
if (!NetworkState.Resume(ref m_NetworkState))
{
return;
}
lock (Listeners)
{
foreach (var listener in Listeners)
{
listener.BeginAcceptingSockets();
}
}
}
public static void Slice()
{
int count = 0;
while (count++ < 250)
{
if (!m_ConnectedQueue.TryDequeue(out var ns))
{
break;
}
Instances.Add(ns);
ns.WriteConsole("Connected. [{0} Online]", Instances.Count);
}
}
private static async void BeginAcceptingSockets(this TcpListener listener)
{
while (true)
{
if (m_NetworkState.Paused)
{
return;
}
Socket socket = null;
try
{
socket = await listener.AcceptSocketAsync();
if (Instances.Count >= MaxConnections)
{
socket.Send(socketRejected, SocketFlags.None);
socket.Shutdown(SocketShutdown.Both);
socket.Close();
throw new MaxConnectionsException();
}
var args = new SocketConnectEventArgs(socket);
EventSink.InvokeSocketConnect(args);
if (!args.AllowConnection)
{
socket.Send(socketRejected, SocketFlags.None);
socket.Shutdown(SocketShutdown.Both);
socket.Close();
}
}
catch (MaxConnectionsException)
{
var ticks = Core.TickCount;
if (_nextMaximumSocketsReachedMessage <= ticks)
{
var ipep = (IPEndPoint)socket!.RemoteEndPoint;
Console.WriteLine("Listener {0}:{1}: Failed (Maximum connections reached)", ipep.Address, ipep.Port);
_nextMaximumSocketsReachedMessage = ticks + _listenerErrorMessageDelay;
}
}
catch
{
// ignored
}
var ns = new NetState(socket);
m_ConnectedQueue.Enqueue(ns);
ns.Start();
}
}
}
}