diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 15d42fceb..bf1100d9d 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -42,7 +42,9 @@ jobs: build-linux: runs-on: ubuntu-latest - container: ${{ matrix.container }} + container: + image: ${{ matrix.container }} + options: --security-opt seccomp=unconfined name: Build (${{ matrix.name }}) strategy: fail-fast: false @@ -68,14 +70,17 @@ jobs: packageManager: dnf steps: - - name: Enable EPEL - run: dnf upgrade --refresh -y && dnf install -y epel-release epel-next-release - if: ${{ startsWith(matrix.name, 'CentOS') }} + - name: Enable EPEL and CRB for CentOS + run: | + dnf upgrade --refresh -y + dnf install -y epel-release epel-next-release + dnf config-manager --set-enabled crb + if: ${{ startsWith(matrix.name, 'CentOS') }} - name: Install Prerequisites using dnf - run: dnf makecache --refresh && dnf install -y findutils libicu libdeflate-devel zstd libargon2-devel + run: dnf makecache --refresh && dnf install -y findutils libicu libdeflate-devel zstd libargon2-devel liburing-devel if: ${{ matrix.packageManager == 'dnf' }} - name: Install Prerequisites using apt - run: apt-get update -y && apt-get install -y curl libicu-dev libdeflate-dev zstd libargon2-dev tzdata + run: apt-get update -y && apt-get install -y curl libicu-dev libdeflate-dev zstd libargon2-dev tzdata liburing-dev if: ${{ matrix.packageManager == 'apt' }} - uses: actions/checkout@v4 with: diff --git a/Projects/Server.Tests/Fixtures/TestServerInitializer.cs b/Projects/Server.Tests/Fixtures/TestServerInitializer.cs index b2cab3fe1..4620ec0ef 100644 --- a/Projects/Server.Tests/Fixtures/TestServerInitializer.cs +++ b/Projects/Server.Tests/Fixtures/TestServerInitializer.cs @@ -78,6 +78,9 @@ public static class TestServerInitializer Core.LoopContext = new EventLoopContext(); Core.Expansion = Expansion.EJ; + // Configure networking (initializes RingSocketManager for tests) + Server.Network.NetState.Configure(); + // Configure / Initialize TestMapDefinitions.ConfigureTestMapDefinitions(); diff --git a/Projects/Server.Tests/Helpers/PacketTestUtilities.cs b/Projects/Server.Tests/Helpers/PacketTestUtilities.cs index afcbc6423..5c6c793d4 100644 --- a/Projects/Server.Tests/Helpers/PacketTestUtilities.cs +++ b/Projects/Server.Tests/Helpers/PacketTestUtilities.cs @@ -1,16 +1,94 @@ using System; +using System.Collections.Generic; +using System.Net; using System.Net.Sockets; +using System.Network; using Server.Network; -namespace Server.Tests.Network -{ - public static class PacketTestUtilities - { - public static Span Compile(this Packet p) => - p.Compile(false, out var length).AsSpan(0, length); +namespace Server.Tests.Network; - public static NetState CreateTestNetState() => new( - new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) - ); +public static class PacketTestUtilities +{ + private static nint _testListener; + private static int _testPort; + private static readonly List _testSocketClients = []; + + public static Span Compile(this Packet p) => p.Compile(false, out var length).AsSpan(0, length); + + /// + /// Creates a NetState for unit testing. + /// Uses a real Socket and RingSocket with actual buffers. + /// Must be disposed after use (use 'using' statement). + /// + public static NetState CreateTestNetState() + { + NetState.Slice(); // Process disconnects/disposes + + for (var i = _testSocketClients.Count - 1; i >= 0; i--) + { + var sock = _testSocketClients[i]; + if (!sock.Connected) + { + sock.Dispose(); + _testSocketClients.RemoveAt(i); + } + } + + var ring = NetState.Ring; + + // Create a test listener if we don't have one (using the ring for RIO-compatible sockets) + if (_testListener == 0) + { + // Disable rate limiter for tests - we don't want connection attempts to be throttled + // NetState.DisableRateLimiter(); + + _testListener = ring.CreateListener("127.0.0.1", 0, 128); + if (_testListener == -1) + { + throw new InvalidOperationException("Failed to create test listener"); + } + + _testPort = SocketHelper.GetLocalEndPoint(_testListener)?.Port ?? 0; + if (_testPort == 0) + { + throw new InvalidOperationException("Failed to get test listener port"); + } + } + + // Queue an accept operation + ring.PrepareAccept(_testListener, 0, 0, IORingUserData.EncodeAccept()); + ring.Submit(); + + Core._now = DateTime.UtcNow; + + // Create a client socket and connect to trigger the accept + var testSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + testSocket.Connect(IPAddress.Loopback, _testPort); + _testSocketClients.Add(testSocket); + + // Slice until we have a new NetState instance added + // AcceptEx is asynchronous, so we may need to wait/retry + const int maxRetries = 100; + for (var i = 0; i < maxRetries; i++) + { + NetState.Slice(); + + // Get the latest instance connected. + foreach (var ns in NetState.Instances) + { + if (ns.ConnectedOn == Core._now) + { + return ns; + } + } + + // Wait a bit for AcceptEx to complete + if (i < maxRetries - 1) + { + System.Threading.Thread.Sleep(1); + } + } + + throw new Exception("Failed to slice for test NetState instance after retries"); } } diff --git a/Projects/Server.Tests/Server.Tests.csproj b/Projects/Server.Tests/Server.Tests.csproj index 74ca781ce..6dd0841c5 100644 --- a/Projects/Server.Tests/Server.Tests.csproj +++ b/Projects/Server.Tests/Server.Tests.csproj @@ -16,6 +16,13 @@ + + + + PreserveNewest + ioring.dll + + diff --git a/Projects/Server.Tests/Tests/Maps/ClientEnumeratorTests.cs b/Projects/Server.Tests/Tests/Maps/ClientEnumeratorTests.cs index 6ec72800f..82722e3df 100644 --- a/Projects/Server.Tests/Tests/Maps/ClientEnumeratorTests.cs +++ b/Projects/Server.Tests/Tests/Maps/ClientEnumeratorTests.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; -using System.Net.Sockets; using Server.Accounting; using Server.Network; +using Server.Tests.Network; using Xunit; namespace Server.Tests.Maps; @@ -356,7 +356,7 @@ public class ClientEnumeratorTests { var map = Map.Felucca; var center = new Point3D(900, 900, 0); - var range = 5; + const int range = 5; var clients = new (NetState, Mobile)[3]; try @@ -456,8 +456,8 @@ public class ClientEnumeratorTests private static (NetState, Mobile) CreateClientWithMobile(Map map, Point3D location) { - var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - var ns = new NetState(socket); + // Create test NetState with real socket and buffers + var ns = PacketTestUtilities.CreateTestNetState(); // Assign a mock account to avoid null reference issues ns.Account = new MockAccount(); @@ -533,16 +533,16 @@ public class ClientEnumeratorTests } } - private static void DeleteAll((NetState, Mobile)[] clients) + private static void DeleteAll((NetState state, Mobile m)[] clients) { for (var i = 0; i < clients.Length; i++) { - if (clients[i].Item1 != null) + if (clients[i].state != null) { - clients[i].Item1.Mobile = null; - clients[i].Item1.Disconnect("Test cleanup"); + clients[i].state.Mobile = null; + clients[i].state.Dispose(); } - clients[i].Item2?.Delete(); + clients[i].m?.Delete(); } } } diff --git a/Projects/Server.Tests/Tests/Maps/MapSelectionTests.cs b/Projects/Server.Tests/Tests/Maps/MapSelectionTests.cs index 793205a35..60c3904b3 100644 --- a/Projects/Server.Tests/Tests/Maps/MapSelectionTests.cs +++ b/Projects/Server.Tests/Tests/Maps/MapSelectionTests.cs @@ -4,7 +4,7 @@ using Server.Json; using System.Text.Json.Serialization; using System.Text.Json; -namespace Server.Tests.Tests.Maps +namespace Server.Tests.Maps { public class MapSelectionTests { diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/AccountPacketTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/AccountPacketTests.cs index 210875979..3f62c01bf 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/AccountPacketTests.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/AccountPacketTests.cs @@ -92,7 +92,7 @@ public class AccountPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendChangeCharacter(account); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -105,7 +105,7 @@ public class AccountPacketTests ns.SendClientVersionRequest(); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -117,7 +117,7 @@ public class AccountPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendCharacterDeleteResult(DeleteResultType.BadRequest); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -129,7 +129,7 @@ public class AccountPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendPopupMessage(PMMessage.LoginSyncError); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -156,7 +156,7 @@ public class AccountPacketTests var expected = new SupportedFeatures(ns).Compile(); ns.SendSupportedFeature(); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -177,7 +177,7 @@ public class AccountPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendLoginConfirmation(m); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -189,7 +189,7 @@ public class AccountPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendLoginComplete(); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -214,7 +214,7 @@ public class AccountPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendCharacterListUpdate(account); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -248,7 +248,7 @@ public class AccountPacketTests ns.SendCharacterList(); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -281,7 +281,7 @@ public class AccountPacketTests ns.SendCharacterList(); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -294,7 +294,7 @@ public class AccountPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendAccountLoginRejected(reason); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -313,7 +313,7 @@ public class AccountPacketTests ns.SendAccountLoginAck(); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -321,7 +321,7 @@ public class AccountPacketTests public void TestPlayServerAck() { var si = new ServerInfo("Test Server", 0, TimeZoneInfo.Local, IPEndPoint.Parse("127.0.0.1")); - var authId = 0x123456; + const int authId = 0x123456; var expected = new PlayServerAck(si, authId).Compile(); @@ -329,7 +329,7 @@ public class AccountPacketTests ns.SendPlayServerAck(si, authId); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } } diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/CombatPacketTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/CombatPacketTests.cs index 2f84cfc67..a885cae9e 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/CombatPacketTests.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/CombatPacketTests.cs @@ -17,7 +17,7 @@ public class CombatPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendSwing(attacker, defender); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -29,7 +29,7 @@ public class CombatPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendSetWarMode(warmode); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -43,7 +43,7 @@ public class CombatPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendChangeCombatant(serial); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } } diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/ContainerPacketTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/ContainerPacketTests.cs index 12b5b6cb9..e18b999f0 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/ContainerPacketTests.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/ContainerPacketTests.cs @@ -7,7 +7,6 @@ namespace Server.Tests.Network; [Collection("Sequential Server Tests")] public class ContainerPacketTests { - [Fact] public void TestContainerDisplay() { @@ -19,7 +18,7 @@ public class ContainerPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendDisplayContainer(serial, gumpId); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -35,7 +34,7 @@ public class ContainerPacketTests ns.ProtocolChanges = ns.ProtocolChanges | ProtocolChanges.ContainerGridLines | ProtocolChanges.HighSeas; ns.SendDisplayContainer(serial, gumpId); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -49,7 +48,7 @@ public class ContainerPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendDisplaySpellbook(serial); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -64,7 +63,7 @@ public class ContainerPacketTests ns.ProtocolChanges = ns.ProtocolChanges | ProtocolChanges.ContainerGridLines | ProtocolChanges.HighSeas; ns.SendDisplaySpellbook(serial); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -85,7 +84,7 @@ public class ContainerPacketTests ns.SendSpellbookContent(serial, graphic, offset, content); ObjectPropertyList.Enabled = opl; - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -102,7 +101,7 @@ public class ContainerPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendSpellbookContent(serial, graphic, offset, content); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -120,7 +119,7 @@ public class ContainerPacketTests ns.ProtocolChanges |= ProtocolChanges.ContainerGridLines; ns.SendSpellbookContent(serial, graphic, offset, content); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -135,7 +134,7 @@ public class ContainerPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendContainerContentUpdate(item); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -151,7 +150,7 @@ public class ContainerPacketTests ns.ProtocolChanges |= ProtocolChanges.ContainerGridLines; ns.SendContainerContentUpdate(item); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -172,7 +171,7 @@ public class ContainerPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendContainerContent(m, cont); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -194,7 +193,7 @@ public class ContainerPacketTests ns.ProtocolChanges |= ProtocolChanges.ContainerGridLines; ns.SendContainerContent(m, cont); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } } diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/DamagePacketTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/DamagePacketTests.cs index 628f1f639..5f31a49d4 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/DamagePacketTests.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/DamagePacketTests.cs @@ -16,7 +16,7 @@ public class DamagePacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendDamage(serial, inputAmount); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -32,7 +32,7 @@ public class DamagePacketTests ns.SendDamage(serial, inputAmount); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } } diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/EffectPacketTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/EffectPacketTests.cs index 0c5eeaf20..7ade294f6 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/EffectPacketTests.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/EffectPacketTests.cs @@ -17,7 +17,7 @@ public class EffectPackets using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendSoundEffect(soundID, p); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -99,7 +99,7 @@ public class EffectPackets using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendScreenEffect(screenType); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/EquipmentPacketTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/EquipmentPacketTests.cs index bd43aaeeb..8dc535e03 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/EquipmentPacketTests.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/EquipmentPacketTests.cs @@ -42,7 +42,7 @@ public class EquipmentPacketTests new List(info.Attributes) ); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -59,7 +59,7 @@ public class EquipmentPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendEquipUpdate(item); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } } diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/GumpPacketTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/GumpPacketTests.cs index bb817f6a0..edbe6b668 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/GumpPacketTests.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/GumpPacketTests.cs @@ -15,7 +15,7 @@ public class GumpPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendCloseGump(typeId, buttonId); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -32,7 +32,7 @@ public class GumpPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendDisplaySignGump(gumpSerial, gumpId, unknownString, caption); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -46,7 +46,7 @@ public class GumpPacketTests var expected = gump.Compile(ns).Compile(); ns.SendGump(gump); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -65,7 +65,7 @@ public class GumpPacketTests var expected = gump.Compile(ns).Compile(); ns.SendGump(gump); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } } diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/ItemPacketTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/ItemPacketTests.cs index 431b1639d..27bad8aaf 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/ItemPacketTests.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/ItemPacketTests.cs @@ -38,7 +38,7 @@ public class ItemPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendWorldItem(item); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -74,7 +74,7 @@ public class ItemPacketTests ns.ProtocolChanges = ProtocolChanges.StygianAbyss; ns.SendWorldItem(item); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -110,7 +110,7 @@ public class ItemPacketTests ns.ProtocolChanges = ProtocolChanges.StygianAbyss | ProtocolChanges.HighSeas; ns.SendWorldItem(item); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } } diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/LightPacketTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/LightPacketTests.cs index 3057eb9f9..c66a61ca7 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/LightPacketTests.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/LightPacketTests.cs @@ -15,7 +15,7 @@ public class LightPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendGlobalLightLevel(lightLevel); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -29,7 +29,7 @@ public class LightPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendPersonalLightLevel(serial, lightLevel); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } } diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MapPacketTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MapPacketTests.cs index aa2752607..a438730e0 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MapPacketTests.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MapPacketTests.cs @@ -15,7 +15,7 @@ public class MapPatchesTests ns.SendMapPatches(); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -27,7 +27,7 @@ public class MapPatchesTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendInvalidMap(); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -42,7 +42,7 @@ public class MapPatchesTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendMapChange(map); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } } diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MenuPacketTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MenuPacketTests.cs index ca30d368b..3da55c89e 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MenuPacketTests.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MenuPacketTests.cs @@ -47,7 +47,7 @@ public class MenuPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendDisplayItemListMenu(menu); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -67,7 +67,7 @@ public class MenuPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendDisplayQuestionMenu(menu); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -95,7 +95,7 @@ public class MenuPacketTests ns.SendDisplayContextMenu(menu); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } } diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MessagePacketTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MessagePacketTests.cs index 63b026550..cf9e83ca8 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MessagePacketTests.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MessagePacketTests.cs @@ -42,7 +42,7 @@ public class MessageTests args ); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -87,7 +87,7 @@ public class MessageTests args ); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -125,7 +125,7 @@ public class MessageTests text ); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -165,7 +165,7 @@ public class MessageTests text ); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -180,7 +180,7 @@ public class MessageTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendFollowMessage(serial, serial2); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -195,7 +195,7 @@ public class MessageTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendHelpResponse(s, text); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -215,9 +215,8 @@ public class MessageTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendPrompt(prompt); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); - } } } diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MobilePacketTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MobilePacketTests.cs index 21671ad10..d7d3beca2 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MobilePacketTests.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MobilePacketTests.cs @@ -17,7 +17,7 @@ public class MobilePacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendDeathAnimation(killed, corpse); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -32,7 +32,7 @@ public class MobilePacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendBondedStatus(petSerial, bonded); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -52,7 +52,7 @@ public class MobilePacketTests ns.SendMobileMoving(m, noto); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -70,7 +70,7 @@ public class MobilePacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendMobileName(m); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -102,7 +102,7 @@ public class MobilePacketTests delay ); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -128,7 +128,7 @@ public class MobilePacketTests delay ); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -148,7 +148,7 @@ public class MobilePacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendMobileHealthbar(m, Healthbar.Poison); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -169,7 +169,7 @@ public class MobilePacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendMobileHealthbar(m, Healthbar.Yellow); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -192,7 +192,7 @@ public class MobilePacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendMobileStatusCompact(m, canBeRenamed); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -225,7 +225,7 @@ public class MobilePacketTests var expected = new MobileStatus(beholder, beheld, ns).Compile(); ns.SendMobileStatus(beholder, beheld); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -263,7 +263,7 @@ public class MobilePacketTests var expected = new MobileStatusExtended(m, ns).Compile(); ns.SendMobileStatus(m, m); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); Core.Expansion = oldExpansion; expansionInfo.MobileStatusVersion = oldVersion; @@ -284,7 +284,7 @@ public class MobilePacketTests var expected = new MobileUpdate(m, ns.StygianAbyss).Compile(); ns.SendMobileUpdate(m); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -339,7 +339,7 @@ public class MobilePacketTests var expected = new MobileIncoming(ns, beholder, beheld).Compile(); ns.SendMobileIncoming(beholder, beheld); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -356,7 +356,7 @@ public class MobilePacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendMobileHits(m); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -373,7 +373,7 @@ public class MobilePacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendMobileHits(m, true); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -390,7 +390,7 @@ public class MobilePacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendMobileMana(m); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -407,7 +407,7 @@ public class MobilePacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendMobileMana(m, true); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -424,7 +424,7 @@ public class MobilePacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendMobileStam(m); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -441,7 +441,7 @@ public class MobilePacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendMobileStam(m, true); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -462,7 +462,7 @@ public class MobilePacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendMobileAttributes(m); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -483,7 +483,7 @@ public class MobilePacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendMobileAttributes(m, true); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -496,7 +496,7 @@ public class MobilePacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendRemoveEntity(e); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } } diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MovementPacketTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MovementPacketTests.cs index 989dbd47f..330eaf0b5 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MovementPacketTests.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MovementPacketTests.cs @@ -17,7 +17,7 @@ public class MovementPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendSpeedControl((SpeedControlSetting)speedControl); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -30,7 +30,7 @@ public class MovementPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendMovePlayer(d); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -47,7 +47,7 @@ public class MovementPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendMovementRej(seq, m); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -64,7 +64,7 @@ public class MovementPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendMovementAck(seq, m); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } } diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/PlayerPacketTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/PlayerPacketTests.cs index 83039a56b..6d6e5daa3 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/PlayerPacketTests.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/PlayerPacketTests.cs @@ -23,7 +23,7 @@ public class PlayerPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendStatLockInfo(m); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -38,7 +38,7 @@ public class PlayerPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendChangeUpdateRange((byte)range); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -50,7 +50,7 @@ public class PlayerPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendDeathStatus(); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -64,7 +64,7 @@ public class PlayerPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendDisplayProfile((Serial)serial, header, body, footer); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -78,7 +78,7 @@ public class PlayerPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendLiftReject(reason); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -90,7 +90,7 @@ public class PlayerPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendLogoutAck(); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -105,7 +105,7 @@ public class PlayerPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendWeather((byte)type, (byte)density, (byte)temp); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -120,7 +120,7 @@ public class PlayerPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendServerChange(p, map); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -135,7 +135,7 @@ public class PlayerPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendSequence(num); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -151,7 +151,7 @@ public class PlayerPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendLaunchBrowser(uri); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -175,7 +175,7 @@ public class PlayerPacketTests itemId, hue, amount ); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -189,7 +189,7 @@ public class PlayerPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendSeasonChange((byte)season, playSound); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -204,7 +204,7 @@ public class PlayerPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendDisplayPaperdoll((Serial)m, title, warmode, canLift); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -219,7 +219,7 @@ public class PlayerPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendPlayMusic(music); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -233,7 +233,7 @@ public class PlayerPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendScrollMessage(type, tip ,text); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -247,7 +247,7 @@ public class PlayerPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendCurrentTime(date); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -261,7 +261,7 @@ public class PlayerPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendPathfindMessage(p); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -276,7 +276,7 @@ public class PlayerPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendPingAck(ping); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -291,7 +291,7 @@ public class PlayerPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendDisplayHuePicker(huePicker.Serial, huePicker.ItemID); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } } diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/SecureTradePacketTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/SecureTradePacketTests.cs index b4e786275..6c10b63e9 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/SecureTradePacketTests.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/SecureTradePacketTests.cs @@ -23,7 +23,7 @@ public class SecureTradePacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendDisplaySecureTrade(m, firstCont, secondCont, name); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -37,7 +37,7 @@ public class SecureTradePacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendCloseSecureTrade(cont); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -55,7 +55,7 @@ public class SecureTradePacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendUpdateSecureTrade(cont, first, second); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -70,7 +70,7 @@ public class SecureTradePacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendUpdateSecureTrade(cont, flag, gold, plat); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -88,7 +88,7 @@ public class SecureTradePacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendSecureTradeEquip(itemInCont, m); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -108,7 +108,7 @@ public class SecureTradePacketTests ns.SendSecureTradeEquip(itemInCont, m); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } } diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/TargetPacketsTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/TargetPacketsTests.cs index 016eed694..c2d4d29fc 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/TargetPacketsTests.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/TargetPacketsTests.cs @@ -44,7 +44,7 @@ public class TargetPacketsTests ns.ProtocolChanges |= ProtocolChanges.HighSeas; ns.SendMultiTargetReq(t); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -60,7 +60,7 @@ public class TargetPacketsTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendMultiTargetReq(t); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -71,7 +71,7 @@ public class TargetPacketsTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendCancelTarget(); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -84,7 +84,7 @@ public class TargetPacketsTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendTargetReq(t); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } } diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/VendorBuyPacketTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/VendorBuyPacketTests.cs index 34eb9d679..6262cc243 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/VendorBuyPacketTests.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/VendorBuyPacketTests.cs @@ -29,7 +29,7 @@ public class VendorBuyPacketTests ns.SendVendorBuyContent(buyStates); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -48,7 +48,7 @@ public class VendorBuyPacketTests ns.SendDisplayBuyList(vendor.Serial); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -72,7 +72,7 @@ public class VendorBuyPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendVendorBuyList(vendor, buyStates); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -87,7 +87,7 @@ public class VendorBuyPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendEndVendorBuy(vendor.Serial); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } } diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/VendorSellPacketTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/VendorSellPacketTests.cs index 40f97d1cb..154223349 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/VendorSellPacketTests.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/VendorSellPacketTests.cs @@ -30,7 +30,7 @@ public class VendorSellPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendVendorSellList(vendor.Serial, sellStates); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -45,7 +45,7 @@ public class VendorSellPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendEndVendorSell(vendor.Serial); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } } diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/VirtualHairPacketTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/VirtualHairPacketTests.cs index b3086acae..d02bb9e2a 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/VirtualHairPacketTests.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/VirtualHairPacketTests.cs @@ -20,7 +20,7 @@ public class VirtualHairPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendHairEquipUpdatePacket(m, (uint)m.Hair.VirtualSerial, m.Hair.ItemId, m.Hair.Hue, Layer.Hair); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -35,7 +35,7 @@ public class VirtualHairPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendRemoveHairPacket((uint) m.Hair.VirtualSerial); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } } diff --git a/Projects/Server.Tests/Tests/Network/PipeTests.cs b/Projects/Server.Tests/Tests/Network/PipeTests.cs deleted file mode 100644 index c32145589..000000000 --- a/Projects/Server.Tests/Tests/Network/PipeTests.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; -using Server.Network; -using Xunit; - -namespace Server.Tests.Network; - -public class PipeTests -{ - [Fact] - public void TestSizeMatchesPageSize() - { - var pageSize = (uint)Environment.SystemPageSize; - using var pipe = new Pipe(128); - - // Available memory should be in increments of system page size minus one. - Assert.Equal(pageSize, pipe.Size); - Assert.Equal(pageSize - 1, (uint)pipe.Writer.AvailableToWrite().Length); - } - - [Fact] - public void TestWriteReadsWrap() - { - var pageSize = (uint)Environment.SystemPageSize; - using var pipe = new Pipe(pageSize); - - var span = pipe.Writer.AvailableToWrite(); - - for (var i = 0; i < span.Length; i++) - { - span[i] = (byte)(i % 256); - } - - pipe.Writer.Advance((uint)(span.Length - 10)); - - var readBytes = pipe.Reader.AvailableToRead(); - - // Make a sequence from what we expect. - Span seq = new byte[readBytes.Length]; - for (var i = 0; i < readBytes.Length; i++) - { - seq[i] = (byte)(i % 256); - } - - AssertThat.Equal(readBytes, seq); - - // Advance by half. Expected writer length should be half + 10 - pipe.Reader.Advance(pageSize / 2); - - span = pipe.Writer.AvailableToWrite(); - - var halfStart = pageSize / 2 + 10; - - Assert.Equal(halfStart, (uint)span.Length); - - seq = new byte[20]; - - for (var i = 0; i < 10; i++) - { - seq[i] = (byte)(0xF5 + i); - } - - // The last element, the sentinel, is excluded. - // The wrap around values start at 11 - for (var i = 11; i < 20; i++) - { - seq[i] = (byte)(i - 11); - } - - // Test the uncommitted overwritten memory and shifted offset to make sure the ring is working - AssertThat.Equal(span[..20], seq[..20]); - } - - [Fact] - public void TestWriteReadMatches() - { - var pipe = new Pipe(16); - - var reader = pipe.Reader; - var writer = pipe.Writer; - - for (uint i = 0; i < 16; i++) - { - writer.Advance(i); - - Assert.Equal((int)i, reader.AvailableToRead().Length); - reader.Advance(i); - } - } -} diff --git a/Projects/Server.Tests/Tests/Network/PollGroupTests.cs b/Projects/Server.Tests/Tests/Network/PollGroupTests.cs deleted file mode 100644 index 34d97e9a2..000000000 --- a/Projects/Server.Tests/Tests/Network/PollGroupTests.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using System.Threading; -using Server.Network; -using Xunit; - -namespace Server.Tests.Network; - -public class PollGroupTests -{ - [Fact] - public void TestPollGroup() - { - // var group = new KQueuePollGroup(); - var nss = new NetState[2048]; - var handles = new IntPtr[2048]; - for (var i = 0; i < nss.Length; i++) - { - nss[i] = PacketTestUtilities.CreateTestNetState(); - handles[i] = (IntPtr)nss[i].Handle; - } - - GC.AddMemoryPressure(10000000000); - GC.Collect(); - GC.RemoveMemoryPressure(10000000000); - GC.Collect(); - - Thread.Sleep(1000); - - for (var i = 0; i < nss.Length; i++) - { - Assert.Equal(nss[i].Handle, (GCHandle)handles[i]); - } - - // group.Dispose(); - } -} diff --git a/Projects/Server/Events/SocketConnectionEvent.cs b/Projects/Server/Events/SocketConnectionEvent.cs index 9ac96dcd3..ec3e35cdd 100644 --- a/Projects/Server/Events/SocketConnectionEvent.cs +++ b/Projects/Server/Events/SocketConnectionEvent.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2023 - ModernUO Development Team * + * Copyright 2019-2026 - ModernUO Development Team * * Email: hi@modernuo.com * * File: SocketConnectionEvent.cs * * * @@ -14,20 +14,20 @@ *************************************************************************/ using System; -using System.Net.Sockets; +using System.Net; using System.Runtime.CompilerServices; namespace Server; public class SocketConnectEventArgs { - public SocketConnectEventArgs(Socket c) + public SocketConnectEventArgs(IPAddress address) { - Connection = c; + Address = address; AllowConnection = true; } - public Socket Connection { get; } + public IPAddress Address { get; } public bool AllowConnection { get; set; } } diff --git a/Projects/Server/Main.cs b/Projects/Server/Main.cs index 2276a8b20..670fcb5ce 100644 --- a/Projects/Server/Main.cs +++ b/Projects/Server/Main.cs @@ -339,7 +339,7 @@ public static class Core World.WaitForWriteCompletion(); World.ExitSerializationThreads(); PingServer.Shutdown(); - TcpServer.Shutdown(); + NetState.Shutdown(); if (!_crashed) { @@ -447,7 +447,7 @@ public static class Core AssemblyHandler.Invoke("Initialize"); - TcpServer.Start(); + NetState.Start(); PingServer.Start(); EventSink.InvokeServerStarted(); RunEventLoop(); diff --git a/Projects/Server/Mobiles/Mobile.cs b/Projects/Server/Mobiles/Mobile.cs index 49915a2ff..56223aaf0 100644 --- a/Projects/Server/Mobiles/Mobile.cs +++ b/Projects/Server/Mobiles/Mobile.cs @@ -1245,7 +1245,7 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro { get { - if (m_NetState?.Connection == null) + if (m_NetState is not { IsConnected: true }) { m_NetState = null; } diff --git a/Projects/Server/Network/Encryption/EncryptionConfig.cs b/Projects/Server/Network/Encryption/EncryptionConfig.cs new file mode 100644 index 000000000..6724e1b2f --- /dev/null +++ b/Projects/Server/Network/Encryption/EncryptionConfig.cs @@ -0,0 +1,45 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2025 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: EncryptionConfig.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; + +/// +/// Specifies which encryption modes the server will accept. +/// +[Flags] +public enum EncryptionMode +{ + /// + /// Encryption handling is disabled. Current behavior. + /// + None = 0x0, + + /// + /// Accept unencrypted clients (e.g., ClassicUO with encryption disabled). + /// + Unencrypted = 0x1, + + /// + /// Accept encrypted clients (original UO client, Enhanced Client). + /// + Encrypted = 0x2, + + /// + /// Auto-detect and accept both encrypted and unencrypted clients. + /// + Both = Unencrypted | Encrypted +} diff --git a/Projects/Server/Network/Encryption/EncryptionManager.cs b/Projects/Server/Network/Encryption/EncryptionManager.cs new file mode 100644 index 000000000..27cbba7ba --- /dev/null +++ b/Projects/Server/Network/Encryption/EncryptionManager.cs @@ -0,0 +1,207 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2025 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: EncryptionManager.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; +using System.Buffers.Binary; +using Server.Logging; + +namespace Server.Network; + +/// +/// Manages encryption detection and configuration for client connections. +/// +public static class EncryptionManager +{ + private static readonly ILogger logger = LogFactory.GetLogger(typeof(EncryptionManager)); + + private static EncryptionMode _mode = EncryptionMode.None; + private static bool _debug; + + /// + /// Gets whether encryption handling is enabled. + /// + public static bool Enabled => _mode != EncryptionMode.None; + + /// + /// Gets the current encryption mode. + /// + public static EncryptionMode Mode => _mode; + + /// + /// Gets whether debug logging is enabled for encryption. + /// + public static bool Debug => _debug; + + /// + /// Configures encryption settings from server configuration. + /// + public static void Configure() + { + _mode = ServerConfiguration.GetSetting("network.encryptionMode", EncryptionMode.Both); + _debug = ServerConfiguration.GetSetting("network.encryptionDebug", false); + + if (_mode != EncryptionMode.None) + { + logger.Information("Encryption support enabled: {Mode}", _mode); + } + } + + /// The network state. + extension(NetState ns) + { + /// + /// Detects and initializes encryption for a login packet (0x80). + /// + /// The 62-byte login packet buffer. + /// The detected encryption, or null if unencrypted. + /// True if detection succeeded (encrypted or unencrypted), false if rejected. + public bool DetectLoginEncryption(ReadOnlySpan buffer, out IClientEncryption encryption) + { + encryption = null; + + if (buffer.Length < 62) + { + return false; + } + + // Check if unencrypted: + // - Packet ID is 0x80, OR + // - Username and password null terminators are present + var isUnencrypted = buffer[0] == 0x80 || buffer[30] == 0x00 && buffer[60] == 0x00; + + if (isUnencrypted) + { + if (!_mode.HasFlag(EncryptionMode.Unencrypted)) + { + if (_debug) + { + logger.Debug("Client {Address}: Unencrypted login rejected (mode: {Mode})", ns.Address, _mode); + } + return false; + } + + if (_debug) + { + logger.Debug("Client {Address}: Unencrypted login detected", ns.Address); + } + + return true; + } + + // Try encrypted + if (!_mode.HasFlag(EncryptionMode.Encrypted)) + { + if (_debug) + { + logger.Debug("Client {Address}: Encrypted login rejected (mode: {Mode})", ns.Address, _mode); + } + return false; + } + + // Attempt decryption with version-derived keys + if (LoginEncryption.TryDecrypt(ns.Version, (uint)ns.Seed, buffer, out var loginEncryption)) + { + encryption = loginEncryption; + + if (_debug) + { + logger.Debug("Client {Address}: Encrypted login detected (version: {Version})", ns.Address, ns.Version); + } + + return true; + } + + if (_debug) + { + logger.Debug("Client {Address}: Login encryption detection failed", ns.Address); + } + + return false; + } + + /// + /// Detects and initializes encryption for a game server login packet (0x91). + /// + /// The 65-byte game login packet buffer. + /// The detected encryption, or null if unencrypted. + /// True if detection succeeded (encrypted or unencrypted), false if rejected. + public bool DetectGameEncryption(ReadOnlySpan buffer, out IClientEncryption encryption) + { + encryption = null; + + if (buffer.Length < 65) + { + return false; + } + + // Extract auth ID from packet (bytes 1-4, big-endian) + var authId = BinaryPrimitives.ReadUInt32BigEndian(buffer[1..]); + + // Check if unencrypted: + // - Packet ID is 0x91, OR + // - Auth ID equals seed (indicates no encryption applied) + var isUnencrypted = buffer[0] == 0x91 || authId == (uint)ns.Seed; + + if (isUnencrypted) + { + if (!_mode.HasFlag(EncryptionMode.Unencrypted)) + { + if (_debug) + { + logger.Debug("Client {Address}: Unencrypted game login rejected (mode: {Mode})", ns.Address, _mode); + } + return false; + } + + if (_debug) + { + logger.Debug("Client {Address}: Unencrypted game login detected", ns.Address); + } + + return true; + } + + // Try encrypted + if (!_mode.HasFlag(EncryptionMode.Encrypted)) + { + if (_debug) + { + logger.Debug("Client {Address}: Encrypted game login rejected (mode: {Mode})", ns.Address, _mode); + } + return false; + } + + // Attempt decryption with seed-derived Twofish + if (GameEncryption.TryDecrypt((uint)ns.Seed, buffer, out var gameEncryption)) + { + encryption = gameEncryption; + + if (_debug) + { + logger.Debug("Client {Address}: Encrypted game login detected (seed: 0x{Seed:X8})", ns.Address, ns.Seed); + } + + return true; + } + + if (_debug) + { + logger.Debug("Client {Address}: Game encryption detection failed", ns.Address); + } + + return false; + } + } +} diff --git a/Projects/Server/Network/Encryption/GameEncryption.cs b/Projects/Server/Network/Encryption/GameEncryption.cs new file mode 100644 index 000000000..78c219b63 --- /dev/null +++ b/Projects/Server/Network/Encryption/GameEncryption.cs @@ -0,0 +1,204 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2025 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: GameEncryption.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; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Security.Cryptography; +using Server.Logging; + +namespace Server.Network; + +/// +/// Implements game packet encryption/decryption using Twofish + MD5. +/// Used for all game packets after login (0x91 and onwards). +/// +public sealed class GameEncryption : IClientEncryption +{ + private static readonly ILogger logger = LogFactory.GetLogger(typeof(GameEncryption)); + + private const int CipherTableSize = 256; + private const int BlockSize = 16; + + // Static identity table [0..255] for vectorized CopyTo initialization + private static readonly byte[] IdentityTable = CreateIdentityTable(); + + private static byte[] CreateIdentityTable() + { + var table = new byte[CipherTableSize]; + for (var i = 0; i < CipherTableSize; i++) + { + table[i] = (byte)i; + } + return table; + } + + private readonly TwofishEngine _twofish; + private readonly byte[] _cipherTable; + private readonly byte[] _xorKey; + + private ushort _recvPos; + private byte _sendPos; + + public GameEncryption(uint seed) + { + // Create 16-byte key from seed (repeated 4 times) + Span key = stackalloc byte[16]; + key[0] = key[4] = key[8] = key[12] = (byte)((seed >> 24) & 0xFF); + key[1] = key[5] = key[9] = key[13] = (byte)((seed >> 16) & 0xFF); + key[2] = key[6] = key[10] = key[14] = (byte)((seed >> 8) & 0xFF); + key[3] = key[7] = key[11] = key[15] = (byte)(seed & 0xFF); + + _twofish = new TwofishEngine(key); + + // Initialize cipher table with identity [0..255] using vectorized copy + _cipherTable = GC.AllocateUninitializedArray(CipherTableSize); + IdentityTable.CopyTo(_cipherTable, 0); + + // Encrypt cipher table with Twofish + RefreshCipherTable(); + + // Compute MD5 hash of cipher table for server->client XOR key + _xorKey = MD5.HashData(_cipherTable); + } + + /// + /// Refreshes the cipher table by encrypting it with Twofish. + /// Called every 256 bytes of received data. + /// + private void RefreshCipherTable() + { + // Encrypt cipher table in 16-byte blocks + for (var i = 0; i < CipherTableSize; i += BlockSize) + { + _twofish.EncryptBlock(_cipherTable.AsSpan(i, BlockSize)); + } + + _recvPos = 0; + } + + /// + /// Decrypts incoming data from the client (in-place). + /// XORs with cipher table, refreshing every 256 bytes. + /// + public void ClientDecrypt(Span buffer) + { + for (var i = 0; i < buffer.Length; i++) + { + if (_recvPos >= CipherTableSize) + { + RefreshCipherTable(); + } + + buffer[i] ^= _cipherTable[_recvPos++]; + } + } + + /// + /// Encrypts outgoing data to the client (in-place). + /// XORs with MD5 hash of cipher table (16-byte rotating key). + /// Uses SIMD optimization for larger buffers. + /// + public void ServerEncrypt(Span buffer) + { + var i = 0; + + // SIMD path: process 16 bytes at a time when aligned with key + if (_sendPos == 0 && buffer.Length >= 16 && Vector128.IsHardwareAccelerated) + { + var keyVec = Vector128.Create(_xorKey); + + for (; i + 16 <= buffer.Length; i += 16) + { + var chunk = Vector128.LoadUnsafe(ref buffer[i]); + var result = Vector128.Xor(chunk, keyVec); + result.StoreUnsafe(ref buffer[i]); + } + } + + // Scalar path for remainder or when not aligned + for (; i < buffer.Length; i++) + { + buffer[i] ^= _xorKey[_sendPos++]; + _sendPos &= 0x0F; // Wrap at 16 + } + } + + /// + /// Attempts to decrypt a game login packet and validate it. + /// Returns true if the packet appears to be validly encrypted. + /// + public static bool TryDecrypt(uint seed, ReadOnlySpan encryptedPacket, out GameEncryption encryption) + { + const int GameLoginPacketSize = 65; + + encryption = null; + + if (encryptedPacket.Length < GameLoginPacketSize) + { + if (EncryptionManager.Debug) + { + logger.Debug("GameEncryption.TryDecrypt: Invalid buffer length {Length}", encryptedPacket.Length); + } + return false; + } + + if (EncryptionManager.Debug) + { + logger.Debug("GameEncryption.TryDecrypt: Seed=0x{Seed:X8}", seed); + logger.Debug("GameEncryption.TryDecrypt: Encrypted[0..16]: {Bytes}", Convert.ToHexString(encryptedPacket[..16])); + } + + // Copy and decrypt + Span decrypted = stackalloc byte[GameLoginPacketSize]; + encryptedPacket[..GameLoginPacketSize].CopyTo(decrypted); + var enc = new GameEncryption(seed); + + if (EncryptionManager.Debug) + { + logger.Debug("GameEncryption.TryDecrypt: CipherTable[0..16]: {Bytes}", + Convert.ToHexString(enc._cipherTable.AsSpan(0, 16))); + logger.Debug("GameEncryption.TryDecrypt: XorKey: {Bytes}", Convert.ToHexString(enc._xorKey)); + } + + enc.ClientDecrypt(decrypted); + + if (EncryptionManager.Debug) + { + logger.Debug("GameEncryption.TryDecrypt: Decrypted[0..16]: {Bytes}", Convert.ToHexString(decrypted[..16])); + logger.Debug("GameEncryption.TryDecrypt: First byte=0x{Byte:X2} (expected 0x91)", decrypted[0]); + } + + // Validate: first byte must be 0x91 (game server login packet ID) + if (decrypted[0] != 0x91) + { + if (EncryptionManager.Debug) + { + logger.Debug("GameEncryption.TryDecrypt: Validation FAILED - first byte is not 0x91"); + } + return false; + } + + if (EncryptionManager.Debug) + { + logger.Debug("GameEncryption.TryDecrypt: Validation PASSED"); + } + + // Re-create encryption for actual use + encryption = new GameEncryption(seed); + return true; + } +} diff --git a/Projects/Server/Network/Encryption/IClientEncryption.cs b/Projects/Server/Network/Encryption/IClientEncryption.cs new file mode 100644 index 000000000..a72b74dc4 --- /dev/null +++ b/Projects/Server/Network/Encryption/IClientEncryption.cs @@ -0,0 +1,37 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2025 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: IClientEncryption.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; + +/// +/// Interface for client encryption implementations. +/// Uses Span-based API for zero-allocation in the hot path. +/// +public interface IClientEncryption +{ + /// + /// Decrypts incoming data from the client (in-place). + /// + /// The buffer containing encrypted data. Modified in-place. + void ClientDecrypt(Span buffer); + + /// + /// Encrypts outgoing data to the client (in-place). + /// + /// The buffer containing plaintext data. Modified in-place. + void ServerEncrypt(Span buffer); +} diff --git a/Projects/Server/Network/Encryption/LoginEncryption.cs b/Projects/Server/Network/Encryption/LoginEncryption.cs new file mode 100644 index 000000000..e7e013700 --- /dev/null +++ b/Projects/Server/Network/Encryption/LoginEncryption.cs @@ -0,0 +1,123 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2025 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: LoginEncryption.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; + +/// +/// Implements login packet encryption/decryption using XOR with version-derived keys. +/// Used for the initial account login packet (0x80). +/// +public sealed class LoginEncryption : IClientEncryption +{ + private uint _table1; + private uint _table2; + private readonly uint _key1; + private readonly uint _key2; + + public LoginEncryption(uint seed, LoginKeys keys) + { + _key1 = keys.Key1; + _key2 = keys.Key2; + + // Initialize state tables from seed + _table1 = ((~seed ^ 0x00001357) << 16) | ((seed ^ 0xFFFFAAAA) & 0x0000FFFF); + _table2 = ((seed ^ 0x43210000) >> 16) | ((~seed ^ 0xABCDFFFF) & 0xFFFF0000); + } + + /// + /// Attempts to initialize login encryption and validate the packet. + /// Returns true if the packet appears to be validly encrypted with this scheme. + /// + public static bool TryDecrypt( + ClientVersion version, + uint seed, + ReadOnlySpan encryptedPacket, + out LoginEncryption encryption) + { + const int LoginPacketSize = 62; + + encryption = null; + + var keys = LoginKeys.GetKeys(version); + if (keys is { Key1: 0, Key2: 0 }) + { + return false; + } + + if (encryptedPacket.Length < LoginPacketSize) + { + return false; + } + + // Copy and decrypt + Span decrypted = stackalloc byte[LoginPacketSize]; + encryptedPacket[..LoginPacketSize].CopyTo(decrypted); + var enc = new LoginEncryption(seed, keys); + enc.ClientDecrypt(decrypted); + + // Validate decrypted packet structure: + // - Byte 0 must be 0x80 (account login packet ID) + // - Byte 30 must be 0x00 (null terminator for username) + // - Byte 60 must be 0x00 (null terminator for password) + if (decrypted[0] != 0x80 || decrypted[30] != 0x00 || decrypted[60] != 0x00) + { + return false; + } + + // Re-initialize encryption state for actual use + encryption = new LoginEncryption(seed, keys); + return true; + } + + /// + /// Decrypts incoming data from the client (in-place). + /// + public void ClientDecrypt(Span buffer) + { + for (var i = 0; i < buffer.Length; i++) + { + buffer[i] ^= (byte)(_table1 & 0xFF); + + var edx = _table2; + var esi = _table1 << 31; + var eax = _table2 >> 1; + + eax |= esi; + eax ^= _key1 - 1; + edx <<= 31; + eax >>= 1; + + var ecx = _table1 >> 1; + + eax |= esi; + ecx |= edx; + eax ^= _key1; + ecx ^= _key2; + + _table1 = ecx; + _table2 = eax; + } + } + + /// + /// Server does not encrypt login responses, so this is a no-op. + /// + public void ServerEncrypt(Span buffer) + { + // Login encryption is client-to-server only + } +} diff --git a/Projects/Server/Network/Encryption/LoginKeys.cs b/Projects/Server/Network/Encryption/LoginKeys.cs new file mode 100644 index 000000000..97860e2c2 --- /dev/null +++ b/Projects/Server/Network/Encryption/LoginKeys.cs @@ -0,0 +1,89 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2025 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: LoginKeys.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.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Server.Network; + +/// +/// Represents encryption keys derived from a client version. +/// Used for login packet encryption/decryption. +/// +public readonly struct LoginKeys +{ + public static readonly LoginKeys Empty = new(0, 0); + + private static readonly Dictionary _cache = []; + + public uint Key1 { get; } + public uint Key2 { get; } + + private LoginKeys(uint key1, uint key2) + { + Key1 = key1; + Key2 = key2; + } + + /// + /// Gets or computes encryption keys for the specified client version. + /// Results are cached for performance. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static LoginKeys GetKeys(ClientVersion version) + { + if (version == null) + { + return Empty; + } + + if (_cache.TryGetValue(version, out var keys)) + { + return keys; + } + + keys = ComputeKeys(version); + _cache[version] = keys; + return keys; + } + + /// + /// Computes encryption keys from client version using the UO key derivation algorithm. + /// + private static LoginKeys ComputeKeys(ClientVersion version) + { + uint major = (uint)version.Major; + uint minor = (uint)version.Minor; + uint revision = (uint)version.Revision; + + // Key1 derivation + uint key1 = (major << 23) | (minor << 14) | (revision << 4); + key1 ^= (revision * revision) << 9; + key1 ^= minor * minor; + key1 ^= (minor * 11) << 24; + key1 ^= (revision * 7) << 19; + key1 ^= 0x2C13A5FD; + + // Key2 derivation + uint key2 = (major << 22) | (revision << 13) | (minor << 3); + key2 ^= (revision * revision * 3) << 10; + key2 ^= minor * minor; + key2 ^= (minor * 13) << 23; + key2 ^= (revision * 7) << 18; + key2 ^= 0xA31D527F; + + return new LoginKeys(key1, key2); + } +} diff --git a/Projects/Server/Network/Encryption/TwofishEngine.cs b/Projects/Server/Network/Encryption/TwofishEngine.cs new file mode 100644 index 000000000..5fc2fb6b1 --- /dev/null +++ b/Projects/Server/Network/Encryption/TwofishEngine.cs @@ -0,0 +1,271 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2025 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: TwofishEngine.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; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Server.Network; + +/// +/// Twofish block cipher implementation for UO encryption. +/// Implements 128-bit block encryption with 128-bit key in ECB mode. +/// Based on the public domain Twofish algorithm by Bruce Schneier et al. +/// +public sealed class TwofishEngine +{ + private const int BlockSize = 16; // 128 bits + private const int Rounds = 16; + private const int InputWhiten = 0; + private const int OutputWhiten = 4; + private const int RoundSubkeys = 8; + private const int TotalSubkeys = RoundSubkeys + 2 * Rounds; + + private const uint SkStep = 0x02020202u; + private const uint SkBump = 0x01010101u; + private const int SkRotl = 9; + private const uint RsGfFdbk = 0x14D; + private const int MdsGfFdbk = 0x169; + + // P0 and P1 permutation tables + private static readonly byte[] P0 = + { + 0xA9, 0x67, 0xB3, 0xE8, 0x04, 0xFD, 0xA3, 0x76, 0x9A, 0x92, 0x80, 0x78, 0xE4, 0xDD, 0xD1, 0x38, + 0x0D, 0xC6, 0x35, 0x98, 0x18, 0xF7, 0xEC, 0x6C, 0x43, 0x75, 0x37, 0x26, 0xFA, 0x13, 0x94, 0x48, + 0xF2, 0xD0, 0x8B, 0x30, 0x84, 0x54, 0xDF, 0x23, 0x19, 0x5B, 0x3D, 0x59, 0xF3, 0xAE, 0xA2, 0x82, + 0x63, 0x01, 0x83, 0x2E, 0xD9, 0x51, 0x9B, 0x7C, 0xA6, 0xEB, 0xA5, 0xBE, 0x16, 0x0C, 0xE3, 0x61, + 0xC0, 0x8C, 0x3A, 0xF5, 0x73, 0x2C, 0x25, 0x0B, 0xBB, 0x4E, 0x89, 0x6B, 0x53, 0x6A, 0xB4, 0xF1, + 0xE1, 0xE6, 0xBD, 0x45, 0xE2, 0xF4, 0xB6, 0x66, 0xCC, 0x95, 0x03, 0x56, 0xD4, 0x1C, 0x1E, 0xD7, + 0xFB, 0xC3, 0x8E, 0xB5, 0xE9, 0xCF, 0xBF, 0xBA, 0xEA, 0x77, 0x39, 0xAF, 0x33, 0xC9, 0x62, 0x71, + 0x81, 0x79, 0x09, 0xAD, 0x24, 0xCD, 0xF9, 0xD8, 0xE5, 0xC5, 0xB9, 0x4D, 0x44, 0x08, 0x86, 0xE7, + 0xA1, 0x1D, 0xAA, 0xED, 0x06, 0x70, 0xB2, 0xD2, 0x41, 0x7B, 0xA0, 0x11, 0x31, 0xC2, 0x27, 0x90, + 0x20, 0xF6, 0x60, 0xFF, 0x96, 0x5C, 0xB1, 0xAB, 0x9E, 0x9C, 0x52, 0x1B, 0x5F, 0x93, 0x0A, 0xEF, + 0x91, 0x85, 0x49, 0xEE, 0x2D, 0x4F, 0x8F, 0x3B, 0x47, 0x87, 0x6D, 0x46, 0xD6, 0x3E, 0x69, 0x64, + 0x2A, 0xCE, 0xCB, 0x2F, 0xFC, 0x97, 0x05, 0x7A, 0xAC, 0x7F, 0xD5, 0x1A, 0x4B, 0x0E, 0xA7, 0x5A, + 0x28, 0x14, 0x3F, 0x29, 0x88, 0x3C, 0x4C, 0x02, 0xB8, 0xDA, 0xB0, 0x17, 0x55, 0x1F, 0x8A, 0x7D, + 0x57, 0xC7, 0x8D, 0x74, 0xB7, 0xC4, 0x9F, 0x72, 0x7E, 0x15, 0x22, 0x12, 0x58, 0x07, 0x99, 0x34, + 0x6E, 0x50, 0xDE, 0x68, 0x65, 0xBC, 0xDB, 0xF8, 0xC8, 0xA8, 0x2B, 0x40, 0xDC, 0xFE, 0x32, 0xA4, + 0xCA, 0x10, 0x21, 0xF0, 0xD3, 0x5D, 0x0F, 0x00, 0x6F, 0x9D, 0x36, 0x42, 0x4A, 0x5E, 0xC1, 0xE0 + }; + + private static readonly byte[] P1 = + { + 0x75, 0xF3, 0xC6, 0xF4, 0xDB, 0x7B, 0xFB, 0xC8, 0x4A, 0xD3, 0xE6, 0x6B, 0x45, 0x7D, 0xE8, 0x4B, + 0xD6, 0x32, 0xD8, 0xFD, 0x37, 0x71, 0xF1, 0xE1, 0x30, 0x0F, 0xF8, 0x1B, 0x87, 0xFA, 0x06, 0x3F, + 0x5E, 0xBA, 0xAE, 0x5B, 0x8A, 0x00, 0xBC, 0x9D, 0x6D, 0xC1, 0xB1, 0x0E, 0x80, 0x5D, 0xD2, 0xD5, + 0xA0, 0x84, 0x07, 0x14, 0xB5, 0x90, 0x2C, 0xA3, 0xB2, 0x73, 0x4C, 0x54, 0x92, 0x74, 0x36, 0x51, + 0x38, 0xB0, 0xBD, 0x5A, 0xFC, 0x60, 0x62, 0x96, 0x6C, 0x42, 0xF7, 0x10, 0x7C, 0x28, 0x27, 0x8C, + 0x13, 0x95, 0x9C, 0xC7, 0x24, 0x46, 0x3B, 0x70, 0xCA, 0xE3, 0x85, 0xCB, 0x11, 0xD0, 0x93, 0xB8, + 0xA6, 0x83, 0x20, 0xFF, 0x9F, 0x77, 0xC3, 0xCC, 0x03, 0x6F, 0x08, 0xBF, 0x40, 0xE7, 0x2B, 0xE2, + 0x79, 0x0C, 0xAA, 0x82, 0x41, 0x3A, 0xEA, 0xB9, 0xE4, 0x9A, 0xA4, 0x97, 0x7E, 0xDA, 0x7A, 0x17, + 0x66, 0x94, 0xA1, 0x1D, 0x3D, 0xF0, 0xDE, 0xB3, 0x0B, 0x72, 0xA7, 0x1C, 0xEF, 0xD1, 0x53, 0x3E, + 0x8F, 0x33, 0x26, 0x5F, 0xEC, 0x76, 0x2A, 0x49, 0x81, 0x88, 0xEE, 0x21, 0xC4, 0x1A, 0xEB, 0xD9, + 0xC5, 0x39, 0x99, 0xCD, 0xAD, 0x31, 0x8B, 0x01, 0x18, 0x23, 0xDD, 0x1F, 0x4E, 0x2D, 0xF9, 0x48, + 0x4F, 0xF2, 0x65, 0x8E, 0x78, 0x5C, 0x58, 0x19, 0x8D, 0xE5, 0x98, 0x57, 0x67, 0x7F, 0x05, 0x64, + 0xAF, 0x63, 0xB6, 0xFE, 0xF5, 0xB7, 0x3C, 0xA5, 0xCE, 0xE9, 0x68, 0x44, 0xE0, 0x4D, 0x43, 0x69, + 0x29, 0x2E, 0xAC, 0x15, 0x59, 0xA8, 0x0A, 0x9E, 0x6E, 0x47, 0xDF, 0x34, 0x35, 0x6A, 0xCF, 0xDC, + 0x22, 0xC9, 0xC0, 0x9B, 0x89, 0xD4, 0xED, 0xAB, 0x12, 0xA2, 0x0D, 0x52, 0xBB, 0x02, 0x2F, 0xA9, + 0xD7, 0x61, 0x1E, 0xB4, 0x50, 0x04, 0xF6, 0xC2, 0x16, 0x25, 0x86, 0x56, 0x55, 0x09, 0xBE, 0x91 + }; + + private readonly uint[] _sboxKeys = new uint[2]; // For 128-bit key + private readonly uint[] _subKeys = new uint[TotalSubkeys]; + + /// + /// Creates a new Twofish engine with the specified 128-bit key. + /// + public TwofishEngine(ReadOnlySpan key) + { + if (key.Length != 16) + { + throw new ArgumentException("Key must be 16 bytes (128 bits)", nameof(key)); + } + + GenerateSubkeys(MemoryMarshal.Cast(key)); + } + + private void GenerateSubkeys(ReadOnlySpan keyWords) + { + // Split key into even and odd words + var k0 = keyWords[0]; + var k1 = keyWords[1]; + var k2 = keyWords[2]; + var k3 = keyWords[3]; + + // Compute S-box keys using RS matrix + _sboxKeys[0] = RsMdsEncode(k2, k3); + _sboxKeys[1] = RsMdsEncode(k0, k1); + + // Generate round subkeys + for (var i = 0; i < TotalSubkeys / 2; i++) + { + var a = F32((uint)(i * SkStep), k0, k2); + var b = F32((uint)(i * SkStep + SkBump), k1, k3); + b = BitOperations.RotateLeft(b, 8); + + _subKeys[2 * i] = a + b; + _subKeys[2 * i + 1] = BitOperations.RotateLeft(a + 2 * b, SkRotl); + } + } + + /// + /// Encrypts a 16-byte block in place. + /// + public void EncryptBlock(Span block) + { + if (block.Length < BlockSize) + { + throw new ArgumentException("Block must be at least 16 bytes", nameof(block)); + } + + var x = MemoryMarshal.Cast(block); + + // Input whitening + x[0] ^= _subKeys[InputWhiten]; + x[1] ^= _subKeys[InputWhiten + 1]; + x[2] ^= _subKeys[InputWhiten + 2]; + x[3] ^= _subKeys[InputWhiten + 3]; + + // 16 rounds + for (var r = 0; r < Rounds; r++) + { + var t0 = F32Sbox(x[0]); + var t1 = F32Sbox(BitOperations.RotateLeft(x[1], 8)); + + x[3] = BitOperations.RotateLeft(x[3], 1); + x[2] ^= t0 + t1 + _subKeys[RoundSubkeys + 2 * r]; + x[3] ^= t0 + 2 * t1 + _subKeys[RoundSubkeys + 2 * r + 1]; + x[2] = BitOperations.RotateRight(x[2], 1); + + if (r < Rounds - 1) + { + // Swap for next round + (x[0], x[2]) = (x[2], x[0]); + (x[1], x[3]) = (x[3], x[1]); + } + } + + // Output whitening + x[0] ^= _subKeys[OutputWhiten]; + x[1] ^= _subKeys[OutputWhiten + 1]; + x[2] ^= _subKeys[OutputWhiten + 2]; + x[3] ^= _subKeys[OutputWhiten + 3]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private uint F32Sbox(uint x) + { + // For 128-bit key, use 2 S-box keys + // Permutation sequence from Twofish spec (P_ij constants): + // b0: P0[P0[P0[x]^k1]^k0] then P1 final + // b1: P0[P0[P1[x]^k1]^k0] then P0 final + // b2: P1[P1[P0[x]^k1]^k0] then P1 final + // b3: P1[P1[P1[x]^k1]^k0] then P0 final + + var b0 = (byte)x; + var b1 = (byte)(x >> 8); + var b2 = (byte)(x >> 16); + var b3 = (byte)(x >> 24); + + var k0 = _sboxKeys[0]; + var k1 = _sboxKeys[1]; + + // First layer: P_02=0(P0), P_12=1(P1), P_22=0(P0), P_32=1(P1) + b0 = (byte)(P0[b0] ^ (byte)k1); + b1 = (byte)(P1[b1] ^ (byte)(k1 >> 8)); + b2 = (byte)(P0[b2] ^ (byte)(k1 >> 16)); + b3 = (byte)(P1[b3] ^ (byte)(k1 >> 24)); + + // Second layer: P_01=0(P0), P_11=0(P0), P_21=1(P1), P_31=1(P1) + b0 = (byte)(P0[b0] ^ (byte)k0); + b1 = (byte)(P0[b1] ^ (byte)(k0 >> 8)); + b2 = (byte)(P1[b2] ^ (byte)(k0 >> 16)); + b3 = (byte)(P1[b3] ^ (byte)(k0 >> 24)); + + // Final layer: P_00=1(P1), P_10=0(P0), P_20=1(P1), P_30=0(P0) + // MDS matrix multiply + return MdsMultiply(P1[b0], P0[b1], P1[b2], P0[b3]); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint F32(uint x, uint k0, uint k2) + { + var b0 = (byte)x; + var b1 = (byte)(x >> 8); + var b2 = (byte)(x >> 16); + var b3 = (byte)(x >> 24); + + // First layer: P_02=0(P0), P_12=1(P1), P_22=0(P0), P_32=1(P1) + b0 = (byte)(P0[b0] ^ (byte)k2); + b1 = (byte)(P1[b1] ^ (byte)(k2 >> 8)); + b2 = (byte)(P0[b2] ^ (byte)(k2 >> 16)); + b3 = (byte)(P1[b3] ^ (byte)(k2 >> 24)); + + // Second layer: P_01=0(P0), P_11=0(P0), P_21=1(P1), P_31=1(P1) + b0 = (byte)(P0[b0] ^ (byte)k0); + b1 = (byte)(P0[b1] ^ (byte)(k0 >> 8)); + b2 = (byte)(P1[b2] ^ (byte)(k0 >> 16)); + b3 = (byte)(P1[b3] ^ (byte)(k0 >> 24)); + + // Final layer: P_00=1(P1), P_10=0(P0), P_20=1(P1), P_30=0(P0) + return MdsMultiply(P1[b0], P0[b1], P1[b2], P0[b3]); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint MdsMultiply(byte b0, byte b1, byte b2, byte b3) + { + // MDS matrix multiplication (Galois Field 2^8) + var m0 = (uint)(b0 ^ Lfsr2(b1) ^ Lfsr1(b2) ^ Lfsr1(b3)); + var m1 = (uint)(Lfsr1(b0) ^ Lfsr2(b1) ^ Lfsr2(b2) ^ b3); + var m2 = (uint)(Lfsr2(b0) ^ Lfsr1(b1) ^ b2 ^ Lfsr2(b3)); + var m3 = (uint)(Lfsr2(b0) ^ b1 ^ Lfsr2(b2) ^ Lfsr1(b3)); + + return m0 | (m1 << 8) | (m2 << 16) | (m3 << 24); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int Lfsr1(int val) => val ^ Lfsr4(val); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int Lfsr2(int val) => val ^ Lfsr3(val) ^ Lfsr4(val); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int Lfsr3(int val) => (val >> 1) ^ ((val & 0x01) == 0x01 ? MdsGfFdbk / 2 : 0); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int Lfsr4(int val) => + (val >> 2) ^ ((val & 0x02) == 0x02 ? MdsGfFdbk / 2 : 0) ^ ((val & 0x01) == 0x01 ? MdsGfFdbk / 4 : 0); + + private static uint RsMdsEncode(uint k0, uint k1) + { + uint r = 0; + for (var i = 0; i < 2; i++) + { + r ^= i > 0 ? k0 : k1; + + for (var j = 0; j < 4; j++) + { + var v1 = (byte)(r >> 24); + var v2 = (uint)(((v1 << 1) ^ ((v1 & 0x80) == 0x80 ? RsGfFdbk : 0)) & 0xFF); + var v3 = (uint)(((v1 >> 1) & 0x7F) ^ ((v1 & 1) == 1 ? RsGfFdbk >> 1 : 0) ^ v2); + + r = (r << 8) ^ (v3 << 24) ^ (v2 << 16) ^ (v3 << 8) ^ v1; + } + } + + return r; + } +} diff --git a/Projects/Server/Network/NetState/DumpNetStates.cs b/Projects/Server/Network/NetState/DumpNetStates.cs index 22746238e..4b4574c34 100644 --- a/Projects/Server/Network/NetState/DumpNetStates.cs +++ b/Projects/Server/Network/NetState/DumpNetStates.cs @@ -32,7 +32,7 @@ public static class DumpNetStates foreach (var ns in NetState.Instances) { - file.WriteLine($"{ns}, {ns.ConnectedOn}, {ns.NextActivityCheck}, {ns.Connection.Connected}, {ns._protocolState}, {ns._parserState}"); + file.WriteLine($"{ns}, {ns.ConnectedOn}, {ns.NextActivityCheck}, {ns.IsConnected}, {ns._protocolState}, {ns._parserState}"); } } } diff --git a/Projects/Server/Network/NetState/NetState.Network.cs b/Projects/Server/Network/NetState/NetState.Network.cs new file mode 100644 index 000000000..494492cc1 --- /dev/null +++ b/Projects/Server/Network/NetState/NetState.Network.cs @@ -0,0 +1,498 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2025 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: NetState.Network.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; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Network; + +namespace Server.Network; + +/// +/// Network infrastructure for IORingGroup-based socket I/O. +/// +public partial class NetState +{ + // Buffer sizes + private const int RecvBufferSize = 1024 * 64; // 64KB recv buffers + private const int SendBufferSize = 1024 * 256; // 256KB send buffers + private const int MaxConnections = 4096; // Max concurrent connections + + // Socket manager handles buffer pools, socket lifecycle, and I/O operations + private static RingSocketManager _socketManager; + + // NetState storage indexed by RingSocket.Id + private static readonly NetState[] _netStates = new NetState[MaxConnections]; + + // Events buffer for ProcessCompletions + private static readonly RingSocketEvent[] _events = new RingSocketEvent[MaxConnections * 2]; + + // Listener management + private static nint[] _listeners = Array.Empty(); + private static int _pendingAcceptCount; + private const int PendingAcceptsPerListener = 32; + + /// + /// Gets the IORingGroup instance for socket operations. + /// + public static IIORingGroup Ring => _socketManager?.Ring; + + /// + /// Gets the listening addresses that the server is bound to. + /// + public static IPEndPoint[] ListeningAddresses { get; private set; } + + private static IPRateLimiter _ipRateLimiter; + + /// + /// Configures the IORingGroup and socket manager. + /// + private static void ConfigureNetwork() + { + // Skip if already configured + if (_socketManager != null) + { + return; + } + + // Initialize IP rate limiter + _ipRateLimiter = new IPRateLimiter(10, 10000, 1000, 2.0, 3_600_000, Core.ClosingTokenSource.Token); + + // Initialize IORingGroup + var ring = IORingGroup.Create(queueSize: MaxConnections * 2, maxConnections: MaxConnections); + + // Create socket manager which handles buffer pools and socket lifecycle + _socketManager = new RingSocketManager( + ring, + maxSockets: MaxConnections, + recvBufferSize: RecvBufferSize, + sendBufferSize: SendBufferSize, + initialBufferSlabs: 8, + maxBufferSlabs: 32 + ); + } + + /// + /// Starts the network server on configured listening addresses. + /// + public static void Start() + { + HashSet listeningAddresses = []; + List listeners = []; + + var ring = _socketManager.Ring; + for (var i = 0; i < ServerConfiguration.Listeners.Count; i++) + { + var ipep = ServerConfiguration.Listeners[i]; + var listener = ring.CreateListener(ipep.Address.ToString(), (ushort)ipep.Port, 256); + if (listener == -1) + { + logger.Warning("Failed to create listener for {Address}", ipep); + continue; + } + + if (ipep.Address.Equals(IPAddress.Any) || ipep.Address.Equals(IPAddress.IPv6Any)) + { + listeningAddresses.UnionWith(GetListeningAddresses(ipep)); + } + else + { + listeningAddresses.Add(ipep); + } + + listeners.Add(listener); + } + + foreach (var ipep in listeningAddresses) + { + logger.Information("Listening: {Address}", ipep); + } + + ListeningAddresses = listeningAddresses.ToArray(); + + // Register listeners to start accepting connections + RegisterListeners(listeners.ToArray()); + } + + /// + /// Shuts down the network server and closes all listeners. + /// + public static void Shutdown() + { + CloseListeners(); + } + + /// + /// Gets the actual listening addresses for a wildcard endpoint. + /// + public static IEnumerable 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)) + ); + + /// + /// Registers listeners with the ring and starts accepting connections. + /// + private static void RegisterListeners(nint[] listeners) + { + _listeners = listeners; + + var ring = _socketManager.Ring; + + // Queue initial accept operations for each listener + for (var i = 0; i < _listeners.Length; i++) + { + var listener = _listeners[i]; + for (var j = 0; j < PendingAcceptsPerListener; j++) + { + ring.PrepareAccept(listener, 0, 0, IORingUserData.EncodeAccept()); + _pendingAcceptCount++; + } + } + } + + /// + /// Closes all listeners. + /// + private static void CloseListeners() + { + var ring = _socketManager?.Ring; + if (ring == null) + { + return; + } + + foreach (var listener in _listeners) + { + ring.CloseListener(listener); + } + + _listeners = []; + } + + private static void HandleAcceptCompletion(int result) + { + _pendingAcceptCount--; + + var ring = _socketManager.Ring; + + // EAGAIN (-11) means no connection pending - just re-queue + if (result == -11) + { + goto ReplenishAccepts; + } + + if (result >= 0) + { + var clientSocket = (nint)result; + var remoteIP = SocketHelper.GetRemoteAddress(clientSocket); + + if (remoteIP != null) + { + if (_ipRateLimiter != null && !_ipRateLimiter.Verify(remoteIP, out var totalAttempts)) + { + logger.Debug("{Address} Past IP limit threshold ({TotalAttempts})", remoteIP, totalAttempts); + } + else if (Firewall.IsBlocked(remoteIP)) + { + logger.Debug("{Address} Firewalled", remoteIP); + } + else + { + // Allow event handlers to reject the connection + var args = new SocketConnectEventArgs(remoteIP); + EventSink.InvokeSocketConnect(args); + + if (args.AllowConnection) + { + ring.ConfigureSocket(clientSocket); + CreateFromSocket(clientSocket, remoteIP); + goto ReplenishAccepts; + } + + logger.Debug("{Address} Rejected by socket handler", remoteIP); + } + } + + ring.CloseSocket(clientSocket); + } + else if (result != -4) // EINTR + { + logger.Debug("Accept error: {Result}", result); + } + + ReplenishAccepts: + var targetAccepts = _listeners.Length * PendingAcceptsPerListener; + while (_pendingAcceptCount < targetAccepts && _listeners.Length > 0) + { + var listenerIndex = _pendingAcceptCount % _listeners.Length; + ring.PrepareAccept(_listeners[listenerIndex], 0, 0, IORingUserData.EncodeAccept()); + _pendingAcceptCount++; + } + } + + /// + /// Creates a NetState from an accepted socket handle. + /// + internal static NetState CreateFromSocket(nint socketHandle, IPAddress address) + { + // Use socket manager to create managed socket (handles buffers, registration, recv posting) + var socket = _socketManager.CreateSocket(socketHandle); + if (socket == null) + { + logger.Debug("Failed to create socket (resources exhausted)"); + _socketManager.Ring.CloseSocket(socketHandle); + return null; + } + + // Create NetState and map by socket ID + var ns = new NetState(socket, address); + return _netStates[socket.Id] = ns; + } + + private static void DisconnectUnattachedSockets() + { + var now = Core.Now; + + // Process connecting queue with lazy removal - O(1) operations + while (_connectingQueue.TryPeek(out var ns)) + { + // Lazy removal: skip already-authenticated or disconnected connections + if (!ns.Running || ns.Account != null) + { + _connectingQueue.Dequeue(); + continue; + } + + // If the socket has been connected for less than the limit, we can stop + // (queue is ordered by connection time, so remaining entries are newer) + if (now - ns.ConnectedOn < ConnectingSocketIdleLimit) + { + break; + } + + _connectingQueue.Dequeue(); + + // Socket must have finished the entire authentication process or be forcibly disconnected + if (!ns.SentFirstPacket || !ns.Seeded) + { + ns.Disconnect(null); + } + } + } + + public static void FlushAll() + { + while (_flushPending.TryDequeue(out var ns)) + { + if (ns == null) + { + continue; + } + + // Reset flag to allow re-queueing if more data is added later + ns._flushQueued = false; + + if (ns.Running) + { + ns._socket?.QueueSend(); + } + } + + // Submit any pending operations + _socketManager?.Submit(); + } + + public static void Slice() + { + DisconnectUnattachedSockets(); + + // Process throttled states + while (_throttled.Count > 0) + { + var ns = _throttled.Dequeue(); + if (ns.Running) + { + ns.HandleReceive(true); + } + } + + // This is enqueued by HandleReceive if already throttled and still throttled + while (_throttledPending.Count > 0) + { + _throttled.Enqueue(_throttledPending.Dequeue()); + } + + // Process all completions through the manager FIRST + // This ensures DataReceived events are processed and HandleReceive runs, + // which may call Send() and add to _flushPending + var eventCount = _socketManager.ProcessCompletions(_events); + + for (var i = 0; i < eventCount; i++) + { + ref var evt = ref _events[i]; + + switch (evt.Type) + { + case RingSocketEventType.Accept: + { + // Handle accept - AcceptedSocketHandle contains the result + HandleAcceptCompletion((int)evt.AcceptedSocketHandle); + break; + } + + case RingSocketEventType.DataReceived: + { + var nsRecv = _netStates[evt.Socket.Id]; + // Verify generation via object identity to avoid stale completion issues + if (nsRecv != null && nsRecv._socket == evt.Socket) + { + HandleDataReceived(nsRecv, evt.BytesTransferred); + } + break; + } + + case RingSocketEventType.DataSent: + { + var nsSend = _netStates[evt.Socket.Id]; + // Verify generation via object identity + if (nsSend != null && nsSend._socket == evt.Socket) + { + // Update activity check on successful send + nsSend.NextActivityCheck = Core.TickCount + 90000; + } + break; + } + + case RingSocketEventType.Disconnected: + { + var nsDisc = _netStates[evt.Socket.Id]; + // Verify generation via object identity + if (nsDisc != null && nsDisc._socket == evt.Socket) + { + HandleDisconnected(nsDisc); + } + break; + } + } + } + + // Process flush queue AFTER event processing + // This ensures sends triggered by HandleReceive (via packet handlers like SendPlayServerAck) + // are queued in the SAME Slice, not the next one + while (_flushPending.TryDequeue(out var ns)) + { + // Reset flag to allow re-queueing if more data is added later + ns._flushQueued = false; + + if (ns.Running) + { + ns._socket?.QueueSend(); + } + } + + // CRITICAL: Process send queue NOW to post pending sends + // This ensures PostSend() runs and sets SendPending=true BEFORE disconnect checks + // Without this, Disconnect() would see SendPending=false even though data is queued + _socketManager.ProcessSendQueue(); + + // Process pending disconnects AFTER flush queue AND send queue processing + // This ensures the traditional order: Game Logic (Sends/Disconnects) → Receives → Flush → Disconnect + // Any Send() calls made after Disconnect() in the same tick are flushed before disconnect + while (_pendingDisconnects.TryDequeue(out var ns)) + { + // Reset flag to allow re-queueing if reconnect happens + ns._disconnectQueued = false; + + if (ns.Running && ns._socket != null) + { + // RingSocket.Disconnect() handles graceful disconnect: + // - Waits for pending sends to flush (if SendBuffer.ReadableBytes > 0) + // - Waits for in-flight I/O to complete + // - Ensures buffers aren't released while kernel is still using them + ns._socket.Disconnect(); + } + } + + // Submit any queued operations + _socketManager.Submit(); + + // Process disposes + while (_disposed.TryDequeue(out var ns)) + { + ns.Dispose(); + } + } + + private static void HandleDataReceived(NetState ns, int bytesReceived) + { + if (!ns._running) + { + return; + } + + // Data is already committed to buffer by RingSocketManager + // Decode if encryption is enabled + ns.DecryptRecvBuffer(bytesReceived); + + // Process packets + ns.HandleReceive(); + } + + private static void HandleDisconnected(NetState ns) + { + var slotId = ns._socket.Id; + + // IMPORTANT: Check if the slot still points to this NetState + // During quick reconnect, the slot might have been reused for a new connection + var currentNs = _netStates[slotId]; + if (currentNs != ns) + { + // Slot was already reused - don't clear it! + // Just mark this NetState as not running and queue for dispose + ns._running = false; + _disposed.Enqueue(ns); + return; + } + + // Clear the NetState slot + _netStates[slotId] = null; + + // Mark as not running and queue for dispose + ns._running = false; + _disposed.Enqueue(ns); + } + + public static void CheckAllAlive() + { + try + { + var curTicks = Core.TickCount; + + foreach (var ns in Instances) + { + ns.CheckAlive(curTicks); + } + } + catch (Exception ex) + { + TraceException(ex); + } + } +} diff --git a/Projects/Server/Network/NetState/NetState.cs b/Projects/Server/Network/NetState/NetState.cs index f44e6442e..57ab6b4c4 100755 --- a/Projects/Server/Network/NetState/NetState.cs +++ b/Projects/Server/Network/NetState/NetState.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2023 - ModernUO Development Team * + * Copyright 2019-2025 - ModernUO Development Team * * Email: hi@modernuo.com * * File: NetState.cs * * * @@ -25,55 +25,46 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Net; -using System.Net.Sockets; using System.Network; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; namespace Server.Network; -public delegate void DecodePacket(Span buffer, ref int length); -public delegate int EncodePacket(ReadOnlySpan inputBuffer, Span outputBuffer); - public partial class NetState : IComparable, IValueLinkListNode, IDisposable { private static readonly ILogger logger = LogFactory.GetLogger(typeof(NetState)); private static readonly TimeSpan ConnectingSocketIdleLimit = TimeSpan.FromMilliseconds(5000); // 5 seconds - private const int RecvPipeSize = 1024 * 64; - private const int SendPipeSize = 1024 * 256; private const int HuePickerCap = 512; private const int MenuCap = 512; private const int PacketPerSecondThreshold = 3000; - private static readonly GCHandle[] _polledStates = new GCHandle[2048]; - private static readonly IPollGroup _pollGroup = PollGroup.Create(); private static readonly Queue _flushPending = new(2048); - private static readonly Queue _flushedPartials = new(256); + private static readonly Queue _pendingDisconnects = new(256); // Processed AFTER flush private static readonly ConcurrentQueue _disposed = new(); private static readonly Queue _throttled = new(256); private static readonly Queue _throttledPending = new(256); - private static readonly SortedSet _connecting = new(NetStateConnectingComparer.Instance); + private static readonly Queue _connectingQueue = new(2048); private static readonly HashSet _instances = new(2048); public static IReadOnlySet Instances => _instances; private readonly string _toString; private ClientVersion _version; private bool _running = true; - private volatile DecodePacket _packetDecoder; - private volatile EncodePacket _packetEncoder; + private IClientEncryption _encryption; private bool _flushQueued; + private bool _disconnectQueued; // Queued for disconnect processing (after flush) private long[] _packetThrottles; private long[] _packetCounts; private string _disconnectReason = string.Empty; internal ParserState _parserState = ParserState.AwaitingNextPacket; internal ProtocolState _protocolState = ProtocolState.AwaitingSeed; - internal GCHandle _handle; private bool _packetLogging; - public GCHandle Handle => _handle; + // Managed socket with buffers (handles lifecycle automatically) + internal RingSocket _socket; // Speed Hack Prevention internal long _movementCredit; @@ -108,6 +99,9 @@ public partial class NetState : IComparable, IValueLinkListNode, IValueLinkListNode, IValueLinkListNode _packetDecoder; - set => _packetDecoder = value; - } - - public EncodePacket PacketEncoder - { - get => _packetEncoder; - set => _packetEncoder = value; + get => _encryption; + set => _encryption = value; } public int CurrentPacket { get; internal set; } @@ -210,13 +177,27 @@ public partial class NetState : IComparable, IValueLinkListNode _running; - public Socket Connection { get; private set; } + /// + /// Gets whether the socket is connected. + /// + public bool IsConnected => _running && _socket != null; + + /// + /// Gets the socket handle. + /// + public nint SocketHandle => _socket?.Handle ?? 0; + + /// + /// Gets the local endpoint (address/port) the client connected to. + /// + public IPEndPoint LocalEndPoint => _socket != null ? SocketHelper.GetLocalEndPoint(_socket.Handle) : null; + + /// + /// Gets the send buffer for this connection. + /// + internal IORingBuffer SendBuffer => _socket?.SendBuffer; public bool CompressionEnabled { get; set; } @@ -235,15 +216,7 @@ public partial class NetState : IComparable, IValueLinkListNode _account; - set - { - if (_account != null) - { - _connecting.Remove(this); - } - - _account = value; - } + set => _account = value; } public string Assistant { get; set; } @@ -464,8 +437,14 @@ public partial class NetState : IComparable, IValueLinkListNode.Empty; + return false; + } + + buffer = _socket.SendBuffer.GetWriteSpan(); + return buffer.Length > 0; } public void Send(ReadOnlySpan span) @@ -483,21 +462,25 @@ public partial class NetState : IComparable, IValueLinkListNode, IValueLinkListNode, IValueLinkListNode, IValueLinkListNode, IValueLinkListNode, IValueLinkListNode 0) - { - NextActivityCheck = Core.TickCount + 90000; - reader.Advance((uint)bytesWritten); - } - - return bytesWritten == buffer.Length; - } - - private void DecodePacket(Span buffer, ref int length) - { - _packetDecoder?.Invoke(buffer, ref length); - } - - private void ReceiveData() - { - var writer = RecvPipe.Writer; - var buffer = writer.AvailableToWrite(); - - if (writer.IsClosed || buffer.Length == 0) - { - return; - } - - var bytesWritten = 0; - - try - { - bytesWritten = Connection.Receive(buffer, SocketFlags.None); - } - catch (SocketException ex) - { - if (ex.ErrorCode is not 54 and not 89 and not 995) - { - logger.Debug(ex, "Disconnected due to a socket exception"); - } - - Disconnect(string.Empty); - } - catch (Exception ex) - { - Disconnect($"Disconnected with error: {ex}"); - TraceException(ex); - } - - if (bytesWritten <= 0) - { - Disconnect(string.Empty); - return; - } - - DecodePacket(buffer, ref bytesWritten); - - writer.Advance((uint)bytesWritten); - NextActivityCheck = Core.TickCount + 90000; - } - - private static void DisconnectUnattachedSockets() - { - var now = Core.Now; - - // Clear out any sockets that have been connecting for too long - while (_connecting.Count > 0) - { - var ns = _connecting.Min; - var socketTime = ns.ConnectedOn; - - // If the socket has been connected for less than the limit, we can stop checking - if (now - socketTime < ConnectingSocketIdleLimit) - { - break; - } - - // Socket must have finished the entire authentication process or be forcibly disconnected. - if (!ns.Running || !ns.SentFirstPacket || !ns.Seeded || ns.Account == null) - { - // Not sending a message because it will fill up the logs. - ns.Disconnect(null); - } - - _connecting.Remove(ns); - } - } - - public static void FlushAll() - { - while (_flushPending.Count != 0) - { - _flushPending.Dequeue()?.Flush(); - } - } - - public static void Slice() - { - DisconnectUnattachedSockets(); - - while (_throttled.Count > 0) - { - var ns = _throttled.Dequeue(); - if (ns.Running) - { - ns.HandleReceive(true); - } - } - - // This is enqueued by HandleReceive if already throttled and still throttled - while (_throttledPending.Count > 0) - { - _throttled.Enqueue(_throttledPending.Dequeue()); - } - - var count = _pollGroup.Poll(_polledStates); - - if (count > 0) - { - for (var i = 0; i < count; i++) - { - (_polledStates[i].Target as NetState)?.HandleReceive(); - _polledStates[i] = default; - } - } - - while (_flushPending.TryDequeue(out var ns)) - { - if (!ns.Flush()) - { - // Incomplete data, so we need to requeue - _flushedPartials.Enqueue(ns); - } - } - - var hasDisposes = false; - while (_disposed.TryDequeue(out var ns)) - { - hasDisposes = true; - ns.Dispose(); - } - - // If they weren't disconnected, requeue them - while (_flushedPartials.TryDequeue(out var ns)) - { - if (ns.Running) - { - _flushPending.Enqueue(ns); - } - } - - if (hasDisposes) - { - _pollGroup.Poll(_polledStates.Length); - } - } - public void CheckAlive(long curTicks) { - if (Connection != null && NextActivityCheck - curTicks < 0) + if (_socket != null && NextActivityCheck - curTicks < 0) { LogInfo("Disconnecting due to inactivity..."); Disconnect("Disconnecting due to inactivity."); } } - public static void CheckAllAlive() - { - try - { - var curTicks = Core.TickCount; - - foreach (var ns in Instances) - { - ns.CheckAlive(curTicks); - } - } - catch (Exception ex) - { - TraceException(ex); - } - } - public void Trace(ReadOnlySpan buffer) { // We don't have data, so nothing to trace @@ -1105,17 +987,24 @@ public partial class NetState : IComparable, IValueLinkListNode + /// Requests a graceful disconnect. The disconnect is queued and processed after the flush + /// queue in Slice(), ensuring Send() calls made in the same tick are processed first. + /// public void Disconnect(string reason) { - if (!_running) + if (!_running || _socket == null) { return; } - _running = false; - _disconnectReason = reason; - _disposed.Enqueue(this); + + if (!_disconnectQueued) + { + _disconnectQueued = true; + _pendingDisconnects.Enqueue(this); + } } public static void TraceDisconnect(string reason, string ip) @@ -1148,16 +1037,18 @@ public partial class NetState : IComparable, IValueLinkListNode, IValueLinkListNode= 0 && slotId < _netStates.Length && _netStates[slotId] == this) { - _pollGroup.Remove(Connection, _handle); - } - catch (Exception ex) - { - TraceException(ex); + _netStates[slotId] = null; } - Connection.Close(); - _handle.Free(); - RecvPipe.Dispose(); - SendPipe.Dispose(); + // Note: RingSocketManager handles cleanup of ring resources (unregister, close, buffer release) + // when it processes the disconnect event. We just clear our reference. + _socket = null; Mobile = null; @@ -1192,46 +1079,9 @@ public partial class NetState : IComparable, IValueLinkListNode - { - public static readonly IComparer Instance = new NetStateConnectingComparer(); - - public int Compare(NetState x, NetState y) - { - if (x == null && y == null) - { - return 0; - } - - if (x == null) - { - return -1; - } - - if (y == null) - { - return 1; - } - - if (ReferenceEquals(x, y)) - { - return 0; - } - - var connectedOn = x.ConnectedOn.CompareTo(y.ConnectedOn); - if (connectedOn != 0) - { - return connectedOn; - } - - return x.CompareTo(y); - } - } } diff --git a/Projects/Server/Network/Packets/OutgoingPackets.cs b/Projects/Server/Network/Packets/OutgoingPackets.cs index 18e95f796..ce2cce0f4 100644 --- a/Projects/Server/Network/Packets/OutgoingPackets.cs +++ b/Projects/Server/Network/Packets/OutgoingPackets.cs @@ -7,7 +7,7 @@ public static class OutgoingPackets [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool CannotSendPackets(this NetState ns) => // Do not check for NetState.Running. Packets are sent to a "disconnected" socket as part of the OnDisconnect events - // up until the Connection is nulled. Closing the connection is done synchronously, therefore packets will not be sent + // up until the socket is closed. Closing the connection is done synchronously, therefore packets will not be sent // once the Mobile.NetState is null. - ns?.Connection == null || ns.BlockAllPackets; + ns == null || ns.SocketHandle == 0 || ns.BlockAllPackets; } diff --git a/Projects/Server/Network/PingServer.cs b/Projects/Server/Network/PingServer.cs index 41a59468b..dc80288c4 100644 --- a/Projects/Server/Network/PingServer.cs +++ b/Projects/Server/Network/PingServer.cs @@ -61,7 +61,7 @@ public static class PingServer if (ipep.Address.Equals(IPAddress.Any) || ipep.Address.Equals(IPAddress.IPv6Any)) { - listeningAddresses.UnionWith(TcpServer.GetListeningAddresses(ipep)); + listeningAddresses.UnionWith(NetState.GetListeningAddresses(ipep)); } else { diff --git a/Projects/Server/Network/Pipe.cs b/Projects/Server/Network/Pipe.cs deleted file mode 100644 index a1c998ee0..000000000 --- a/Projects/Server/Network/Pipe.cs +++ /dev/null @@ -1,519 +0,0 @@ -/************************************************************************* - * ModernUO * - * Copyright 2019-2023 - 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 . * - *************************************************************************/ - -using System; -using System.IO; -using System.Runtime.InteropServices; - -namespace Server.Network; - -public partial class Pipe : IDisposable -{ - public class PipeWriter - { - private readonly Pipe _pipe; - - internal PipeWriter(Pipe pipe) => _pipe = pipe; - - public unsafe Span AvailableToWrite() - { - var read = _pipe._readIdx; - var write = _pipe._writeIdx; - - uint sz; - if (read <= write) - { - sz = _pipe.Size - write + read - 1; - } - else - { - sz = read - write - 1; - } - - return new Span((void*)(_pipe._buffer + write), (int)sz); - } - - public void Advance(uint count) - { - var read = _pipe._readIdx; - var write = _pipe._writeIdx; - - if (count == 0) - { - return; - } - - if (count > _pipe.Size - 1) - { - throw new EndOfPipeException("Unable to advance beyond the end of the pipe."); - } - - if (read <= write) - { - if (count > read + _pipe.Size - write - 1) - { - throw new EndOfPipeException("Unable to advance beyond the end of the pipe."); - } - - var sz = Math.Min(count, _pipe.Size - write); - - write += sz; - if (write > _pipe.Size - 1) - { - write = 0; - } - count -= sz; - - if (count > 0) - { - if (count >= read) - { - throw new EndOfPipeException("Unable to advance beyond the end of the pipe."); - } - - write = count; - } - } - else - { - if (count > read - write - 1) - { - throw new EndOfPipeException("Unable to advance beyond the end of the pipe."); - } - - write += count; - } - - // It's never valid to advance the write pointer to become equal to - // the read pointer. Check that here. - if (write == read) - { - throw new EndOfPipeException("Unable to advance beyond the end of the pipe."); - } - - _pipe._writeIdx = write; - } - - public void Close() => _pipe._closed = true; - - public bool IsClosed => _pipe._closed; - } - - public class PipeReader - { - private readonly Pipe _pipe; - - internal PipeReader(Pipe pipe) => _pipe = pipe; - - public unsafe Span AvailableToRead() - { - var read = _pipe._readIdx; - var write = _pipe._writeIdx; - - uint sz; - if (read <= write) - { - sz = write - read; - } - else - { - sz = _pipe.Size - read + write; - } - - return new Span((void*)(_pipe._buffer + read), (int)sz); - } - - public void Advance(uint count) - { - var read = _pipe._readIdx; - var write = _pipe._writeIdx; - - if (read <= write) - { - if (count > write - read) - { - throw new EndOfPipeException("Unable to advance beyond the end of the pipe."); - } - - read += count; - } - else - { - var sz = Math.Min(count, _pipe.Size - read); - - read += sz; - if (read > _pipe.Size - 1) - { - read = 0; - } - count -= sz; - - if (count > 0) - { - if (count > write) - { - throw new EndOfPipeException("Unable to advance beyond the end of the pipe."); - } - - read = count; - } - } - - if (read == write) - { - // If the read pointer catches up to the write pointer, then the pipe is empty. - // As a performance optimization, set both to 0. This should improve the chances cache lines are hit. - _pipe._readIdx = 0; - _pipe._writeIdx = 0; - } - else - { - _pipe._readIdx = read; - } - } - - public void Close() => _pipe._closed = true; - - public bool IsClosed => _pipe._closed; - } - - private IntPtr _handle; // Doubles as the file descriptor for linux/darwin - private IntPtr _buffer; - private readonly uint _bufferSize; - private uint _writeIdx; - private uint _readIdx; - private bool _closed; - - public PipeWriter Writer { get; } - public PipeReader Reader { get; } - - public uint Size => _bufferSize; - - public bool Closed => _closed; - - public Pipe(uint size) - { - var pageSize = (uint)Environment.SystemPageSize; - - // Virtual allocation requires multiples of system page size - // So let's adjust the requested size rounded to the next available page size - var adjustedSize = (size + pageSize - 1) & ~(pageSize - 1); - - if (Core.IsWindows) - { - // Reserve a region of virtual memory. We need twice the size so we can later mirror. - var region = NativeMethods_Windows.VirtualAlloc2( - IntPtr.Zero, - IntPtr.Zero, - adjustedSize * 2, - NativeMethods_Windows.MEM_RESERVE | NativeMethods_Windows.MEM_RESERVE_PLACEHOLDER, - NativeMethods_Windows.PAGE_NOACCESS, - IntPtr.Zero, - 0 - ); - - if (region == IntPtr.Zero) - { - throw new InvalidOperationException($"Allocating virtual memory failed. ({Marshal.GetLastPInvokeError()})"); - } - - // Releases half of the region so we can map the same memory region twice - var freed = NativeMethods_Windows.VirtualFree( - region, - adjustedSize, - NativeMethods_Windows.MEM_RELEASE | NativeMethods_Windows.MEM_PRESERVE_PLACEHOLDER - ); - - if (!freed) - { - throw new InvalidOperationException($"Creating virtual placeholder failed. ({Marshal.GetLastPInvokeError()})"); - } - - // Create a file descriptor - _handle = NativeMethods_Windows.CreateFileMappingW( - NativeMethods_Windows.InvalidHandleValue, - IntPtr.Zero, - NativeMethods_Windows.PAGE_READWRITE, - 0, - adjustedSize, - null - ); - - if (_handle == IntPtr.Zero) - { - throw new InvalidOperationException($"Creating file mapping failed. ({Marshal.GetLastPInvokeError()})"); - } - - // Map the region to the first half of the virtual space - _buffer = NativeMethods_Windows.MapViewOfFile3( - _handle, - IntPtr.Zero, - region, - 0, - adjustedSize, - NativeMethods_Windows.MEM_REPLACE_PLACEHOLDER, - NativeMethods_Windows.PAGE_READWRITE, - IntPtr.Zero, - 0 - ); - - if (_buffer == IntPtr.Zero) - { - throw new InvalidOperationException($"Mapping file view failed. ({Marshal.GetLastPInvokeError()})"); - } - - // Map the same region to the second half of the virtual space - var view2 = NativeMethods_Windows.MapViewOfFile3( - _handle, - IntPtr.Zero, - new IntPtr(_buffer + adjustedSize), - 0, - adjustedSize, - NativeMethods_Windows.MEM_REPLACE_PLACEHOLDER, - NativeMethods_Windows.PAGE_READWRITE, - IntPtr.Zero, - 0 - ); - - if (view2 == IntPtr.Zero) - { - throw new InvalidOperationException($"Mapping file view mirror failed. ({Marshal.GetLastPInvokeError()})"); - } - } - else if (Core.IsLinux || Core.IsDarwin) - { - var anon = Core.IsLinux ? NativeMethods_Linux.MAP_ANONYMOUS : NativeMethods_Linux.MAP_ANON; - - int fd; - - if (Core.IsLinux) - { - // Create a memory-backed file descriptor - fd = NativeMethods_Linux.memfd_create("mirrored_ring_buffer", 0); - } - else - { - var fdName = $"/muo/ring/{GetHashCode()}"; - fd = NativeMethods_Linux.shm_open(fdName, NativeMethods_Linux.O_CREAT | NativeMethods_Linux.O_RDWR, 0600); - - // Unlink immediately to emulate memfd_create() functionality - NativeMethods_Linux.shm_unlink(fdName); - } - - if (fd == NativeMethods_Linux.InvalidPtrValue) - { - throw new InvalidOperationException($"Creating file descriptor failed. ({Marshal.GetLastPInvokeError()})"); - } - - // Set the size of the file descriptor - if (NativeMethods_Linux.ftruncate(fd, (int)adjustedSize) != 0) - { - throw new InvalidOperationException($"Setting file descriptor size failed. ({Marshal.GetLastPInvokeError()})"); - } - - // Get virtual address space, must be double the size so we can map twice - _buffer = NativeMethods_Linux.mmap(IntPtr.Zero, adjustedSize * 2, - NativeMethods_Linux.PROT_READ | NativeMethods_Linux.PROT_WRITE, - NativeMethods_Linux.MAP_PRIVATE | anon, NativeMethods_Linux.InvalidFileDescriptor, 0); - - if (_buffer == NativeMethods_Linux.InvalidPtrValue) - { - throw new InsufficientMemoryException($"Allocating virtual memory failed. ({Marshal.GetLastPInvokeError()})"); - } - - // Map the file descriptor to the first half of the virtual space - var view1 = NativeMethods_Linux.mmap(_buffer, adjustedSize, - NativeMethods_Linux.PROT_READ | NativeMethods_Linux.PROT_WRITE, - NativeMethods_Linux.MAP_SHARED | NativeMethods_Linux.MAP_FIXED, fd, 0); - - if (view1 == NativeMethods_Linux.InvalidPtrValue) - { - throw new InvalidOperationException($"Mapping memory failed. ({Marshal.GetLastPInvokeError()})"); - } - - // Map the file descriptor to the second half of the virtual space - var view2 = NativeMethods_Linux.mmap(new IntPtr(_buffer + adjustedSize), adjustedSize, - NativeMethods_Linux.PROT_READ | NativeMethods_Linux.PROT_WRITE, - NativeMethods_Linux.MAP_SHARED | NativeMethods_Linux.MAP_FIXED, fd, 0); - - if (view2 == NativeMethods_Linux.InvalidPtrValue) - { - throw new InvalidOperationException($"Mapping mirrored memory failed. ({Marshal.GetLastPInvokeError()})"); - } - - _handle = fd; - } - - _bufferSize = adjustedSize; - _writeIdx = 0; - _readIdx = 0; - _closed = false; - - Writer = new PipeWriter(this); - Reader = new PipeReader(this); - } - - private static partial class NativeMethods_Windows - { - private const string Kernel32 = "kernel32.dll"; - private const string KernelBase = "kernelbase.dll"; - public const IntPtr InvalidHandleValue = -1; - - [LibraryImport(Kernel32, SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] - public static partial IntPtr CreateFileMappingW( - IntPtr hFile, IntPtr lpFileMappingAttributes, uint flProtect, uint dwMaximumSizeHigh, uint dwMaximumSizeLow, - string lpName - ); - - [LibraryImport(KernelBase, SetLastError = true)] - public static partial IntPtr MapViewOfFile3( - IntPtr hFileMappingObject, IntPtr processHandle, IntPtr pvBaseAddress, ulong ullOffset, ulong ullSize, - uint allocFlags, uint dwDesiredAccess, - IntPtr hExtendedParameter, int parameterCount - ); - - [LibraryImport(Kernel32, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static partial bool UnmapViewOfFile(IntPtr lpBaseAddress); - - [LibraryImport(Kernel32, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static partial bool CloseHandle(IntPtr hObject); - - [LibraryImport(KernelBase, SetLastError = true)] - public static partial IntPtr VirtualAlloc2( - IntPtr process, - IntPtr address, - ulong size, - uint allocationType, - uint protect, - IntPtr extendedParameters, - uint parameterCount - ); - - [LibraryImport(Kernel32, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static partial bool VirtualFree(IntPtr lpAddress, uint dwSize, uint dwFreeType); - - public const uint MEM_PRESERVE_PLACEHOLDER = 0x02; - public const uint MEM_RESERVE = 0x2000; - public const uint MEM_REPLACE_PLACEHOLDER = 0x4000; - public const uint MEM_RELEASE = 0x8000; - public const uint MEM_RESERVE_PLACEHOLDER = 0x40000; - public const uint PAGE_NOACCESS = 0x01; - public const uint PAGE_READWRITE = 0x04; - } - - private static partial class NativeMethods_Linux - { - private const string LibC = "libc"; - public const IntPtr InvalidPtrValue = -1; - public const int InvalidFileDescriptor = -1; - - // For MacOS - [LibraryImport(LibC, SetLastError = true, StringMarshalling = StringMarshalling.Utf8)] - public static partial int shm_open(string name, int oflag, int mode); - - [LibraryImport(LibC, SetLastError = true, StringMarshalling = StringMarshalling.Utf8)] - public static partial int shm_unlink(string name); - - [LibraryImport(LibC, SetLastError = true, StringMarshalling = StringMarshalling.Utf8)] - public static partial int memfd_create(string name, uint flags); - - [LibraryImport(LibC, SetLastError = true)] - public static partial int ftruncate(int fd, int length); - - [LibraryImport(LibC, SetLastError = true)] - public static partial int close(int fd); - - [LibraryImport(LibC, SetLastError = true)] - public static partial IntPtr mmap(IntPtr addr, ulong length, int prot, int flags, int fd, int offset); - - [LibraryImport(LibC, SetLastError = true)] - public static partial int munmap(IntPtr addr, ulong length); - - public const int PROT_READ = 0x1; - public const int PROT_WRITE = 0x2; - public const int MAP_PRIVATE = 0x02; - public const int MAP_SHARED = 0x01; - public const int MAP_FIXED = 0x10; - public const int MAP_ANONYMOUS = 0x20; - - // Darwin - public const int O_RDWR = 0x2; - public const int O_CREAT = 0x200; - public const int MAP_ANON = 0x1000; - } - - private void ReleaseUnmanagedResources() - { - if (_buffer == IntPtr.Zero) - { - return; - } - - if (Core.IsWindows) - { - if (_handle != IntPtr.Zero) - { - NativeMethods_Windows.CloseHandle(_handle); - _handle = IntPtr.Zero; - } - - if (_buffer != IntPtr.Zero) - { - NativeMethods_Windows.UnmapViewOfFile(_buffer); - NativeMethods_Windows.UnmapViewOfFile(new IntPtr(_buffer + _bufferSize)); - } - } - else if (Core.IsLinux || Core.IsDarwin) - { - if (_handle != NativeMethods_Linux.InvalidFileDescriptor) - { -#pragma warning disable CA2020 - NativeMethods_Linux.close((int)_handle); -#pragma warning restore CA2020 - - _handle = NativeMethods_Linux.InvalidFileDescriptor; - } - - if (_buffer != IntPtr.Zero) - { - NativeMethods_Linux.munmap(_buffer, _bufferSize); - NativeMethods_Linux.munmap(new IntPtr(_buffer + _bufferSize), _bufferSize); - } - } - - _buffer = IntPtr.Zero; - } - - public void Dispose() - { - ReleaseUnmanagedResources(); - GC.SuppressFinalize(this); - } - - ~Pipe() - { - ReleaseUnmanagedResources(); - } -} - -public class EndOfPipeException : IOException -{ - public EndOfPipeException(string message) : base(message) - { - } -} diff --git a/Projects/Server/Network/SocketHelper.cs b/Projects/Server/Network/SocketHelper.cs new file mode 100644 index 000000000..fc779f592 --- /dev/null +++ b/Projects/Server/Network/SocketHelper.cs @@ -0,0 +1,218 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2025 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: SocketHelper.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; +using System.Net; +using System.Net.Sockets; +using System.Runtime.InteropServices; + +namespace Server.Network; + +/// +/// Platform-specific socket utilities for working with raw socket handles. +/// +public static partial class SocketHelper +{ + /// + /// Gets the remote IP address from a socket handle. + /// + /// The socket handle. + /// The remote IP address, or null if unable to retrieve. + public static IPAddress GetRemoteAddress(nint socket) + { + if (socket is 0 or -1) + { + return null; + } + + try + { + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? GetRemoteAddressWindows(socket) + : GetRemoteAddressUnix(socket); + } + catch + { + return null; + } + } + + private static unsafe IPAddress GetRemoteAddressWindows(nint socket) + { + Span buffer = stackalloc byte[128]; + var len = buffer.Length; + + fixed (byte* ptr = buffer) + { + if (getpeername(socket, ptr, ref len) != 0) + { + return null; + } + } + + return ParseSockAddr(buffer[..len]); + } + + private static unsafe IPAddress GetRemoteAddressUnix(nint socket) + { + Span buffer = stackalloc byte[128]; + var len = (uint)buffer.Length; + + fixed (byte* ptr = buffer) + { + if (getpeername_unix(socket, ptr, ref len) != 0) + { + return null; + } + } + + return ParseSockAddr(buffer[..(int)len]); + } + + /// + /// Gets the local endpoint from a socket handle. + /// + /// The socket handle. + /// The local endpoint, or null if unable to retrieve. + public static IPEndPoint GetLocalEndPoint(nint socket) + { + if (socket is 0 or -1) + { + return null; + } + + try + { + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? GetLocalEndPointWindows(socket) + : GetLocalEndPointUnix(socket); + } + catch + { + return null; + } + } + + private static unsafe IPEndPoint GetLocalEndPointWindows(nint socket) + { + Span buffer = stackalloc byte[128]; + var len = buffer.Length; + + fixed (byte* ptr = buffer) + { + if (getsockname(socket, ptr, ref len) != 0) + { + return null; + } + } + + return ParseSockAddrEndPoint(buffer[..len]); + } + + private static unsafe IPEndPoint GetLocalEndPointUnix(nint socket) + { + Span buffer = stackalloc byte[128]; + var len = (uint)buffer.Length; + + fixed (byte* ptr = buffer) + { + if (getsockname_unix(socket, ptr, ref len) != 0) + { + return null; + } + } + + return ParseSockAddrEndPoint(buffer[..(int)len]); + } + + private static IPAddress ParseSockAddr(ReadOnlySpan buffer) + { + if (buffer.Length < 2) + { + return null; + } + + // macOS/BSD: sockaddr has sin_len (1 byte) + sin_family (1 byte) + // Linux: sockaddr has sa_family (2 bytes) + var isBsd = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || + RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD); + var family = isBsd + ? (AddressFamily)buffer[1] // BSD: family is second byte + : (AddressFamily)BitConverter.ToInt16(buffer); // Linux: family is first 2 bytes + + if (family == AddressFamily.InterNetwork && buffer.Length >= 8) + { + // IPv4: family (2) + port (2) + addr (4) + return new IPAddress(buffer.Slice(4, 4)); + } + + if (family == AddressFamily.InterNetworkV6 && buffer.Length >= 28) + { + // IPv6: family (2) + port (2) + flowinfo (4) + addr (16) + scope (4) + return new IPAddress(buffer.Slice(8, 16)); + } + + return null; + } + + private static IPEndPoint ParseSockAddrEndPoint(ReadOnlySpan buffer) + { + if (buffer.Length < 4) + { + return null; + } + + // macOS/BSD: sockaddr has sin_len (1 byte) + sin_family (1 byte) + // Linux: sockaddr has sa_family (2 bytes) + var isBsd = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || + RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD); + var family = isBsd + ? (AddressFamily)buffer[1] // BSD: family is second byte + : (AddressFamily)BitConverter.ToInt16(buffer); // Linux: family is first 2 bytes + + // Port is in network byte order (big-endian), at offset 2 on both platforms + var port = (buffer[2] << 8) | buffer[3]; + + if (family == AddressFamily.InterNetwork && buffer.Length >= 8) + { + // IPv4: family (2) + port (2) + addr (4) + return new IPEndPoint(new IPAddress(buffer.Slice(4, 4)), port); + } + + if (family == AddressFamily.InterNetworkV6 && buffer.Length >= 28) + { + // IPv6: family (2) + port (2) + flowinfo (4) + addr (16) + scope (4) + return new IPEndPoint(new IPAddress(buffer.Slice(8, 16)), port); + } + + return null; + } + + // Windows getpeername + [LibraryImport("ws2_32.dll", SetLastError = true)] + private static unsafe partial int getpeername(nint s, byte* name, ref int namelen); + + // Unix/Linux getpeername + [LibraryImport("libc", EntryPoint = "getpeername", SetLastError = true)] + private static unsafe partial int getpeername_unix(nint sockfd, byte* addr, ref uint addrlen); + + // Windows getsockname + [LibraryImport("ws2_32.dll", SetLastError = true)] + private static unsafe partial int getsockname(nint s, byte* name, ref int namelen); + + // Unix/Linux getsockname + [LibraryImport("libc", EntryPoint = "getsockname", SetLastError = true)] + private static unsafe partial int getsockname_unix(nint sockfd, byte* addr, ref uint addrlen); +} diff --git a/Projects/Server/Network/TcpServer.cs b/Projects/Server/Network/TcpServer.cs deleted file mode 100644 index 5e5fe3a1b..000000000 --- a/Projects/Server/Network/TcpServer.cs +++ /dev/null @@ -1,254 +0,0 @@ -/************************************************************************* - * ModernUO * - * Copyright 2019-2024 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: TcpServer.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; -using System.Buffers.Binary; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.NetworkInformation; -using System.Net.Sockets; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using Server.Logging; - -namespace Server.Network; - -public static class TcpServer -{ - private static readonly ILogger logger = LogFactory.GetLogger(typeof(TcpServer)); - - // AccountLoginReject BadComm - private static readonly byte[] _socketRejected = [0x82, 0xFF]; - - public static IPEndPoint[] ListeningAddresses { get; private set; } - public static Socket[] Listeners { get; private set; } - - private static IPRateLimiter _ipRateLimiter; - - public static void Start() - { - _ipRateLimiter = new IPRateLimiter(10, 10000, 1000, 2.0, 3_600_000, Core.ClosingTokenSource.Token); - HashSet listeningAddresses = []; - List listeners = []; - foreach (var ipep in ServerConfiguration.Listeners) - { - var listener = CreateListener(ipep); - if (listener == null) - { - continue; - } - - if (ipep.Address.Equals(IPAddress.Any) || ipep.Address.Equals(IPAddress.IPv6Any)) - { - listeningAddresses.UnionWith(GetListeningAddresses(ipep)); - } - else - { - listeningAddresses.Add(ipep); - } - - listeners.Add(listener); - BeginAcceptingSockets(listener); - } - - foreach (var ipep in listeningAddresses) - { - logger.Information("Listening: {Address}", ipep); - } - - ListeningAddresses = listeningAddresses.ToArray(); - Listeners = listeners.ToArray(); - } - - public static void Shutdown() - { - foreach (var listener in Listeners) - { - listener.Close(); - } - } - - public static IEnumerable 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 Socket CreateListener(IPEndPoint ipep) - { - var listener = new Socket(ipep.AddressFamily, SocketType.Stream, ProtocolType.Tcp) - { - LingerState = new LingerOption(false, 0), - ExclusiveAddressUse = true, - NoDelay = true, - Blocking = false, - SendBufferSize = 64 * 1024, - ReceiveBufferSize = 64 * 1024 - }; - - try - { - listener.Bind(ipep); - listener.Listen(256); - return listener; - } - catch (SocketException se) - { - // WSAEADDRINUSE - if (se.ErrorCode == 10048) - { - logger.Warning("Listener: {Address} Exception: {Reason}", ipep, "Currently in use"); - } - // WSAEADDRNOTAVAIL - else if (se.ErrorCode == 10049) - { - logger.Warning("Listener {Address} Exception: {Reason}", ipep, "Unavailable"); - } - else - { - logger.Warning(se, "Listener {Address} Exception: {Reason}", ipep, se.Message); - } - } - - return null; - } - - private static async ValueTask BeginAcceptingSockets(Socket listener) - { - while (!Core.Closing) - { - try - { - var socket = await listener.AcceptAsync(Core.ClosingTokenSource.Token); - var remoteIP = ((IPEndPoint)socket.RemoteEndPoint)!.Address; - - if (!_ipRateLimiter.Verify(remoteIP, out var totalAttempts)) - { - logger.Debug("{Address} Past IP limit threshold ({TotalAttempts})", remoteIP, totalAttempts); - } - else if (Firewall.IsBlocked(remoteIP)) - { - logger.Debug("{Address} Firewalled", remoteIP); - } - else - { - _ = Task.Run(() => ProcessSocketConnection(socket), Core.ClosingTokenSource.Token); - } - } - catch - { - // ignored - } - } - } - - [ThreadStatic] - private static byte[] _firstBytes; - - private static async ValueTask ProcessSocketConnection(Socket socket) - { - _firstBytes ??= GC.AllocateUninitializedArray(128); - - using var cts = CancellationTokenSource.CreateLinkedTokenSource(Core.ClosingTokenSource.Token); - cts.CancelAfter(TimeSpan.FromMilliseconds(500)); - - try - { - var bytesRead = await socket.ReceiveAsync(_firstBytes, SocketFlags.Peek, cts.Token); - - var isValid = - // Sometimes when newer clients are connecting to the game server the first 4 bytes are sent separately - bytesRead == 4 || - - // Support Freeshard Protocol (UOGateway) - bytesRead == 8 && - BinaryPrimitives.ReadUInt32BigEndian(_firstBytes.AsSpan(4)) is 0xF10004FF or 0xF10004FE || - - // Older clients only send the 4 byte seed first then 0x80 - (UOClient.MinRequired == null || UOClient.MinRequired < ClientVersion.Version6050) && - bytesRead >= 66 && _firstBytes[4] == 0x80 || - - // Newer clients - (UOClient.MaxRequired == null || UOClient.MaxRequired >= ClientVersion.Version6050) && ( - // Account Login - 0xEF + 0x80 (83 bytes) - bytesRead >= 83 && _firstBytes[0] == 0xEF && _firstBytes[21] == 0x80 || - bytesRead == 21 && _firstBytes[0] == 0xEF || - // Game Login - 4 bytes + 0x91 (69 bytes) - bytesRead >= 69 && _firstBytes[4] == 0x91 - ); - - // TODO: Validate client version is v4 -> v7 for 0xEF packet - // TODO: Validate Account Login seed matches Game Login seed - // TODO: Validate AuthId for 0x91 packet - // TODO: Validate username is ascii and not empty - // TODO: Validate password is ascii and not empty - if (isValid) - { - var args = new SocketConnectEventArgs(socket); - EventSink.InvokeSocketConnect(args); - - if (args.AllowConnection) - { - Core.LoopContext.Post(() => _ = new NetState(socket), EventLoopContext.Priority.High); - return; - } - - logger.Debug("{Address} Rejected by socket handler", ((IPEndPoint)socket.RemoteEndPoint)!.Address); - - cts.TryReset(); - cts.CancelAfter(TimeSpan.FromMilliseconds(500)); - await socket.SendAsync(_socketRejected, SocketFlags.None, cts.Token); - CloseSocket(socket); - } - else - { - ForceCloseSocket(socket); - } - } - catch - { - ForceCloseSocket(socket); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void CloseSocket(Socket socket) - { - try - { - socket.Shutdown(SocketShutdown.Both); - } - finally - { - socket.Close(); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void ForceCloseSocket(Socket socket) - { - try - { - socket.Disconnect(false); - } - finally - { - socket.Close(0); - } - } -} diff --git a/Projects/Server/Server.csproj b/Projects/Server/Server.csproj index e50db55ff..d80b415c2 100644 --- a/Projects/Server/Server.csproj +++ b/Projects/Server/Server.csproj @@ -21,12 +21,12 @@ - + + - @@ -34,9 +34,9 @@ + - diff --git a/Projects/Server/Utilities/BitMask256.cs b/Projects/Server/Utilities/BitMask256.cs index aed43d4e5..fb01f495e 100644 --- a/Projects/Server/Utilities/BitMask256.cs +++ b/Projects/Server/Utilities/BitMask256.cs @@ -66,10 +66,22 @@ public struct BitMask256 switch (segment) { - case 0: Bits0 |= mask; break; - case 1: Bits1 |= mask; break; - case 2: Bits2 |= mask; break; - case 3: Bits3 |= mask; break; + case 0: + { + Bits0 |= mask; break; + } + case 1: + { + Bits1 |= mask; break; + } + case 2: + { + Bits2 |= mask; break; + } + case 3: + { + Bits3 |= mask; break; + } } } @@ -90,10 +102,22 @@ public struct BitMask256 switch (segment) { - case 0: Bits0 &= ~mask; break; - case 1: Bits1 &= ~mask; break; - case 2: Bits2 &= ~mask; break; - case 3: Bits3 &= ~mask; break; + case 0: + { + Bits0 &= ~mask; break; + } + case 1: + { + Bits1 &= ~mask; break; + } + case 2: + { + Bits2 &= ~mask; break; + } + case 3: + { + Bits3 &= ~mask; break; + } } } diff --git a/Projects/UOContent.Tests/Fixtures/UOContentFixture.cs b/Projects/UOContent.Tests/Fixtures/UOContentFixture.cs index d4c96c7e5..4a1905922 100644 --- a/Projects/UOContent.Tests/Fixtures/UOContentFixture.cs +++ b/Projects/UOContent.Tests/Fixtures/UOContentFixture.cs @@ -26,6 +26,9 @@ public class UOContentFixture : ICollectionFixture, IDisposabl // Load Skills SkillsInfo.Configure(); + // Configure networking (initializes RingSocketManager for tests) + Server.Network.NetState.Configure(); + // Configure / Initialize TestMapDefinitions.ConfigureTestMapDefinitions(); diff --git a/Projects/UOContent.Tests/Tests/Engines/Chat/ChatPacketTests.cs b/Projects/UOContent.Tests/Tests/Engines/Chat/ChatPacketTests.cs index 2f80bc2a1..a3fc311d0 100644 --- a/Projects/UOContent.Tests/Tests/Engines/Chat/ChatPacketTests.cs +++ b/Projects/UOContent.Tests/Tests/Engines/Chat/ChatPacketTests.cs @@ -5,7 +5,7 @@ using Xunit; namespace UOContent.Tests; -[Collection("Sequential Server Tests")] +[Collection("Sequential UOContent Tests")] public class ChatPacketTests { [Theory] @@ -18,7 +18,7 @@ public class ChatPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendChatMessage(lang, number, param1, param2); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } } diff --git a/Projects/UOContent.Tests/Tests/Engines/Help/TestHelpTopicPacket.cs b/Projects/UOContent.Tests/Tests/Engines/Help/TestHelpTopicPacket.cs index 4c724db43..520dfd22e 100644 --- a/Projects/UOContent.Tests/Tests/Engines/Help/TestHelpTopicPacket.cs +++ b/Projects/UOContent.Tests/Tests/Engines/Help/TestHelpTopicPacket.cs @@ -19,7 +19,7 @@ public class TestHelpTopicPacket using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendDisplayHelpTopic(topic, display); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } } diff --git a/Projects/UOContent.Tests/Tests/Engines/ML Quests/MLQuestPacketTests.cs b/Projects/UOContent.Tests/Tests/Engines/ML Quests/MLQuestPacketTests.cs index a36388a95..3f28fc1e3 100644 --- a/Projects/UOContent.Tests/Tests/Engines/ML Quests/MLQuestPacketTests.cs +++ b/Projects/UOContent.Tests/Tests/Engines/ML Quests/MLQuestPacketTests.cs @@ -73,7 +73,7 @@ public class MLQuestPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendRaceChanger(female, race); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -85,7 +85,7 @@ public class MLQuestPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendCloseRaceChanger(); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } } diff --git a/Projects/UOContent.Tests/Tests/Engines/Party/PartyPacketTests.cs b/Projects/UOContent.Tests/Tests/Engines/Party/PartyPacketTests.cs index 63e24f299..99921aea6 100644 --- a/Projects/UOContent.Tests/Tests/Engines/Party/PartyPacketTests.cs +++ b/Projects/UOContent.Tests/Tests/Engines/Party/PartyPacketTests.cs @@ -19,7 +19,7 @@ public class PartyPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendPartyRemoveMember(m); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -40,7 +40,7 @@ public class PartyPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendPartyRemoveMember(member.Serial, p); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -61,7 +61,7 @@ public class PartyPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendPartyMemberList(p); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -78,7 +78,7 @@ public class PartyPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendPartyTextMessage(serial, text, toAll); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -92,7 +92,7 @@ public class PartyPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendPartyInvitation(m); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } } diff --git a/Projects/UOContent.Tests/Tests/Engines/Veteran Rewards/Character Statue Maker/CharacterStatuePacketTests.cs b/Projects/UOContent.Tests/Tests/Engines/Veteran Rewards/Character Statue Maker/CharacterStatuePacketTests.cs index 64d9449de..1dad7688a 100644 --- a/Projects/UOContent.Tests/Tests/Engines/Veteran Rewards/Character Statue Maker/CharacterStatuePacketTests.cs +++ b/Projects/UOContent.Tests/Tests/Engines/Veteran Rewards/Character Statue Maker/CharacterStatuePacketTests.cs @@ -7,7 +7,7 @@ using Xunit; namespace UOContent.Tests; -[Collection("Sequential Server Tests")] +[Collection("Sequential UOContent Tests")] public class CharacterStatuePacketTests { [Theory] @@ -19,7 +19,7 @@ public class CharacterStatuePacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendStatueAnimation((Serial)s, status, anim, frame); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } } diff --git a/Projects/UOContent.Tests/Tests/Items/Books/BookPacketTests.cs b/Projects/UOContent.Tests/Tests/Items/Books/BookPacketTests.cs index e47f56efb..20eabc940 100644 --- a/Projects/UOContent.Tests/Tests/Items/Books/BookPacketTests.cs +++ b/Projects/UOContent.Tests/Tests/Items/Books/BookPacketTests.cs @@ -50,7 +50,7 @@ public class BookPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendBookCover(m, book); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -87,7 +87,7 @@ public class BookPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendBookContent(book); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } } diff --git a/Projects/UOContent.Tests/Tests/Items/Bulletin Boards/BulletinBoardPacketTests.cs b/Projects/UOContent.Tests/Tests/Items/Bulletin Boards/BulletinBoardPacketTests.cs index dd0c60901..99976ad84 100644 --- a/Projects/UOContent.Tests/Tests/Items/Bulletin Boards/BulletinBoardPacketTests.cs +++ b/Projects/UOContent.Tests/Tests/Items/Bulletin Boards/BulletinBoardPacketTests.cs @@ -23,7 +23,7 @@ public class BulletinBoardPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendBBDisplayBoard(bb); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -50,7 +50,7 @@ public class BulletinBoardPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendBBMessage(bb, msg, content); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } } diff --git a/Projects/UOContent.Tests/Tests/Items/Games/Mahjong/MahjongPacketTests.cs b/Projects/UOContent.Tests/Tests/Items/Games/Mahjong/MahjongPacketTests.cs index 3ac722374..af764e668 100644 --- a/Projects/UOContent.Tests/Tests/Items/Games/Mahjong/MahjongPacketTests.cs +++ b/Projects/UOContent.Tests/Tests/Items/Games/Mahjong/MahjongPacketTests.cs @@ -19,7 +19,7 @@ public class MahjongPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendMahjongJoinGame(game); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -39,7 +39,7 @@ public class MahjongPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendMahjongPlayersInfo(game, m); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -57,7 +57,7 @@ public class MahjongPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendMahjongGeneralInfo(game); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -77,7 +77,7 @@ public class MahjongPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendMahjongTilesInfo(game, m); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -97,7 +97,7 @@ public class MahjongPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendMahjongTileInfo(game.Tiles[0], m); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -111,7 +111,7 @@ public class MahjongPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendMahjongRelieve(game); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } } diff --git a/Projects/UOContent.Tests/Tests/Items/Maps/TestMapItemPackets.cs b/Projects/UOContent.Tests/Tests/Items/Maps/TestMapItemPackets.cs index 74e686582..0ce96e82a 100644 --- a/Projects/UOContent.Tests/Tests/Items/Maps/TestMapItemPackets.cs +++ b/Projects/UOContent.Tests/Tests/Items/Maps/TestMapItemPackets.cs @@ -24,7 +24,7 @@ public class TestMapItemPackets (Packet)new MapDetailsNew(mapItem) : new MapDetails(mapItem)).Compile(); ns.SendMapDetails(mapItem); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -42,7 +42,7 @@ public class TestMapItemPackets using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendMapCommand(mapItem, command, x, y, number > 0); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } } diff --git a/Projects/UOContent.Tests/Tests/Items/Misc/Corpses/CorpsePacketTests.cs b/Projects/UOContent.Tests/Tests/Items/Misc/Corpses/CorpsePacketTests.cs index 49c075548..40ad7c6d7 100644 --- a/Projects/UOContent.Tests/Tests/Items/Misc/Corpses/CorpsePacketTests.cs +++ b/Projects/UOContent.Tests/Tests/Items/Misc/Corpses/CorpsePacketTests.cs @@ -26,7 +26,7 @@ public class CorpsePacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendCorpseEquip(m, c); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -50,7 +50,7 @@ public class CorpsePacketTests ns.SendCorpseContent(m, c); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } } diff --git a/Projects/UOContent.Tests/Tests/Items/Weapons/Abilities/WeaponAbilityPacketTests.cs b/Projects/UOContent.Tests/Tests/Items/Weapons/Abilities/WeaponAbilityPacketTests.cs index 88aaa9392..1919ab2e5 100644 --- a/Projects/UOContent.Tests/Tests/Items/Weapons/Abilities/WeaponAbilityPacketTests.cs +++ b/Projects/UOContent.Tests/Tests/Items/Weapons/Abilities/WeaponAbilityPacketTests.cs @@ -20,7 +20,7 @@ public class WeaponAbilityPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendToggleSpecialAbility(abilityId, active); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -32,7 +32,7 @@ public class WeaponAbilityPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendClearWeaponAbility(); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } } diff --git a/Projects/UOContent.Tests/Tests/Multis/Boats/BoatPacketTests.cs b/Projects/UOContent.Tests/Tests/Multis/Boats/BoatPacketTests.cs index 9fc0e05c9..4f5e567b3 100644 --- a/Projects/UOContent.Tests/Tests/Multis/Boats/BoatPacketTests.cs +++ b/Projects/UOContent.Tests/Tests/Multis/Boats/BoatPacketTests.cs @@ -59,7 +59,7 @@ public class BoatPacketTests ns.SendMoveBoatHS(boat, list, d, speed, xOffset, yOffset); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -103,7 +103,7 @@ public class BoatPacketTests ns.SendDisplayBoatHS(beholder, boat); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } diff --git a/Projects/UOContent.Tests/Tests/Multis/Houses/HousePacketTests.cs b/Projects/UOContent.Tests/Tests/Multis/Houses/HousePacketTests.cs index 24b98cafa..912570c11 100644 --- a/Projects/UOContent.Tests/Tests/Multis/Houses/HousePacketTests.cs +++ b/Projects/UOContent.Tests/Tests/Multis/Houses/HousePacketTests.cs @@ -20,7 +20,7 @@ public class HousePacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendBeginHouseCustomization((Serial)serial); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -33,7 +33,7 @@ public class HousePacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendEndHouseCustomization((Serial)serial); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -47,7 +47,7 @@ public class HousePacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendDesignStateGeneral((Serial)serial, revision); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } diff --git a/Projects/UOContent.Tests/Tests/Network/Packets/ArrowPacketTests.cs b/Projects/UOContent.Tests/Tests/Network/Packets/ArrowPacketTests.cs index c51022cbc..6fb16c4f9 100644 --- a/Projects/UOContent.Tests/Tests/Network/Packets/ArrowPacketTests.cs +++ b/Projects/UOContent.Tests/Tests/Network/Packets/ArrowPacketTests.cs @@ -14,7 +14,7 @@ public class ArrowPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendCancelArrow(0, 0, Serial.Zero); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -29,7 +29,7 @@ public class ArrowPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendSetArrow(x, y, Serial.Zero); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -47,7 +47,7 @@ public class ArrowPacketTests ns.ProtocolChanges = ProtocolChanges.HighSeas; ns.SendCancelArrow(x, y, serial); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -65,7 +65,7 @@ public class ArrowPacketTests ns.ProtocolChanges = ProtocolChanges.HighSeas; ns.SendSetArrow(x, y, serial); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } } diff --git a/Projects/UOContent.Tests/Tests/Network/Packets/BuffIconPacketTests.cs b/Projects/UOContent.Tests/Tests/Network/Packets/BuffIconPacketTests.cs index 89e2eccd2..a815bd08b 100644 --- a/Projects/UOContent.Tests/Tests/Network/Packets/BuffIconPacketTests.cs +++ b/Projects/UOContent.Tests/Tests/Network/Packets/BuffIconPacketTests.cs @@ -24,7 +24,7 @@ public class BuffIconPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendAddBuffPacket((Serial)mob, iconID, titleCliloc, secondaryCliloc, args, (int)timeSpan.TotalMilliseconds); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -38,7 +38,7 @@ public class BuffIconPacketTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendRemoveBuffPacket(m, buffIcon); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } } diff --git a/Projects/UOContent.Tests/Tests/Skills/SkillPacketsTests.cs b/Projects/UOContent.Tests/Tests/Skills/SkillPacketsTests.cs index f253c86a9..8dd4e2d25 100644 --- a/Projects/UOContent.Tests/Tests/Skills/SkillPacketsTests.cs +++ b/Projects/UOContent.Tests/Tests/Skills/SkillPacketsTests.cs @@ -27,7 +27,7 @@ public class SkillPacketsTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendSkillChange(skill); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } @@ -45,7 +45,7 @@ public class SkillPacketsTests using var ns = PacketTestUtilities.CreateTestNetState(); ns.SendSkillsUpdate(skills); - var result = ns.SendPipe.Reader.AvailableToRead(); + var result = ns.SendBuffer.GetReadSpan(); AssertThat.Equal(result, expected); } } diff --git a/Projects/UOContent/Misc/ServerList.cs b/Projects/UOContent/Misc/ServerList.cs index 4b00996c7..c8a05a3bf 100644 --- a/Projects/UOContent/Misc/ServerList.cs +++ b/Projects/UOContent/Misc/ServerList.cs @@ -79,14 +79,14 @@ namespace Server.Misc { var ns = e.State; - var ipep = (IPEndPoint)ns.Connection?.LocalEndPoint; - if (ipep == null) + var localEndPoint = ns.LocalEndPoint; + if (localEndPoint == null) { return; } - var localAddress = ipep.Address; - var localPort = ipep.Port; + var localAddress = localEndPoint.Address; + var localPort = localEndPoint.Port; if (_useServerListingAddressConfig) { @@ -94,9 +94,8 @@ namespace Server.Misc } else if (localAddress.IsPrivateNetwork()) { - ipep = (IPEndPoint)ns.Connection.RemoteEndPoint; - - if (ipep == null || !ipep.Address.IsPrivateNetwork() && _publicAddress != null) + // Check if client is from a public network + if (!ns.Address.IsPrivateNetwork() && _publicAddress != null) { localAddress = _publicAddress; } diff --git a/Projects/UOContent/Network/Packets/IncomingAccountPackets.cs b/Projects/UOContent/Network/Packets/IncomingAccountPackets.cs index 2ec4f8059..39005347e 100644 --- a/Projects/UOContent/Network/Packets/IncomingAccountPackets.cs +++ b/Projects/UOContent/Network/Packets/IncomingAccountPackets.cs @@ -384,7 +384,6 @@ public static class IncomingAccountPackets // Comment out these lines to turn off huffman compression state.CompressionEnabled = true; - state.PacketEncoder ??= NetworkCompression.Compress; state.SendSupportedFeature(); state.SendCharacterList(); diff --git a/Projects/UOContent/Special Systems/Engines/PreventInaccess.cs b/Projects/UOContent/Special Systems/Engines/PreventInaccess.cs index 8e45c53a7..cea6a65ad 100644 --- a/Projects/UOContent/Special Systems/Engines/PreventInaccess.cs +++ b/Projects/UOContent/Special Systems/Engines/PreventInaccess.cs @@ -69,7 +69,7 @@ namespace Server.Misc } } - private static bool HasDisconnected(Mobile m) => m.NetState?.Connection == null; + private static bool HasDisconnected(Mobile m) => m.NetState is not { IsConnected: true }; private static LocationInfo GetRandomDestination() => m_Destinations.RandomElement(); diff --git a/README.md b/README.md index 41f54b14a..daec5f817 100644 --- a/README.md +++ b/README.md @@ -71,13 +71,13 @@ ModernUO [![Discord](https://img.shields.io/discord/751317910504603701?logo=disc dnf upgrade --refresh -y # CentOS does not come with EPEL enabled dnf install -y epel-release epel-next-release -dnf install -y findutils libicu libdeflate-devel zstd libargon2-devel +dnf install -y findutils libicu libdeflate-devel zstd libargon2-devel liburing-devel ``` ### Ubuntu, Debian, etc ```shell apt-get update -y -apt-get install -y libicu-dev libdeflate-dev zstd libargon2-dev +apt-get install -y libicu-dev libdeflate-dev zstd libargon2-dev liburing-dev ``` ## OSX Requirements diff --git a/version.json b/version.json index 9a9ad6785..89634f300 100644 --- a/version.json +++ b/version.json @@ -1,4 +1,4 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "0.15.4" + "version": "0.15.5" }