diff --git a/Projects/Server.Tests/Buffers/CircularBufferReaderTests.cs b/Projects/Server.Tests/Buffers/CircularBufferReaderTests.cs new file mode 100644 index 000000000..afd402f08 --- /dev/null +++ b/Projects/Server.Tests/Buffers/CircularBufferReaderTests.cs @@ -0,0 +1,119 @@ +using System; +using System.IO; +using System.Text; +using Server.Network; +using Xunit; + +namespace Server.Tests.Network +{ + public class CircularBufferReaderTests + { + [Theory] + // First only, beginning + [InlineData("Test String", "us-ascii", false, -1, 1024, 1024, 0)] + [InlineData("Test String", "utf-u", false, -1, 1024, 1024, 0)] + [InlineData("Test String", "utf-16BE", false, -1, 1024, 1024, 0)] + [InlineData("Test String", "utf-16", false, -1, 1024, 1024, 0)] + + // Second only + [InlineData("Test String", "us-ascii", false, -1, 1024, 1024, 1030)] + [InlineData("Test String", "utf-u", false, -1, 1024, 1024, 1030)] + [InlineData("Test String", "utf-16BE", false, -1, 1024, 1024, 1030)] + [InlineData("Test String", "utf-16", false, -1, 1024, 1024, 1030)] + + // Split + [InlineData("Test String", "us-ascii", false, -1, 1024, 1024, 1020)] + [InlineData("Test String", "utf-u", false, -1, 1024, 1024, 1020)] + [InlineData("Test String", "utf-16BE", false, -1, 1024, 1024, 1020)] + [InlineData("Test String", "utf-16", false, -1, 1024, 1024, 1020)] + + // First only, beginning, fixed length smaller + [InlineData("Test String", "us-ascii", false, 8, 1024, 1024, 0)] + [InlineData("Test String", "utf-u", false, 8, 1024, 1024, 0)] + [InlineData("Test String", "utf-16BE", false, 8, 1024, 1024, 0)] + [InlineData("Test String", "utf-16", false, 8, 1024, 1024, 0)] + + // Second only, fixed length smaller + [InlineData("Test String", "us-ascii", false, 8, 1024, 1024, 1030)] + [InlineData("Test String", "utf-u", false, 8, 1024, 1024, 1030)] + [InlineData("Test String", "utf-16BE", false, 8, 1024, 1024, 1030)] + [InlineData("Test String", "utf-16", false, 8, 1024, 1024, 1030)] + + // Split, fixed length smaller + [InlineData("Test String", "us-ascii", false, 8, 1024, 1024, 1020)] + [InlineData("Test String", "utf-u", false, 8, 1024, 1024, 1020)] + [InlineData("Test String", "utf-16BE", false, 8, 1024, 1024, 1020)] + [InlineData("Test String", "utf-16", false, 8, 1024, 1024, 1020)] + + // First only, beginning, fixed length bigger + [InlineData("Test String", "us-ascii", false, 20, 1024, 1024, 0)] + [InlineData("Test String", "utf-u", false, 20, 1024, 1024, 0)] + [InlineData("Test String", "utf-16BE", false, 20, 1024, 1024, 0)] + [InlineData("Test String", "utf-16", false, 20, 1024, 1024, 0)] + + // Second only, fixed length bigger + [InlineData("Test String", "us-ascii", false, 20, 1024, 1024, 1030)] + [InlineData("Test String", "utf-u", false, 20, 1024, 1024, 1030)] + [InlineData("Test String", "utf-16BE", false, 20, 1024, 1024, 1030)] + [InlineData("Test String", "utf-16", false, 20, 1024, 1024, 1030)] + + // Split, fixed length bigger + [InlineData("Test String", "us-ascii", false, 20, 1024, 1024, 1020)] + [InlineData("Test String", "utf-u", false, 20, 1024, 1024, 1020)] + [InlineData("Test String", "utf-16BE", false, 20, 1024, 1024, 1020)] + [InlineData("Test String", "utf-16", false, 20, 1024, 1024, 1020)] + public void TestReadString( + string value, + string encodingStr, + bool isSafe, + int fixedLength, + int firstSize, + int secondSize, + int offset + ) + { + Span buffer = stackalloc byte[firstSize + secondSize]; + buffer.Clear(); + + var encoding = EncodingHelpers.GetEncoding(encodingStr); + + var strLength = fixedLength > -1 ? Math.Min(value.Length, fixedLength) : value.Length; + var chars = value.AsSpan(0, strLength); +; + encoding.GetBytes(chars, buffer.Slice(offset)); + + var reader = new CircularBufferReader(buffer.Slice(0, firstSize), buffer.Slice(firstSize)); + reader.Seek(offset, SeekOrigin.Begin); + + var actual = reader.ReadString(encoding, isSafe, fixedLength); + + Assert.Equal(value.Substring(0, strLength), actual); + } + + [Fact] + public void TestReadStringBetween() + { + Span expected = stackalloc byte[19]; + expected[0] = 0x1; + expected[1] = 0x1; + expected[2] = 0x1; + expected[3] = 0x1; + Encoding.ASCII.GetBytes("TestString", expected.Slice(4, 10)); + expected[14] = 0x0; // Null + expected[15] = 0x2; + expected[16] = 0x2; + expected[17] = 0x2; + expected[18] = 0x2; + + var reader = new CircularBufferReader(expected, stackalloc byte[0]); + + var num1 = reader.ReadInt32(); + var str = reader.ReadAscii(); + var num2 = reader.ReadInt32(); + + Assert.Equal(0x01010101, num1); + Assert.Equal("TestString", str); + Assert.Equal(0x02020202, num2); + } + } +} diff --git a/Projects/Server.Tests/Buffers/CircularBufferWriterTests.cs b/Projects/Server.Tests/Buffers/CircularBufferWriterTests.cs new file mode 100644 index 000000000..c85072c07 --- /dev/null +++ b/Projects/Server.Tests/Buffers/CircularBufferWriterTests.cs @@ -0,0 +1,99 @@ +using System; +using System.Buffers; +using System.IO; +using Xunit; + +namespace Server.Tests.Buffers +{ + public class CircularBufferWriterTests + { + [Theory] + // First only, beginning + [InlineData("Test String", "us-ascii", -1, 1024, 1024, 0)] + [InlineData("Test String", "utf-8", -1, 1024, 1024, 0)] + [InlineData("Test String", "utf-16BE", -1, 1024, 1024, 0)] + [InlineData("Test String", "utf-16", -1, 1024, 1024, 0)] + + // Second only + [InlineData("Test String", "us-ascii", -1, 1024, 1024, 1030)] + [InlineData("Test String", "utf-8", -1, 1024, 1024, 1030)] + [InlineData("Test String", "utf-16BE", -1, 1024, 1024, 1030)] + [InlineData("Test String", "utf-16", -1, 1024, 1024, 1030)] + + // Split + [InlineData("Test String", "us-ascii", -1, 1024, 1024, 1020)] + [InlineData("Test String", "utf-8", -1, 1024, 1024, 1020)] + [InlineData("Test String", "utf-16BE", -1, 1024, 1024, 1020)] + [InlineData("Test String", "utf-16", -1, 1024, 1024, 1020)] + + // First only, beginning, fixed length smaller + [InlineData("Test String", "us-ascii", 8, 1024, 1024, 0)] + [InlineData("Test String", "utf-16BE", 8, 1024, 1024, 0)] + [InlineData("Test String", "utf-16", 8, 1024, 1024, 0)] + + // Second only, fixed length smaller + [InlineData("Test String", "us-ascii", 8, 1024, 1024, 1030)] + [InlineData("Test String", "utf-16BE", 8, 1024, 1024, 1030)] + [InlineData("Test String", "utf-16", 8, 1024, 1024, 1030)] + + // Split, fixed length smaller + [InlineData("Test String", "us-ascii", 8, 1024, 1024, 1020)] + [InlineData("Test String", "utf-16BE", 8, 1024, 1024, 1020)] + [InlineData("Test String", "utf-16", 8, 1024, 1024, 1020)] + + // First only, beginning, fixed length bigger + [InlineData("Test String", "us-ascii", 20, 1024, 1024, 0)] + [InlineData("Test String", "utf-16BE", 20, 1024, 1024, 0)] + [InlineData("Test String", "utf-16", 20, 1024, 1024, 0)] + + // Second only, fixed length bigger + [InlineData("Test String", "us-ascii", 20, 1024, 1024, 1030)] + [InlineData("Test String", "utf-16BE", 20, 1024, 1024, 1030)] + [InlineData("Test String", "utf-16", 20, 1024, 1024, 1030)] + + // Split, fixed length bigger + [InlineData("Test String", "us-ascii", 20, 1024, 1024, 1020)] + [InlineData("Test String", "utf-16BE", 20, 1024, 1024, 1020)] + [InlineData("Test String", "utf-16", 20, 1024, 1024, 1020)] + public void TestWriteString( + string value, + string encodingStr, + int fixedLength, + int firstSize, + int secondSize, + int offset + ) + { + Span buffer = stackalloc byte[firstSize + secondSize]; + buffer.Clear(); + + var encoding = EncodingHelpers.GetEncoding(encodingStr); + var strLength = fixedLength > -1 ? Math.Min(value.Length, fixedLength) : value.Length; + var chars = value.AsSpan(0, strLength); + + var writer = new CircularBufferWriter(buffer.Slice(0, firstSize), buffer.Slice(firstSize)); + writer.Seek(offset, SeekOrigin.Begin); + writer.WriteString(chars, encoding); + + if (offset > 0) + { + Span testEmpty = stackalloc byte[offset]; + testEmpty.Clear(); + AssertThat.Equal(buffer.Slice(0, offset), testEmpty); + } + + Span expectedStr = stackalloc byte[encoding.GetByteCount(chars)]; + encoding.GetBytes(chars, expectedStr.Slice(0)); + + AssertThat.Equal(buffer.Slice(offset, expectedStr.Length), expectedStr); + offset += expectedStr.Length; + + if (offset < buffer.Length) + { + Span testEmpty = stackalloc byte[buffer.Length - offset]; + testEmpty.Clear(); + AssertThat.Equal(buffer.Slice(offset), testEmpty); + } + } + } +} diff --git a/Projects/Server.Tests/Network/PacketReaderTests.cs b/Projects/Server.Tests/Network/PacketReaderTests.cs deleted file mode 100644 index b509e35cf..000000000 --- a/Projects/Server.Tests/Network/PacketReaderTests.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System; -using System.IO; -using System.Text; -using Server.Network; -using Xunit; -using Xunit.Abstractions; - -namespace Server.Tests.Network -{ - public class PacketReaderTests - { - private (Encoding, Type) GetEncoding(string value) => - value.ToUpper() switch - { - "UTF8" => (Utility.UTF8, typeof(byte)), - "UNICODELE" => (Utility.UnicodeLE, typeof(char)), - "UNICODE" => (Utility.Unicode, typeof(char)), - _ => (Encoding.ASCII, typeof(byte)) - }; - - private ITestOutputHelper _outputHelper; - - public PacketReaderTests(ITestOutputHelper outputHelper) => _outputHelper = outputHelper; - - [Theory] - // First only, beginning - [InlineData("Test String", "ASCII", false, -1, 1024, 1024, 0)] - [InlineData("Test String", "UTF8", false, -1, 1024, 1024, 0)] - [InlineData("Test String", "Unicode", false, -1, 1024, 1024, 0)] - [InlineData("Test String", "UnicodeLE", false, -1, 1024, 1024, 0)] - - // Second only - [InlineData("Test String", "ASCII", false, -1, 1024, 1024, 1030)] - [InlineData("Test String", "UTF8", false, -1, 1024, 1024, 1030)] - [InlineData("Test String", "Unicode", false, -1, 1024, 1024, 1030)] - [InlineData("Test String", "UnicodeLE", false, -1, 1024, 1024, 1030)] - public void TestReadString( - string value, - string encodingStr, - bool isSafe, - int fixedLength, - int firstSize, - int secondSize, - int offset - ) - { - var (encoding, byteType) = GetEncoding(encodingStr); - var bytes = encoding.GetBytes(value); - Span expectedBytes = bytes.AsSpan(0, fixedLength > -1 ? Math.Min(fixedLength, bytes.Length) : bytes.Length); - - if (offset + expectedBytes.Length > firstSize + secondSize) - { - throw new ArgumentException("Test failed due to bad assumptions. Out of memory exception would be thrown"); - } - - Span first = stackalloc byte[firstSize]; - first.Clear(); // Just in case - - int bytesWrittenFirst = 0; - - if (offset < first.Length) - { - bytesWrittenFirst = Math.Min(first.Length, expectedBytes.Length); - expectedBytes.Slice(offset, bytesWrittenFirst).CopyTo(first); - } - - Span second = stackalloc byte[secondSize]; - second.Clear(); // Just in case - - if (offset > first.Length || expectedBytes.Length > bytesWrittenFirst) - { - var secondOffset = Math.Max(offset - first.Length, 0); - var secondSlice = second.Slice(secondOffset, second.Length - secondOffset); - expectedBytes.Slice(bytesWrittenFirst, expectedBytes.Length - bytesWrittenFirst).CopyTo(secondSlice); - } - - // _outputHelper.WriteLine(HexStringConverter.GetString(first)); - // _outputHelper.WriteLine(HexStringConverter.GetString(second)); - - var reader = new PacketReader(first, second); - reader.Seek(offset, SeekOrigin.Begin); - - _outputHelper.WriteLine(reader.Position.ToString()); - - var actual = byteType switch - { - var b when b == typeof(char) => reader.ReadString(encoding, isSafe, fixedLength), - _ => reader.ReadString(encoding, isSafe, fixedLength) - }; - - _outputHelper.WriteLine(actual); - - if (fixedLength > 0 && fixedLength < value.Length) - { - value = value.Substring(0, fixedLength); - } - - Assert.Equal(value, actual); - } - } -} diff --git a/Projects/Server.Tests/Network/PipeTests.cs b/Projects/Server.Tests/Network/PipeTests.cs index 9d7e81823..11b870362 100644 --- a/Projects/Server.Tests/Network/PipeTests.cs +++ b/Projects/Server.Tests/Network/PipeTests.cs @@ -131,28 +131,28 @@ namespace Server.Tests.Network var result = writer.GetAvailable(); Assert.True(result.Length == 9); - Assert.True(reader.GetRemaining() == 0); + Assert.True(reader.GetAvailable() == 0); result = reader.TryRead(); Assert.True(result.Length == 0); writer.Advance(7); result = writer.GetAvailable(); Assert.True(result.Length == 2); - Assert.True(reader.GetRemaining() == 7); + Assert.True(reader.GetAvailable() == 7); result = reader.TryRead(); Assert.True(result.Length == 7); reader.Advance(4); result = writer.GetAvailable(); Assert.True(result.Length == 6); - Assert.True(reader.GetRemaining() == 3); + Assert.True(reader.GetAvailable() == 3); result = reader.TryRead(); Assert.True(result.Length == 3); writer.Advance(3); result = writer.GetAvailable(); Assert.True(result.Length == 3); - Assert.True(reader.GetRemaining() == 6); + Assert.True(reader.GetAvailable() == 6); result = reader.TryRead(); Assert.True(result.Length == 6); @@ -171,7 +171,7 @@ namespace Server.Tests.Network writer.Advance(i); writer.Flush(); - Assert.True(reader.GetRemaining() == i); + Assert.True(reader.GetAvailable() == i); reader.Advance(i); } } @@ -192,7 +192,7 @@ namespace Server.Tests.Network writer.Advance(9); writer.Flush(); - Assert.True(reader.GetRemaining() == 9); + Assert.True(reader.GetAvailable() == 9); buffer = reader.TryRead(); diff --git a/Projects/Server.Tests/Utility/EncodingHelpers.cs b/Projects/Server.Tests/Utility/EncodingHelpers.cs new file mode 100644 index 000000000..ca179e4a2 --- /dev/null +++ b/Projects/Server.Tests/Utility/EncodingHelpers.cs @@ -0,0 +1,17 @@ +using System; +using System.Text; + +namespace Server.Tests +{ + public static class EncodingHelpers + { + public static Encoding GetEncoding(string bodyname) => + bodyname switch + { + "utf-8" => Utility.UTF8, + "utf-16" => Utility.UnicodeLE, + "utf-16BE" => Utility.Unicode, + _ => Encoding.ASCII + }; + } +} diff --git a/Projects/Server/Network/PacketReader.cs b/Projects/Server/Buffers/CircularBufferReader.cs similarity index 58% rename from Projects/Server/Network/PacketReader.cs rename to Projects/Server/Buffers/CircularBufferReader.cs index cf8b4b4f3..80d2e3c25 100644 --- a/Projects/Server/Network/PacketReader.cs +++ b/Projects/Server/Buffers/CircularBufferReader.cs @@ -15,35 +15,29 @@ using System; using System.Buffers.Binary; -using System.Data; using System.IO; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Text; namespace Server.Network { - public ref struct PacketReader + public ref struct CircularBufferReader { - public Span First; - public Span Second; + private readonly ReadOnlySpan _first; + private readonly ReadOnlySpan _second; public int Length { get; } public int Position { get; private set; } public int Remaining => Length - Position; - public PacketReader(ArraySegment[] buffers) + public CircularBufferReader(ArraySegment[] buffers) : this(buffers[0], buffers[1]) { - First = buffers[0]; - Second = buffers[1]; - Position = 0; - Length = First.Length + Second.Length; } - public PacketReader(Span first, Span second) + public CircularBufferReader(ReadOnlySpan first, ReadOnlySpan second) { - First = first; - Second = second; + _first = first; + _second = second; Position = 0; Length = first.Length + second.Length; } @@ -51,7 +45,7 @@ namespace Server.Network public void Trace(NetState state) { // We don't have data, so nothing to trace - if (First.Length == 0) + if (_first.Length == 0) { return; } @@ -60,9 +54,9 @@ namespace Server.Network { using var sw = new StreamWriter("Packets.log", true); - sw.WriteLine("Client: {0}: Unhandled packet 0x{1:X2}", state, First[0]); + sw.WriteLine("Client: {0}: Unhandled packet 0x{1:X2}", state, _first[0]); - Utility.FormatBuffer(sw, First.ToArray(), new Memory(Second.ToArray())); + Utility.FormatBuffer(sw, _first.ToArray(), new Memory(_second.ToArray())); sw.WriteLine(); sw.WriteLine(); @@ -76,14 +70,14 @@ namespace Server.Network [MethodImpl(MethodImplOptions.AggressiveInlining)] public byte ReadByte() { - if (Position < First.Length) + if (Position < _first.Length) { - return First[Position++]; + return _first[Position++]; } if (Position < Length) { - return Second[Position++ - First.Length]; + return _second[Position++ - _first.Length]; } throw new OutOfMemoryException(); @@ -100,15 +94,15 @@ namespace Server.Network { short value; - if (Position < First.Length) + if (Position < _first.Length) { - if (!BinaryPrimitives.TryReadInt16BigEndian(First.Slice(Position), out value)) + if (!BinaryPrimitives.TryReadInt16BigEndian(_first.Slice(Position), out value)) { // Not enough space. Split the spans return (short)((ReadByte() >> 8) | ReadByte()); } } - else if (!BinaryPrimitives.TryReadInt16BigEndian(Second.Slice(Position - First.Length), out value)) + else if (!BinaryPrimitives.TryReadInt16BigEndian(_second.Slice(Position - _first.Length), out value)) { throw new OutOfMemoryException(); } @@ -122,15 +116,15 @@ namespace Server.Network { ushort value; - if (Position < First.Length) + if (Position < _first.Length) { - if (!BinaryPrimitives.TryReadUInt16BigEndian(First.Slice(Position), out value)) + if (!BinaryPrimitives.TryReadUInt16BigEndian(_first.Slice(Position), out value)) { // Not enough space. Split the spans return (ushort)((ReadByte() >> 8) | ReadByte()); } } - else if (!BinaryPrimitives.TryReadUInt16BigEndian(Second.Slice(Position - First.Length), out value)) + else if (!BinaryPrimitives.TryReadUInt16BigEndian(_second.Slice(Position - _first.Length), out value)) { throw new OutOfMemoryException(); } @@ -144,15 +138,15 @@ namespace Server.Network { int value; - if (Position < First.Length) + if (Position < _first.Length) { - if (!BinaryPrimitives.TryReadInt32BigEndian(First.Slice(Position), out value)) + if (!BinaryPrimitives.TryReadInt32BigEndian(_first.Slice(Position), out value)) { // Not enough space. Split the spans return (ReadByte() >> 24) | (ReadByte() >> 16) | (ReadByte() >> 8) | ReadByte(); } } - else if (!BinaryPrimitives.TryReadInt32BigEndian(Second.Slice(Position - First.Length), out value)) + else if (!BinaryPrimitives.TryReadInt32BigEndian(_second.Slice(Position - _first.Length), out value)) { throw new OutOfMemoryException(); } @@ -166,15 +160,15 @@ namespace Server.Network { uint value; - if (Position < First.Length) + if (Position < _first.Length) { - if (!BinaryPrimitives.TryReadUInt32BigEndian(First.Slice(Position), out value)) + if (!BinaryPrimitives.TryReadUInt32BigEndian(_first.Slice(Position), out value)) { // Not enough space. Split the spans return (uint)((ReadByte() >> 24) | (ReadByte() >> 16) | (ReadByte() >> 8) | ReadByte()); } } - else if (!BinaryPrimitives.TryReadUInt32BigEndian(Second.Slice(Position - First.Length), out value)) + else if (!BinaryPrimitives.TryReadUInt32BigEndian(_second.Slice(Position - _first.Length), out value)) { throw new OutOfMemoryException(); } @@ -183,28 +177,20 @@ namespace Server.Network return value; } - private static bool IsSafeChar(ushort c) => c >= 0x20 && c < 0xFFFE; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadString(Encoding encoding, bool safeString = false, int fixedLength = -1) where T : struct, IEquatable + public string ReadString(Encoding encoding, bool safeString = false, int fixedLength = -1) { - int sizeT = Unsafe.SizeOf(); - - if (sizeT > 2) - { - throw new InvalidConstraintException("ReadString only accepts byte, sbyte, char, short, and ushort as a constraint"); - } + int sizeT = Utility.GetByteLengthForEncoding(encoding); bool isFixedLength = fixedLength > -1; var remaining = Remaining; int size; - // Not fixed length if (isFixedLength) { size = fixedLength * sizeT; - if (fixedLength > Remaining) + if (size > Remaining) { throw new OutOfMemoryException(); } @@ -214,130 +200,107 @@ namespace Server.Network size = remaining - (remaining & (sizeT - 1)); } - Span span; + ReadOnlySpan span; int index; - if (Position < First.Length) + if (Position < _first.Length) { + var firstLength = Math.Min(_first.Length - Position, size); + // Find terminator - index = MemoryMarshal - .Cast(First.Slice(Position, Math.Min(size, First.Length))) - .IndexOf(default(T)) * sizeT; + index = Utility.IndexOfTerminator(_first.Slice(Position, firstLength), sizeT); if (index < 0) { - remaining = size - First.Length; + remaining = size - firstLength; // We don't have a terminator, but a fixed size to the end of the first span, so stop there if (remaining <= 0) { - index = First.Length; + index = firstLength; } else { - index = MemoryMarshal - .Cast(Second.Slice(0, remaining)) - .IndexOf(default(T)) * sizeT; + index = Utility.IndexOfTerminator(_second.Slice(0, remaining), sizeT); int secondLength = index < 0 ? remaining : index; - int length = First.Length + secondLength; + int length = firstLength + secondLength; // Assume no strings should be too long for the stack Span bytes = stackalloc byte[length]; - First.Slice(Position).CopyTo(bytes); - Second.Slice(0, secondLength).CopyTo(bytes.Slice(First.Length)); + _first.Slice(Position).CopyTo(bytes); + _second.Slice(0, secondLength).CopyTo(bytes.Slice(firstLength)); - Position += length; - return GetString(bytes, encoding, safeString); + Position += length + (index >= 0 ? sizeT : 0); + return Utility.GetString(bytes, encoding, safeString); } } - span = First.Slice(Position, index); + span = _first.Slice(Position, index); } else { - span = Second.Slice( Position - First.Length, Math.Min(remaining, size)); - index = MemoryMarshal.Cast(span).IndexOf(default(T)) * sizeT; + size = Math.Min(remaining, size); + span = _second.Slice( Position - _first.Length, size); + index = Utility.IndexOfTerminator(span, sizeT); if (index >= 0) { span = span.Slice(0, index); } - } - - Position += isFixedLength ? size : index; - return GetString(span, encoding, safeString); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static string GetString(Span span, Encoding encoding, bool safeString = false) - { - string s = encoding.GetString(span); - - if (!safeString) - { - return s; - } - - ReadOnlySpan chars = s.AsSpan(); - - StringBuilder stringBuilder = null; - - for (int i = 0, last = 0; i < chars.Length; i++) - { - if (!IsSafeChar(chars[i]) || stringBuilder != null && i == chars.Length - 1) + else { - (stringBuilder ??= new StringBuilder()).Append(chars.Slice(last, i - last)); - last = i + 1; // Skip the unsafe char + index = size; } } - return stringBuilder?.ToString() ?? s; + Position += isFixedLength ? size : index + sizeT; + return Utility.GetString(span, encoding, safeString); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadLittleUniSafe(int fixedLength) => ReadString(Utility.UnicodeLE, true, fixedLength); + public string ReadLittleUniSafe(int fixedLength) => ReadString(Utility.UnicodeLE, true, fixedLength); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadLittleUniSafe() => ReadString(Utility.UnicodeLE, true); + public string ReadLittleUniSafe() => ReadString(Utility.UnicodeLE, true); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadLittleUni(int fixedLength) => ReadString(Utility.UnicodeLE, false, fixedLength); + public string ReadLittleUni(int fixedLength) => ReadString(Utility.UnicodeLE, false, fixedLength); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadLittleUni() => ReadString(Utility.UnicodeLE); + public string ReadLittleUni() => ReadString(Utility.UnicodeLE); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadBigUniSafe(int fixedLength) => ReadString(Utility.Unicode, true, fixedLength); + public string ReadBigUniSafe(int fixedLength) => ReadString(Utility.Unicode, true, fixedLength); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadBigUniSafe() => ReadString(Utility.Unicode, true); + public string ReadBigUniSafe() => ReadString(Utility.Unicode, true); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadBigUni(int fixedLength) => ReadString(Utility.Unicode, false, fixedLength); + public string ReadBigUni(int fixedLength) => ReadString(Utility.Unicode, false, fixedLength); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadBigUni() => ReadString(Utility.Unicode); + public string ReadBigUni() => ReadString(Utility.Unicode); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadUTF8Safe(int fixedLength) => ReadString(Utility.UTF8, true, fixedLength); + public string ReadUTF8Safe(int fixedLength) => ReadString(Utility.UTF8, true, fixedLength); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadUTF8Safe() => ReadString(Utility.UTF8, true); + public string ReadUTF8Safe() => ReadString(Utility.UTF8, true); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadUTF8() => ReadString(Utility.UTF8); + public string ReadUTF8() => ReadString(Utility.UTF8); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadAsciiSafe(int fixedLength) => ReadString(Encoding.ASCII, true, fixedLength); + public string ReadAsciiSafe(int fixedLength) => ReadString(Encoding.ASCII, true, fixedLength); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadAsciiSafe() => ReadString(Encoding.ASCII, true); + public string ReadAsciiSafe() => ReadString(Encoding.ASCII, true); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadAscii(int fixedLength) => ReadString(Encoding.ASCII, false, fixedLength); + public string ReadAscii(int fixedLength) => ReadString(Encoding.ASCII, false, fixedLength); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadAscii() => ReadString(Encoding.ASCII); + public string ReadAscii() => ReadString(Encoding.ASCII); [MethodImpl(MethodImplOptions.AggressiveInlining)] public int Seek(int offset, SeekOrigin origin) => diff --git a/Projects/Server/Buffers/CircularBufferWriter.cs b/Projects/Server/Buffers/CircularBufferWriter.cs new file mode 100644 index 000000000..542c72b8a --- /dev/null +++ b/Projects/Server/Buffers/CircularBufferWriter.cs @@ -0,0 +1,372 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: CircularBufferWriter.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.Buffers.Binary; +using System.IO; +using System.Runtime.CompilerServices; +using System.Text; +using Server; + +namespace System.Buffers +{ + public ref struct CircularBufferWriter + { + private readonly Span _first; + private readonly Span _second; + + public int Length { get; } + public int Position { get; private set; } + + public CircularBufferWriter(ArraySegment[] buffers) : this(buffers[0], buffers[1]) + { + } + + public CircularBufferWriter(Span first, Span second) + { + _first = first; + _second = second; + Position = 0; + Length = first.Length + second.Length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(byte value) + { + if (Position < _first.Length) + { + _first[Position++] = value; + } + else if (Position < Length) + { + _second[Position++ - _first.Length] = value; + } + else + { + throw new OutOfMemoryException(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(bool value) + { + if (value) + { + Write((byte)1); + } + else + { + Write((byte)0); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(sbyte value) + { + Write((byte)value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(short value) + { + if (Position < _first.Length) + { + if (!BinaryPrimitives.TryWriteInt16BigEndian(_first.Slice(Position), value)) + { + // Not enough space. Split the spans + Write((byte)(value >> 8)); + Write((byte)value); + } + else + { + Position += 2; + } + } + else if (BinaryPrimitives.TryWriteInt16BigEndian(_second.Slice(Position - _first.Length), value)) + { + Position += 2; + } + else + { + throw new OutOfMemoryException(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(ushort value) + { + if (Position < _first.Length) + { + if (!BinaryPrimitives.TryWriteUInt16BigEndian(_first.Slice(Position), value)) + { + // Not enough space. Split the spans + Write((byte)(value >> 8)); + Write((byte)value); + } + else + { + Position += 2; + } + } + else if (BinaryPrimitives.TryWriteUInt16BigEndian(_second.Slice(Position - _first.Length), value)) + { + Position += 2; + } + else + { + throw new OutOfMemoryException(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(int value) + { + if (Position < _first.Length) + { + if (!BinaryPrimitives.TryWriteInt32BigEndian(_first.Slice(Position), value)) + { + // Not enough space. Split the spans + Write((byte)(value >> 24)); + Write((byte)(value >> 16)); + Write((byte)(value >> 8)); + Write((byte)value); + } + else + { + Position += 4; + } + } + else if (BinaryPrimitives.TryWriteInt32BigEndian(_second.Slice(Position - _first.Length), value)) + { + Position += 4; + } + else + { + throw new OutOfMemoryException(); + } + } + + /// + /// Writes a 4-byte unsigned integer value to the underlying stream. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(uint value) + { + if (Position < _first.Length) + { + if (!BinaryPrimitives.TryWriteUInt32BigEndian(_first.Slice(Position), value)) + { + // Not enough space. Split the spans + Write((byte)(value >> 24)); + Write((byte)(value >> 16)); + Write((byte)(value >> 8)); + Write((byte)value); + } + else + { + Position += 4; + } + } + else if (BinaryPrimitives.TryWriteUInt32BigEndian(_second.Slice(Position - _first.Length), value)) + { + Position += 4; + } + else + { + throw new OutOfMemoryException(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(ReadOnlySpan buffer) + { + if (Position + buffer.Length > Length) + { + throw new OutOfMemoryException(); + } + + if (Position < _first.Length) + { + var sz = Math.Min(buffer.Length, _first.Length - Position); + buffer.CopyTo(_first.Slice(Position)); + buffer.Slice(sz).CopyTo(_second); + Position += buffer.Length; + } + else if (Position < Length) + { + buffer.CopyTo(_second.Slice(Position - _first.Length)); + } + else + { + throw new OutOfMemoryException(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteString(ReadOnlySpan value, Encoding encoding) + { + var byteCount = encoding.GetByteCount(value); + + if (Position + byteCount > Length) + { + throw new OutOfMemoryException(); + } + + Span bytes = stackalloc byte[byteCount]; + encoding.GetBytes(value, bytes); + + int count; + if (Position < _first.Length) + { + count = Math.Min(_first.Length - Position, byteCount); + bytes.Slice(0, count).CopyTo(_first.Slice(Position)); + byteCount -= count; + Position += count; + } + else + { + count = 0; + } + + if (byteCount > 0) + { + bytes.Slice(count).CopyTo(_second.Slice(Math.Max(0, Position - _first.Length))); + Position += byteCount; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteLittleUni(string value, int fixedLength = -1) + { + if (fixedLength < 0) { fixedLength = value.Length; } + + WriteString(value.AsSpan(0, Math.Min(fixedLength, value.Length)), Utility.UnicodeLE); + var count = fixedLength - value.Length; + if (count > 0) + { + Clear(count * 2); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteLittleUniNull(string value) + { + WriteString(value, Utility.UnicodeLE); + Write((ushort)0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteBigUni(string value, int fixedLength = -1) + { + if (fixedLength < 0) { fixedLength = value.Length; } + + WriteString(value.AsSpan(0, Math.Min(fixedLength, value.Length)), Utility.Unicode); + var count = fixedLength - value.Length; + if (count > 0) + { + Clear(count * 2); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteBigUniNull(string value) + { + WriteString(value, Utility.Unicode); + Write((ushort)0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUTF8(string value) => WriteString(value, Utility.UTF8); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUTF8Null(string value) + { + WriteString(value, Utility.UTF8); + Write((byte)0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteAscii(string value, int fixedLength = -1) + { + if (fixedLength < 0) { fixedLength = value.Length; } + + WriteString(value.AsSpan(0, Math.Min(fixedLength, value.Length)), Encoding.ASCII); + var count = fixedLength - value.Length; + if (count > 0) + { + Clear(count); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteAsciiNull(string value) + { + WriteString(value, Encoding.ASCII); + Write((byte)0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear() + { + if (Position < _first.Length) + { + _first.Slice(Position).Clear(); + _second.Clear(); + } + else + { + _second.Slice(Position - _first.Length).Clear(); + } + + Position = Length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear(int amount) + { + if (Position + amount > Length) + { + throw new OutOfMemoryException(); + } + + int count; + if (Position < _first.Length) + { + count = Math.Min(amount, _first.Length - Position); + _first.Slice(Position, count).Clear(); + count = amount - count; + } + else + { + count = amount; + } + + if (count > 0) + { + _second.Slice(Position - _first.Length, count).Clear(); + } + + Position += amount; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Seek(int offset, SeekOrigin origin) => + Position = origin switch + { + SeekOrigin.Begin => offset, + SeekOrigin.End => Length - offset, + _ => Position + offset // Current + }; + } +} diff --git a/Projects/Server/Buffers/SpanReader.cs b/Projects/Server/Buffers/SpanReader.cs new file mode 100644 index 000000000..66b246a12 --- /dev/null +++ b/Projects/Server/Buffers/SpanReader.cs @@ -0,0 +1,187 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: SpanReader.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.IO; +using System.Runtime.CompilerServices; +using System.Text; + +namespace Server.Buffers +{ + ref struct SpanReader + { + private readonly ReadOnlySpan _buffer; + + public int Length { get; } + public int Position { get; private set; } + public int Remaining => Length - Position; + + public SpanReader(ReadOnlySpan span) + { + _buffer = span; + Position = 0; + Length = span.Length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte ReadByte() + { + if (Position >= Length) + { + throw new OutOfMemoryException(); + } + + return _buffer[Position++]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool ReadBoolean() => ReadByte() > 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public sbyte ReadSByte() => (sbyte)ReadByte(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public short ReadInt16() + { + if (!BinaryPrimitives.TryReadInt16BigEndian(_buffer.Slice(Position), out var value)) + { + throw new OutOfMemoryException(); + } + + Position += 2; + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort ReadUInt16() + { + if (!BinaryPrimitives.TryReadUInt16BigEndian(_buffer.Slice(Position), out var value)) + { + throw new OutOfMemoryException(); + } + + Position += 2; + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int ReadInt32() + { + if (!BinaryPrimitives.TryReadInt32BigEndian(_buffer.Slice(Position), out var value)) + { + throw new OutOfMemoryException(); + } + + Position += 4; + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint ReadUInt32() + { + if (!BinaryPrimitives.TryReadUInt32BigEndian(_buffer.Slice(Position), out var value)) + { + throw new OutOfMemoryException(); + } + + Position += 4; + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadString(Encoding encoding, bool safeString = false, int fixedLength = -1) + { + int sizeT = Utility.GetByteLengthForEncoding(encoding); + + bool isFixedLength = fixedLength > -1; + + var remaining = Remaining; + int size; + + if (isFixedLength) + { + size = fixedLength * sizeT; + if (size > Remaining) + { + throw new OutOfMemoryException(); + } + } + else + { + size = remaining - (remaining & (sizeT - 1)); + } + + int index = Utility.IndexOfTerminator(_buffer.Slice(Position, size), sizeT); + + Position += isFixedLength || index < 0 ? size : index + sizeT; + return Utility.GetString(_buffer.Slice(Position, index < 0 ? size : index), encoding, safeString); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadLittleUniSafe(int fixedLength) => ReadString(Utility.UnicodeLE, true, fixedLength); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadLittleUniSafe() => ReadString(Utility.UnicodeLE, true); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadLittleUni(int fixedLength) => ReadString(Utility.UnicodeLE, false, fixedLength); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadLittleUni() => ReadString(Utility.UnicodeLE); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadBigUniSafe(int fixedLength) => ReadString(Utility.Unicode, true, fixedLength); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadBigUniSafe() => ReadString(Utility.Unicode, true); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadBigUni(int fixedLength) => ReadString(Utility.Unicode, false, fixedLength); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadBigUni() => ReadString(Utility.Unicode); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadUTF8Safe(int fixedLength) => ReadString(Utility.UTF8, true, fixedLength); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadUTF8Safe() => ReadString(Utility.UTF8, true); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadUTF8() => ReadString(Utility.UTF8); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadAsciiSafe(int fixedLength) => ReadString(Encoding.ASCII, true, fixedLength); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadAsciiSafe() => ReadString(Encoding.ASCII, true); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadAscii(int fixedLength) => ReadString(Encoding.ASCII, false, fixedLength); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadAscii() => ReadString(Encoding.ASCII); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Seek(int offset, SeekOrigin origin) => + Position = origin switch + { + SeekOrigin.Begin => offset, + SeekOrigin.End => Length - offset, + _ => Position + offset // Current + }; + } +} diff --git a/Projects/Server/Buffers/SpanWriter.cs b/Projects/Server/Buffers/SpanWriter.cs new file mode 100644 index 000000000..cae1a5068 --- /dev/null +++ b/Projects/Server/Buffers/SpanWriter.cs @@ -0,0 +1,244 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: SpanWriter.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.Data; +using System.IO; +using System.Runtime.CompilerServices; +using System.Text; + +namespace Server.Buffers +{ + public ref struct SpanWriter + { + private readonly Span _buffer; + + public int Length => _buffer.Length; + + public ReadOnlySpan Span => _buffer.Slice(0, Position); + + public int Position { get; private set; } + + public SpanWriter(Span span) + { + _buffer = span; + Position = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void Write(bool value) + { + if (Position >= Length) + { + throw new OutOfMemoryException(); + } + + _buffer[Position++] = *(byte*) & value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(byte value) + { + _buffer[Position++] = value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(sbyte value) + { + _buffer[Position++] = (byte)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(short value) + { + if (!BinaryPrimitives.TryWriteInt16BigEndian(_buffer.Slice(Position), value)) + { + throw new OutOfMemoryException(); + } + + Position += 2; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(ushort value) + { + if (!BinaryPrimitives.TryWriteUInt16BigEndian(_buffer.Slice(Position), value)) + { + throw new OutOfMemoryException(); + } + + Position += 2; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(int value) + { + if (!BinaryPrimitives.TryWriteInt32BigEndian(_buffer.Slice(Position), value)) + { + throw new OutOfMemoryException(); + } + + Position += 4; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(uint value) + { + if (!BinaryPrimitives.TryWriteUInt32BigEndian(_buffer.Slice(Position), value)) + { + throw new OutOfMemoryException(); + } + + Position += 4; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(long value) + { + if (!BinaryPrimitives.TryWriteInt64BigEndian(_buffer.Slice(Position), value)) + { + throw new OutOfMemoryException(); + } + + Position += 8; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(ulong value) + { + if (!BinaryPrimitives.TryWriteUInt64BigEndian(_buffer.Slice(Position), value)) + { + throw new OutOfMemoryException(); + } + + Position += 8; + } + + public void Write(ReadOnlySpan buffer) + { + var size = buffer.Length; + if (Position + size > Length) + { + throw new OutOfMemoryException(); + } + + buffer.Slice(0, size).CopyTo(_buffer.Slice(Position)); + Position += size; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteString(string value, Encoding encoding, int fixedLength = -1) where T : struct, IEquatable + { + int sizeT = Unsafe.SizeOf(); + + if (sizeT > 2) + { + throw new InvalidConstraintException("WriteString only accepts byte, sbyte, char, short, and ushort as a constraint"); + } + + value ??= string.Empty; + + var charLength = Math.Min(fixedLength > -1 ? fixedLength : value.Length, value.Length); + var src = value.AsSpan(0, charLength); + + var byteCount = fixedLength > -1 ? fixedLength * sizeT : encoding.GetByteCount(value); + + if (Position + byteCount > Length) + { + throw new OutOfMemoryException(); + } + + Position += encoding.GetBytes(src, _buffer.Slice(Position)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteLittleUni(string value) => WriteString(value, Utility.UnicodeLE); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteLittleUniNull(string value) + { + WriteString(value, Utility.UnicodeLE); + Write((ushort)0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteLittleUni(string value, int fixedLength) => WriteString(value, Utility.UnicodeLE, fixedLength); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteBigUni(string value) => WriteString(value, Utility.Unicode); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteBigUniNull(string value) + { + WriteString(value, Utility.Unicode); + Write((ushort)0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteBigUni(string value, int fixedLength) => WriteString(value, Utility.Unicode, fixedLength); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUTF8(string value) => WriteString(value, Utility.UTF8); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUTF8Null(string value) + { + WriteString(value, Utility.UTF8); + Write((byte)0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteAscii(string value) => WriteString(value, Encoding.ASCII); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteAsciiNull(string value) + { + WriteString(value, Encoding.ASCII); + Write((byte)0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteAscii(string value, int fixedLength) => WriteString(value, Encoding.ASCII, fixedLength); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear() + { + _buffer.Slice(Position).Clear(); + Position = Length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear(int amount) + { + if (Position + amount > Length) + { + throw new OutOfMemoryException(); + } + + _buffer.Slice(Position, amount).Clear(); + Position += amount; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Seek(int offset, SeekOrigin origin) => + Position = origin switch + { + SeekOrigin.Begin => offset, + SeekOrigin.End => Length - offset, + _ => Position + offset // Current + }; + } +} diff --git a/Projects/Server/Network/EncodedReader.cs b/Projects/Server/Network/EncodedReader.cs index bcf3a2d1e..9c2ecf5cb 100644 --- a/Projects/Server/Network/EncodedReader.cs +++ b/Projects/Server/Network/EncodedReader.cs @@ -2,9 +2,9 @@ namespace Server.Network { public ref struct EncodedReader { - private PacketReader m_Reader; + private CircularBufferReader m_Reader; - public EncodedReader(PacketReader reader) => m_Reader = reader; + public EncodedReader(CircularBufferReader reader) => m_Reader = reader; public void Trace(NetState state) { diff --git a/Projects/Server/Network/NetState/NetState.cs b/Projects/Server/Network/NetState/NetState.cs index 51291e4c1..18ef0b071 100644 --- a/Projects/Server/Network/NetState/NetState.cs +++ b/Projects/Server/Network/NetState/NetState.cs @@ -14,6 +14,7 @@ *************************************************************************/ using System; +using System.Buffers; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; diff --git a/Projects/Server/Network/Packet.cs b/Projects/Server/Network/Packet.cs index a2bbe7b55..3a7b0177d 100644 --- a/Projects/Server/Network/Packet.cs +++ b/Projects/Server/Network/Packet.cs @@ -227,22 +227,22 @@ namespace Server.Network m_CompiledLength = length; } - if (length > 0) + if (m_CompiledLength > 0) { var old = m_CompiledBuffer; if ((m_State & State.Static) != 0) { - m_CompiledBuffer = new byte[length]; + m_CompiledBuffer = new byte[m_CompiledLength]; } else { // Release it later using Release() - m_CompiledBuffer = ArrayPool.Shared.Rent(length); + m_CompiledBuffer = ArrayPool.Shared.Rent(m_CompiledLength); m_State |= State.Buffered; } - Buffer.BlockCopy(old, 0, m_CompiledBuffer, 0, length); + Buffer.BlockCopy(old, 0, m_CompiledBuffer, 0, m_CompiledLength); if (compress) { diff --git a/Projects/Server/Network/PacketHandler.cs b/Projects/Server/Network/PacketHandler.cs index 7da62fa7b..2f5bbd2b7 100644 --- a/Projects/Server/Network/PacketHandler.cs +++ b/Projects/Server/Network/PacketHandler.cs @@ -2,7 +2,7 @@ using System; namespace Server.Network { - public delegate void OnPacketReceive(NetState state, PacketReader reader); + public delegate void OnPacketReceive(NetState state, CircularBufferReader reader); public delegate TimeSpan ThrottlePacketCallback(NetState state); diff --git a/Projects/Server/Network/PacketHandlers.cs b/Projects/Server/Network/PacketHandlers.cs index f37823681..5d06fba56 100644 --- a/Projects/Server/Network/PacketHandlers.cs +++ b/Projects/Server/Network/PacketHandlers.cs @@ -283,7 +283,7 @@ namespace Server.Network public static int ProcessPacket(NetState ns, ArraySegment[] segments) { - var reader = new PacketReader(segments); + var reader = new CircularBufferReader(segments); var packetId = reader.ReadByte(); @@ -360,11 +360,11 @@ namespace Server.Network return packetLength; } - private static void UnhandledBF(NetState state, PacketReader reader) + private static void UnhandledBF(NetState state, CircularBufferReader reader) { } - public static void Empty(NetState state, PacketReader reader) + public static void Empty(NetState state, CircularBufferReader reader) { } @@ -383,7 +383,7 @@ namespace Server.Network EventSink.InvokeQuestGumpRequest(state.Mobile); } - public static void EncodedCommand(NetState state, PacketReader reader) + public static void EncodedCommand(NetState state, CircularBufferReader reader) { var e = World.FindEntity(reader.ReadUInt32()); int packetId = reader.ReadUInt16(); @@ -415,7 +415,7 @@ namespace Server.Network } } - public static void RenameRequest(NetState state, PacketReader reader) + public static void RenameRequest(NetState state, CircularBufferReader reader) { var from = state.Mobile; var targ = World.FindMobile(reader.ReadUInt32()); @@ -426,7 +426,7 @@ namespace Server.Network } } - public static void SecureTrade(NetState state, PacketReader reader) + public static void SecureTrade(NetState state, CircularBufferReader reader) { switch (reader.ReadByte()) { @@ -498,7 +498,7 @@ namespace Server.Network } } - public static void VendorBuyReply(NetState state, PacketReader reader) + public static void VendorBuyReply(NetState state, CircularBufferReader reader) { var vendor = World.FindMobile(reader.ReadUInt32()); var flag = reader.ReadByte(); @@ -545,7 +545,7 @@ namespace Server.Network } } - public static void VendorSellReply(NetState state, PacketReader reader) + public static void VendorSellReply(NetState state, CircularBufferReader reader) { Serial serial = reader.ReadUInt32(); var vendor = World.FindMobile(serial); @@ -587,7 +587,7 @@ namespace Server.Network } } - public static void DeleteCharacter(NetState state, PacketReader reader) + public static void DeleteCharacter(NetState state, CircularBufferReader reader) { reader.Seek(30, SeekOrigin.Current); var index = reader.ReadInt32(); @@ -595,12 +595,12 @@ namespace Server.Network EventSink.InvokeDeleteRequest(state, index); } - public static void DeathStatusResponse(NetState state, PacketReader reader) + public static void DeathStatusResponse(NetState state, CircularBufferReader reader) { // Ignored } - public static void ObjectHelpRequest(NetState state, PacketReader reader) + public static void ObjectHelpRequest(NetState state, CircularBufferReader reader) { var from = state.Mobile; @@ -629,7 +629,7 @@ namespace Server.Network } } - public static void MobileNameRequest(NetState state, PacketReader reader) + public static void MobileNameRequest(NetState state, CircularBufferReader reader) { var m = World.FindMobile(reader.ReadUInt32()); @@ -639,13 +639,13 @@ namespace Server.Network } } - public static void RequestScrollWindow(NetState state, PacketReader reader) + public static void RequestScrollWindow(NetState state, CircularBufferReader reader) { int lastTip = reader.ReadInt16(); int type = reader.ReadByte(); } - public static void AttackReq(NetState state, PacketReader reader) + public static void AttackReq(NetState state, CircularBufferReader reader) { var from = state.Mobile; var m = World.FindMobile(reader.ReadUInt32()); @@ -656,7 +656,7 @@ namespace Server.Network } } - public static void HuePickerResponse(NetState state, PacketReader reader) + public static void HuePickerResponse(NetState state, CircularBufferReader reader) { var serial = reader.ReadUInt32(); _ = reader.ReadInt16(); // Item ID @@ -677,7 +677,7 @@ namespace Server.Network } } - public static void SystemInfo(NetState state, PacketReader reader) + public static void SystemInfo(NetState state, CircularBufferReader reader) { int v1 = reader.ReadByte(); int v2 = reader.ReadUInt16(); @@ -693,11 +693,11 @@ namespace Server.Network var v8 = reader.ReadInt32(); } - public static void AccountID(NetState state, PacketReader reader) + public static void AccountID(NetState state, CircularBufferReader reader) { } - public static void TextCommand(NetState state, PacketReader reader) + public static void TextCommand(NetState state, CircularBufferReader reader) { int type = reader.ReadByte(); var command = reader.ReadAscii(); @@ -790,7 +790,7 @@ namespace Server.Network } } - public static void AsciiPromptResponse(NetState state, PacketReader reader) + public static void AsciiPromptResponse(NetState state, CircularBufferReader reader) { var serial = reader.ReadUInt32(); var prompt = reader.ReadInt32(); @@ -820,7 +820,7 @@ namespace Server.Network } } - public static void UnicodePromptResponse(NetState state, PacketReader reader) + public static void UnicodePromptResponse(NetState state, CircularBufferReader reader) { var serial = reader.ReadUInt32(); var prompt = reader.ReadInt32(); @@ -851,7 +851,7 @@ namespace Server.Network } } - public static void MenuResponse(NetState state, PacketReader reader) + public static void MenuResponse(NetState state, CircularBufferReader reader) { var serial = reader.ReadUInt32(); int menuID = reader.ReadInt16(); // unused in our implementation @@ -881,7 +881,7 @@ namespace Server.Network } } - public static void ProfileReq(NetState state, PacketReader reader) + public static void ProfileReq(NetState state, CircularBufferReader reader) { int type = reader.ReadByte(); Serial serial = reader.ReadUInt32(); @@ -921,12 +921,12 @@ namespace Server.Network } } - public static void Disconnect(NetState state, PacketReader reader) + public static void Disconnect(NetState state, CircularBufferReader reader) { var minusOne = reader.ReadInt32(); } - public static void LiftReq(NetState state, PacketReader reader) + public static void LiftReq(NetState state, CircularBufferReader reader) { Serial serial = reader.ReadUInt32(); int amount = reader.ReadUInt16(); @@ -935,7 +935,7 @@ namespace Server.Network state.Mobile.Lift(item, amount, out var rejected, out var reject); } - public static void EquipReq(NetState state, PacketReader reader) + public static void EquipReq(NetState state, CircularBufferReader reader) { var from = state.Mobile; var item = from.Holding; @@ -960,7 +960,7 @@ namespace Server.Network item.ClearBounce(); } - public static void DropReq(NetState state, PacketReader reader) + public static void DropReq(NetState state, CircularBufferReader reader) { reader.ReadInt32(); // serial, ignored int x = reader.ReadInt16(); @@ -997,7 +997,7 @@ namespace Server.Network } } - public static void DropReq6017(NetState state, PacketReader reader) + public static void DropReq6017(NetState state, CircularBufferReader reader) { reader.ReadInt32(); // serial, ignored int x = reader.ReadInt16(); @@ -1035,28 +1035,28 @@ namespace Server.Network } } - public static void ConfigurationFile(NetState state, PacketReader reader) + public static void ConfigurationFile(NetState state, CircularBufferReader reader) { } - public static void LogoutReq(NetState state, PacketReader reader) + public static void LogoutReq(NetState state, CircularBufferReader reader) { state.Send(new LogoutAck()); } - public static void ChangeSkillLock(NetState state, PacketReader reader) + public static void ChangeSkillLock(NetState state, CircularBufferReader reader) { var s = state.Mobile.Skills[reader.ReadInt16()]; s?.SetLockNoRelay((SkillLock)reader.ReadByte()); } - public static void HelpRequest(NetState state, PacketReader reader) + public static void HelpRequest(NetState state, CircularBufferReader reader) { EventSink.InvokeHelpRequest(state.Mobile); } - public static void TargetResponse(NetState state, PacketReader reader) + public static void TargetResponse(NetState state, CircularBufferReader reader) { int type = reader.ReadByte(); var targetID = reader.ReadInt32(); @@ -1170,7 +1170,7 @@ namespace Server.Network } } - public static void DisplayGumpResponse(NetState state, PacketReader reader) + public static void DisplayGumpResponse(NetState state, CircularBufferReader reader) { var serial = reader.ReadUInt32(); var typeID = reader.ReadInt32(); @@ -1292,12 +1292,12 @@ namespace Server.Network } } - public static void SetWarMode(NetState state, PacketReader reader) + public static void SetWarMode(NetState state, CircularBufferReader reader) { state.Mobile.DelayChangeWarmode(reader.ReadBoolean()); } - public static void Resynchronize(NetState state, PacketReader reader) + public static void Resynchronize(NetState state, CircularBufferReader reader) { var m = state.Mobile; @@ -1319,7 +1319,7 @@ namespace Server.Network m.ClearFastwalkStack(); } - public static void AsciiSpeech(NetState state, PacketReader reader) + public static void AsciiSpeech(NetState state, CircularBufferReader reader) { var from = state.Mobile; @@ -1341,7 +1341,7 @@ namespace Server.Network from.DoSpeech(text, m_EmptyInts, type, Utility.ClipDyedHue(hue)); } - public static void UnicodeSpeech(NetState state, PacketReader reader) + public static void UnicodeSpeech(NetState state, CircularBufferReader reader) { var from = state.Mobile; @@ -1420,7 +1420,7 @@ namespace Server.Network from.DoSpeech(text, keywords, type, Utility.ClipDyedHue(hue)); } - public static void UseReq(NetState state, PacketReader reader) + public static void UseReq(NetState state, CircularBufferReader reader) { var from = state.Mobile; @@ -1464,7 +1464,7 @@ namespace Server.Network } } - public static void LookReq(NetState state, PacketReader reader) + public static void LookReq(NetState state, CircularBufferReader reader) { var from = state.Mobile; @@ -1513,17 +1513,17 @@ namespace Server.Network } } - public static void PingReq(NetState state, PacketReader reader) + public static void PingReq(NetState state, CircularBufferReader reader) { state.Send(PingAck.Instantiate(reader.ReadByte())); } - public static void SetUpdateRange(NetState state, PacketReader reader) + public static void SetUpdateRange(NetState state, CircularBufferReader reader) { state.Send(ChangeUpdateRange.Instantiate(18)); } - public static void MovementReq(NetState state, PacketReader reader) + public static void MovementReq(NetState state, CircularBufferReader reader) { var dir = (Direction)reader.ReadByte(); int seq = reader.ReadByte(); @@ -1551,7 +1551,7 @@ namespace Server.Network } } - public static void Animate(NetState state, PacketReader reader) + public static void Animate(NetState state, CircularBufferReader reader) { var from = state.Mobile; var action = reader.ReadInt32(); @@ -1569,7 +1569,7 @@ namespace Server.Network } } - public static void QuestArrow(NetState state, PacketReader reader) + public static void QuestArrow(NetState state, CircularBufferReader reader) { var rightClick = reader.ReadBoolean(); var from = state.Mobile; @@ -1577,7 +1577,7 @@ namespace Server.Network from?.QuestArrow?.OnClick(rightClick); } - public static void ExtendedCommand(NetState state, PacketReader reader) + public static void ExtendedCommand(NetState state, CircularBufferReader reader) { int packetID = reader.ReadUInt16(); @@ -1607,7 +1607,7 @@ namespace Server.Network } } - public static void CastSpell(NetState state, PacketReader reader) + public static void CastSpell(NetState state, CircularBufferReader reader) { var from = state.Mobile; @@ -1628,7 +1628,7 @@ namespace Server.Network EventSink.InvokeCastSpellRequest(from, spellID, spellbook); } - public static void BandageTarget(NetState state, PacketReader reader) + public static void BandageTarget(NetState state, CircularBufferReader reader) { var from = state.Mobile; @@ -1663,12 +1663,12 @@ namespace Server.Network } } - public static void ToggleFlying(NetState state, PacketReader reader) + public static void ToggleFlying(NetState state, CircularBufferReader reader) { state.Mobile.ToggleFlying(); } - public static void BatchQueryProperties(NetState state, PacketReader reader) + public static void BatchQueryProperties(NetState state, CircularBufferReader reader) { if (!ObjectPropertyList.Enabled) { @@ -1710,7 +1710,7 @@ namespace Server.Network } } - public static void QueryProperties(NetState state, PacketReader reader) + public static void QueryProperties(NetState state, CircularBufferReader reader) { if (!ObjectPropertyList.Enabled) { @@ -1742,7 +1742,7 @@ namespace Server.Network } } - public static void PartyMessage(NetState state, PacketReader reader) + public static void PartyMessage(NetState state, CircularBufferReader reader) { if (state.Mobile == null) { @@ -1778,17 +1778,17 @@ namespace Server.Network } } - public static void PartyMessage_AddMember(NetState state, PacketReader reader) + public static void PartyMessage_AddMember(NetState state, CircularBufferReader reader) { PartyCommands.Handler?.OnAdd(state.Mobile); } - public static void PartyMessage_RemoveMember(NetState state, PacketReader reader) + public static void PartyMessage_RemoveMember(NetState state, CircularBufferReader reader) { PartyCommands.Handler?.OnRemove(state.Mobile, World.FindMobile(reader.ReadUInt32())); } - public static void PartyMessage_PrivateMessage(NetState state, PacketReader reader) + public static void PartyMessage_PrivateMessage(NetState state, CircularBufferReader reader) { PartyCommands.Handler?.OnPrivateMessage( state.Mobile, @@ -1797,37 +1797,37 @@ namespace Server.Network ); } - public static void PartyMessage_PublicMessage(NetState state, PacketReader reader) + public static void PartyMessage_PublicMessage(NetState state, CircularBufferReader reader) { PartyCommands.Handler?.OnPublicMessage(state.Mobile, reader.ReadBigUniSafe()); } - public static void PartyMessage_SetCanLoot(NetState state, PacketReader reader) + public static void PartyMessage_SetCanLoot(NetState state, CircularBufferReader reader) { PartyCommands.Handler?.OnSetCanLoot(state.Mobile, reader.ReadBoolean()); } - public static void PartyMessage_Accept(NetState state, PacketReader reader) + public static void PartyMessage_Accept(NetState state, CircularBufferReader reader) { PartyCommands.Handler?.OnAccept(state.Mobile, World.FindMobile(reader.ReadUInt32())); } - public static void PartyMessage_Decline(NetState state, PacketReader reader) + public static void PartyMessage_Decline(NetState state, CircularBufferReader reader) { PartyCommands.Handler?.OnDecline(state.Mobile, World.FindMobile(reader.ReadUInt32())); } - public static void StunRequest(NetState state, PacketReader reader) + public static void StunRequest(NetState state, CircularBufferReader reader) { EventSink.InvokeStunRequest(state.Mobile); } - public static void DisarmRequest(NetState state, PacketReader reader) + public static void DisarmRequest(NetState state, CircularBufferReader reader) { EventSink.InvokeDisarmRequest(state.Mobile); } - public static void StatLockChange(NetState state, PacketReader reader) + public static void StatLockChange(NetState state, CircularBufferReader reader) { int stat = reader.ReadByte(); int lockValue = reader.ReadByte(); @@ -1856,13 +1856,13 @@ namespace Server.Network } } - public static void ScreenSize(NetState state, PacketReader reader) + public static void ScreenSize(NetState state, CircularBufferReader reader) { var width = reader.ReadInt32(); var unk = reader.ReadInt32(); } - public static void ContextMenuResponse(NetState state, PacketReader reader) + public static void ContextMenuResponse(NetState state, CircularBufferReader reader) { var from = state.Mobile; @@ -1918,7 +1918,7 @@ namespace Server.Network } } - public static void ContextMenuRequest(NetState state, PacketReader reader) + public static void ContextMenuRequest(NetState state, CircularBufferReader reader) { var from = state.Mobile; var target = World.FindEntity(reader.ReadUInt32()); @@ -1962,12 +1962,12 @@ namespace Server.Network } } - public static void CloseStatus(NetState state, PacketReader reader) + public static void CloseStatus(NetState state, CircularBufferReader reader) { Serial serial = reader.ReadUInt32(); } - public static void Language(NetState state, PacketReader reader) + public static void Language(NetState state, CircularBufferReader reader) { var lang = reader.ReadAscii(4); @@ -1977,20 +1977,20 @@ namespace Server.Network } } - public static void AssistVersion(NetState state, PacketReader reader) + public static void AssistVersion(NetState state, CircularBufferReader reader) { var unk = reader.ReadInt32(); var av = reader.ReadAscii(); } - public static void ClientVersion(NetState state, PacketReader reader) + public static void ClientVersion(NetState state, CircularBufferReader reader) { var version = state.Version = new CV(reader.ReadAscii()); EventSink.InvokeClientVersionReceived(state, version); } - public static void ClientType(NetState state, PacketReader reader) + public static void ClientType(NetState state, CircularBufferReader reader) { reader.ReadUInt16(); @@ -2000,7 +2000,7 @@ namespace Server.Network EventSink.InvokeClientVersionReceived(state, version); } - public static void MobileQuery(NetState state, PacketReader reader) + public static void MobileQuery(NetState state, CircularBufferReader reader) { var from = state.Mobile; @@ -2031,7 +2031,7 @@ namespace Server.Network } } - public static void PlayCharacter(NetState state, PacketReader reader) + public static void PlayCharacter(NetState state, CircularBufferReader reader) { reader.ReadInt32(); // 0xEDEDEDED @@ -2093,7 +2093,7 @@ namespace Server.Network } } - public static void ShowPublicHouseContent(NetState state, PacketReader reader) + public static void ShowPublicHouseContent(NetState state, CircularBufferReader reader) { var showPublicHouseContent = reader.ReadBoolean(); } @@ -2201,7 +2201,7 @@ namespace Server.Network m.ClearFastwalkStack(); } - public static void CreateCharacter(NetState state, PacketReader reader) + public static void CreateCharacter(NetState state, CircularBufferReader reader) { var unk1 = reader.ReadInt32(); var unk2 = reader.ReadInt32(); @@ -2337,7 +2337,7 @@ namespace Server.Network } } - public static void CreateCharacter70160(NetState state, PacketReader reader) + public static void CreateCharacter70160(NetState state, CircularBufferReader reader) { var unk1 = reader.ReadInt32(); var unk2 = reader.ReadInt32(); @@ -2499,7 +2499,7 @@ namespace Server.Network return authID; } - public static void GameLogin(NetState state, PacketReader reader) + public static void GameLogin(NetState state, CircularBufferReader reader) { if (state.SentFirstPacket) { @@ -2567,7 +2567,7 @@ namespace Server.Network } } - public static void PlayServer(NetState state, PacketReader reader) + public static void PlayServer(NetState state, CircularBufferReader reader) { int index = reader.ReadInt16(); var info = state.ServerInfo; @@ -2588,7 +2588,7 @@ namespace Server.Network } } - public static void LoginServerSeed(NetState state, PacketReader reader) + public static void LoginServerSeed(NetState state, CircularBufferReader reader) { state.m_Seed = reader.ReadInt32(); state.Seeded = true; @@ -2608,7 +2608,7 @@ namespace Server.Network state.Version = new ClientVersion(clientMaj, clientMin, clientRev, clientPat); } - public static void CrashReport(NetState state, PacketReader reader) + public static void CrashReport(NetState state, CircularBufferReader reader) { var clientMaj = reader.ReadByte(); var clientMin = reader.ReadByte(); @@ -2642,7 +2642,7 @@ namespace Server.Network } } - public static void AccountLogin(NetState state, PacketReader reader) + public static void AccountLogin(NetState state, CircularBufferReader reader) { if (state.SentFirstPacket) { @@ -2696,7 +2696,7 @@ namespace Server.Network state.Dispose(); } - public static void EquipMacro(NetState state, PacketReader reader) + public static void EquipMacro(NetState state, CircularBufferReader reader) { int count = reader.ReadByte(); var serialList = new List(count); @@ -2708,7 +2708,7 @@ namespace Server.Network EventSink.InvokeEquipMacro(state.Mobile, serialList); } - public static void UnequipMacro(NetState state, PacketReader reader) + public static void UnequipMacro(NetState state, CircularBufferReader reader) { int count = reader.ReadByte(); var layers = new List(count); @@ -2720,21 +2720,21 @@ namespace Server.Network EventSink.InvokeUnequipMacro(state.Mobile, layers); } - public static void TargetedSpell(NetState state, PacketReader reader) + public static void TargetedSpell(NetState state, CircularBufferReader reader) { var spellId = (short)(reader.ReadInt16() - 1); // zero based; EventSink.InvokeTargetedSpell(state.Mobile, World.FindEntity(reader.ReadUInt32()), spellId); } - public static void TargetedSkillUse(NetState state, PacketReader reader) + public static void TargetedSkillUse(NetState state, CircularBufferReader reader) { var skillId = reader.ReadInt16(); EventSink.InvokeTargetedSkillUse(state.Mobile, World.FindEntity(reader.ReadUInt32()), skillId); } - public static void TargetByResourceMacro(NetState state, PacketReader reader) + public static void TargetByResourceMacro(NetState state, CircularBufferReader reader) { Serial serial = reader.ReadUInt32(); diff --git a/Projects/Server/Network/Pipe.cs b/Projects/Server/Network/Pipe.cs index 63c1c0643..cf11309ac 100644 --- a/Projects/Server/Network/Pipe.cs +++ b/Projects/Server/Network/Pipe.cs @@ -118,55 +118,55 @@ namespace Server.Network return result; } - public void Advance(uint bytes) + public void Advance(uint count) { var read = _pipe._readIdx; var write = _pipe._writeIdx; - if (bytes == 0) + if (count == 0) { return; } - if (bytes > _pipe.Size - 1) + if (count > _pipe.Size - 1) { throw new InvalidOperationException(); } if (read <= write) { - if (bytes > read + _pipe.Size - write - 1) + if (count > read + _pipe.Size - write - 1) { throw new InvalidOperationException(); } - var sz = Math.Min(bytes, _pipe.Size - write); + var sz = Math.Min(count, _pipe.Size - write); write += sz; if (write > _pipe.Size - 1) { write = 0; } - bytes -= sz; + count -= sz; - if (bytes > 0) + if (count > 0) { - if (bytes >= read) + if (count >= read) { throw new InvalidOperationException(); } - write = bytes; + write = count; } } else { - if (bytes > read - write - 1) + if (count > read - write - 1) { throw new InvalidOperationException(); } - write += bytes; + write += count; } // It's never valid to advance the write pointer to become equal to @@ -216,7 +216,7 @@ namespace Server.Network internal PipeReader(Pipe pipe) => _pipe = pipe; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint GetRemaining() + public uint GetAvailable() { var read = _pipe._readIdx; var write = _pipe._writeIdx; @@ -260,39 +260,39 @@ namespace Server.Network return this; } - public void Advance(uint bytes) + public void Advance(uint count) { var read = _pipe._readIdx; var write = _pipe._writeIdx; if (read <= write) { - if (bytes > write - read) + if (count > write - read) { throw new InvalidOperationException(); } - read += bytes; + read += count; } else { - var sz = Math.Min(bytes, _pipe.Size - read); + var sz = Math.Min(count, _pipe.Size - read); read += sz; if (read > _pipe.Size - 1) { read = 0; } - bytes -= sz; + count -= sz; - if (bytes > 0) + if (count > 0) { - if (bytes > write) + if (count > write) { throw new InvalidOperationException(); } - read = bytes; + read = count; } } @@ -309,7 +309,7 @@ namespace Server.Network { get { - if (GetRemaining() > 0) + if (GetAvailable() > 0) { return true; } diff --git a/Projects/Server/Utilities/Utility.cs b/Projects/Server/Utilities/Utility.cs index 8529aaa53..7f49ba0ba 100644 --- a/Projects/Server/Utilities/Utility.cs +++ b/Projects/Server/Utilities/Utility.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Net; using System.Net.Sockets; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Text; using System.Xml; using Server.Random; @@ -106,6 +107,55 @@ namespace Server public static Encoding Unicode => m_Unicode ??= new UnicodeEncoding(true, false, false); public static Encoding UnicodeLE => m_UnicodeLE ??= new UnicodeEncoding(false, false, false); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetByteLengthForEncoding(Encoding encoding) => + encoding.BodyName switch + { + "utf-16BE" => 2, + "utf-16" => 2, + "utf-32BE" => 4, + "utf-32" => 4, + _ => 1 + }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOfTerminator(ReadOnlySpan buffer, int sizeT) => + sizeT switch + { + 2 => MemoryMarshal.Cast(buffer).IndexOf((char)0) * 2, + 4 => MemoryMarshal.Cast(buffer).IndexOf((uint)0) * 4, + _ => buffer.IndexOf((byte)0) + }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsSafeChar(ushort c) => c >= 0x20 && c < 0xFFFE; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string GetString(ReadOnlySpan span, Encoding encoding, bool safeString = false) + { + string s = encoding.GetString(span); + + if (!safeString) + { + return s; + } + + ReadOnlySpan chars = s.AsSpan(); + + StringBuilder stringBuilder = null; + + for (int i = 0, last = 0; i < chars.Length; i++) + { + if (!IsSafeChar(chars[i]) || stringBuilder != null && i == chars.Length - 1) + { + (stringBuilder ??= new StringBuilder()).Append(chars.Slice(last, i - last)); + last = i + 1; // Skip the unsafe char + } + } + + return stringBuilder?.ToString() ?? s; + } + public static void Separate(StringBuilder sb, string value, string separator) { if (sb.Length > 0) diff --git a/Projects/UOContent/Engines/Chat/Chat.cs b/Projects/UOContent/Engines/Chat/Chat.cs index 46184e8e6..4ce7ece73 100644 --- a/Projects/UOContent/Engines/Chat/Chat.cs +++ b/Projects/UOContent/Engines/Chat/Chat.cs @@ -23,7 +23,7 @@ namespace Server.Engines.Chat to?.Send(new ChatMessagePacket(null, (int)type + 20, param1, param2)); } - public static void OpenChatWindowRequest(NetState state, PacketReader reader) + public static void OpenChatWindowRequest(NetState state, CircularBufferReader reader) { var from = state.Mobile; @@ -56,7 +56,7 @@ namespace Server.Engines.Chat return user; } - public static void ChatAction(NetState state, PacketReader reader) + public static void ChatAction(NetState state, CircularBufferReader reader) { if (!Enabled) { diff --git a/Projects/UOContent/Engines/MLQuests/Gumps/RaceChangeGump.cs b/Projects/UOContent/Engines/MLQuests/Gumps/RaceChangeGump.cs index 5869bf00f..db2efa2ef 100644 --- a/Projects/UOContent/Engines/MLQuests/Gumps/RaceChangeGump.cs +++ b/Projects/UOContent/Engines/MLQuests/Gumps/RaceChangeGump.cs @@ -193,7 +193,7 @@ namespace Server.Engines.MLQuests.Gumps return false; } - private static void RaceChangeReply(NetState state, PacketReader reader) + private static void RaceChangeReply(NetState state, CircularBufferReader reader) { if (!m_Pending.TryGetValue(state, out var raceChangeState)) { diff --git a/Projects/UOContent/Items/Books/BaseBook.cs b/Projects/UOContent/Items/Books/BaseBook.cs index cd5d7cbaf..1ff98b2be 100644 --- a/Projects/UOContent/Items/Books/BaseBook.cs +++ b/Projects/UOContent/Items/Books/BaseBook.cs @@ -366,7 +366,7 @@ namespace Server.Items PacketHandlers.Register(0x93, 99, true, OldHeaderChange); } - public static void OldHeaderChange(NetState state, PacketReader reader) + public static void OldHeaderChange(NetState state, CircularBufferReader reader) { var from = state.Mobile; @@ -385,7 +385,7 @@ namespace Server.Items book.Author = Utility.FixHtml(author); } - public static void HeaderChange(NetState state, PacketReader reader) + public static void HeaderChange(NetState state, CircularBufferReader reader) { var from = state.Mobile; @@ -419,7 +419,7 @@ namespace Server.Items book.Author = Utility.FixHtml(author); } - public static void ContentChange(NetState state, PacketReader reader) + public static void ContentChange(NetState state, CircularBufferReader reader) { var from = state.Mobile; diff --git a/Projects/UOContent/Items/Games/Mahjong/MahjongPacketHandlers.cs b/Projects/UOContent/Items/Games/Mahjong/MahjongPacketHandlers.cs index afc2e5d78..e9d6675ae 100644 --- a/Projects/UOContent/Items/Games/Mahjong/MahjongPacketHandlers.cs +++ b/Projects/UOContent/Items/Games/Mahjong/MahjongPacketHandlers.cs @@ -2,7 +2,7 @@ using Server.Network; namespace Server.Engines.Mahjong { - public delegate void OnMahjongPacketReceive(MahjongGame game, NetState state, PacketReader reader); + public delegate void OnMahjongPacketReceive(MahjongGame game, NetState state, CircularBufferReader reader); public sealed class MahjongPacketHandlers { @@ -41,7 +41,7 @@ namespace Server.Engines.Mahjong RegisterSubCommand(0x18, MoveDealerIndicator); } - public static void OnPacket(NetState state,PacketReader reader) + public static void OnPacket(NetState state,CircularBufferReader reader) { var game = World.FindItem(reader.ReadUInt32()) as MahjongGame; @@ -85,7 +85,7 @@ namespace Server.Engines.Mahjong }; } - public static void ExitGame(MahjongGame game, NetState state, PacketReader reader) + public static void ExitGame(MahjongGame game, NetState state, CircularBufferReader reader) { if (game == null) { @@ -97,7 +97,7 @@ namespace Server.Engines.Mahjong game.Players.LeaveGame(from); } - public static void GivePoints(MahjongGame game, NetState state, PacketReader reader) + public static void GivePoints(MahjongGame game, NetState state, CircularBufferReader reader) { if (game?.Players.IsInGamePlayer(state.Mobile) != true) { @@ -110,7 +110,7 @@ namespace Server.Engines.Mahjong game.Players.TransferScore(state.Mobile, to, amount); } - public static void RollDice(MahjongGame game, NetState state, PacketReader reader) + public static void RollDice(MahjongGame game, NetState state, CircularBufferReader reader) { if (game?.Players.IsInGamePlayer(state.Mobile) != true) { @@ -120,7 +120,7 @@ namespace Server.Engines.Mahjong game.Dices.RollDices(state.Mobile); } - public static void BuildWalls(MahjongGame game, NetState state, PacketReader reader) + public static void BuildWalls(MahjongGame game, NetState state, CircularBufferReader reader) { if (game?.Players.IsInGameDealer(state.Mobile) != true) { @@ -130,7 +130,7 @@ namespace Server.Engines.Mahjong game.ResetWalls(state.Mobile); } - public static void ResetScores(MahjongGame game, NetState state, PacketReader reader) + public static void ResetScores(MahjongGame game, NetState state, CircularBufferReader reader) { if (game?.Players.IsInGameDealer(state.Mobile) != true) { @@ -140,7 +140,7 @@ namespace Server.Engines.Mahjong game.Players.ResetScores(MahjongGame.BaseScore); } - public static void AssignDealer(MahjongGame game, NetState state, PacketReader reader) + public static void AssignDealer(MahjongGame game, NetState state, CircularBufferReader reader) { if (game?.Players.IsInGameDealer(state.Mobile) != true) { @@ -152,7 +152,7 @@ namespace Server.Engines.Mahjong game.Players.AssignDealer(position); } - public static void OpenSeat(MahjongGame game, NetState state, PacketReader reader) + public static void OpenSeat(MahjongGame game, NetState state, CircularBufferReader reader) { if (game?.Players.IsInGameDealer(state.Mobile) != true) { @@ -169,7 +169,7 @@ namespace Server.Engines.Mahjong game.Players.OpenSeat(position); } - public static void ChangeOption(MahjongGame game, NetState state, PacketReader reader) + public static void ChangeOption(MahjongGame game, NetState state, CircularBufferReader reader) { if (game?.Players.IsInGameDealer(state.Mobile) != true) { @@ -185,7 +185,7 @@ namespace Server.Engines.Mahjong game.SpectatorVision = (options & 0x2) != 0; } - public static void MoveWallBreakIndicator(MahjongGame game, NetState state, PacketReader reader) + public static void MoveWallBreakIndicator(MahjongGame game, NetState state, CircularBufferReader reader) { if (game?.Players.IsInGameDealer(state.Mobile) != true) { @@ -198,7 +198,7 @@ namespace Server.Engines.Mahjong game.WallBreakIndicator.Move(new Point2D(x, y)); } - public static void TogglePublicHand(MahjongGame game, NetState state, PacketReader reader) + public static void TogglePublicHand(MahjongGame game, NetState state, CircularBufferReader reader) { if (game?.Players.IsInGamePlayer(state.Mobile) != true) { @@ -213,7 +213,7 @@ namespace Server.Engines.Mahjong game.Players.SetPublic(game.Players.GetPlayerIndex(state.Mobile), publicHand); } - public static void MoveTile(MahjongGame game, NetState state, PacketReader reader) + public static void MoveTile(MahjongGame game, NetState state, CircularBufferReader reader) { if (game?.Players.IsInGamePlayer(state.Mobile) != true) { @@ -248,7 +248,7 @@ namespace Server.Engines.Mahjong game.Tiles[number].Move(new Point2D(x, y), direction, flip, game.Players.GetPlayerIndex(state.Mobile)); } - public static void MoveDealerIndicator(MahjongGame game, NetState state, PacketReader reader) + public static void MoveDealerIndicator(MahjongGame game, NetState state, CircularBufferReader reader) { if (game?.Players.IsInGameDealer(state.Mobile) != true) { diff --git a/Projects/UOContent/Items/Maps/MapItem.cs b/Projects/UOContent/Items/Maps/MapItem.cs index 7d454b186..cfea9fac2 100644 --- a/Projects/UOContent/Items/Maps/MapItem.cs +++ b/Projects/UOContent/Items/Maps/MapItem.cs @@ -334,7 +334,7 @@ namespace Server.Items PacketHandlers.Register(0x56, 11, true, OnMapCommand); } - private static void OnMapCommand(NetState state, PacketReader reader) + private static void OnMapCommand(NetState state, CircularBufferReader reader) { var from = state.Mobile; diff --git a/Projects/UOContent/Items/Misc/BulletinBoards.cs b/Projects/UOContent/Items/Misc/BulletinBoards.cs index 7922cdbfb..e0f0c53cb 100644 --- a/Projects/UOContent/Items/Misc/BulletinBoards.cs +++ b/Projects/UOContent/Items/Misc/BulletinBoards.cs @@ -232,7 +232,7 @@ namespace Server.Items PacketHandlers.Register(0x71, 0, true, BBClientRequest); } - public static void BBClientRequest(NetState state, PacketReader reader) + public static void BBClientRequest(NetState state, CircularBufferReader reader) { var from = state.Mobile; @@ -260,7 +260,7 @@ namespace Server.Items } } - public static void BBRequestContent(Mobile from, BaseBulletinBoard board, PacketReader reader) + public static void BBRequestContent(Mobile from, BaseBulletinBoard board, CircularBufferReader reader) { if (!(World.FindItem(reader.ReadUInt32()) is BulletinMessage msg) || msg.Parent != board) { @@ -270,7 +270,7 @@ namespace Server.Items from.Send(new BBMessageContent(board, msg)); } - public static void BBRequestHeader(Mobile from, BaseBulletinBoard board, PacketReader reader) + public static void BBRequestHeader(Mobile from, BaseBulletinBoard board, CircularBufferReader reader) { if (!(World.FindItem(reader.ReadUInt32()) is BulletinMessage msg) || msg.Parent != board) { @@ -280,7 +280,7 @@ namespace Server.Items from.Send(new BBMessageHeader(board, msg)); } - public static void BBPostMessage(Mobile from, BaseBulletinBoard board, PacketReader reader) + public static void BBPostMessage(Mobile from, BaseBulletinBoard board, CircularBufferReader reader) { var thread = World.FindItem(reader.ReadUInt32()) as BulletinMessage; @@ -337,7 +337,7 @@ namespace Server.Items board.PostMessage(from, thread, subject, lines); } - public static void BBRemoveMessage(Mobile from, BaseBulletinBoard board, PacketReader reader) + public static void BBRemoveMessage(Mobile from, BaseBulletinBoard board, CircularBufferReader reader) { if (!(World.FindItem(reader.ReadUInt32()) is BulletinMessage msg) || msg.Parent != board) { diff --git a/Projects/UOContent/Misc/HardwareInfo.cs b/Projects/UOContent/Misc/HardwareInfo.cs index d02f32a1a..f0968aec4 100644 --- a/Projects/UOContent/Misc/HardwareInfo.cs +++ b/Projects/UOContent/Misc/HardwareInfo.cs @@ -142,7 +142,7 @@ namespace Server } } - public static void OnReceive(NetState state, PacketReader reader) + public static void OnReceive(NetState state, CircularBufferReader reader) { reader.ReadByte(); // 1: <4.0.1a, 2>=4.0.1a diff --git a/Projects/UOContent/Misc/MapUO.cs b/Projects/UOContent/Misc/MapUO.cs index 3fa0f6ddf..b163343a3 100644 --- a/Projects/UOContent/Misc/MapUO.cs +++ b/Projects/UOContent/Misc/MapUO.cs @@ -20,7 +20,7 @@ namespace Server.Misc } } - private static void OnPartyTrack(NetState state, PacketReader reader) + private static void OnPartyTrack(NetState state, CircularBufferReader reader) { var from = state.Mobile; var party = Party.Get(from); @@ -36,7 +36,7 @@ namespace Server.Misc } } - private static void OnGuildTrack(NetState state, PacketReader reader) + private static void OnGuildTrack(NetState state, CircularBufferReader reader) { var from = state.Mobile; diff --git a/Projects/UOContent/Misc/ProtocolExtensions.cs b/Projects/UOContent/Misc/ProtocolExtensions.cs index ce374ab2e..9504d57bd 100644 --- a/Projects/UOContent/Misc/ProtocolExtensions.cs +++ b/Projects/UOContent/Misc/ProtocolExtensions.cs @@ -19,7 +19,7 @@ namespace Server.Misc public static PacketHandler GetHandler(int packetID) => packetID >= 0 && packetID < m_Handlers.Length ? m_Handlers[packetID] : null; - public static void DecodeBundledPacket(NetState state, PacketReader reader) + public static void DecodeBundledPacket(NetState state, CircularBufferReader reader) { int packetID = reader.ReadByte(); diff --git a/Projects/UOContent/Multis/HouseFoundation.cs b/Projects/UOContent/Multis/HouseFoundation.cs index ad0c7867f..9edf0918e 100644 --- a/Projects/UOContent/Multis/HouseFoundation.cs +++ b/Projects/UOContent/Multis/HouseFoundation.cs @@ -1770,7 +1770,7 @@ namespace Server.Multis context.Foundation.SendInfoTo(state); } - public static void QueryDesignDetails(NetState state, PacketReader reader) + public static void QueryDesignDetails(NetState state, CircularBufferReader reader) { var from = state.Mobile;