diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 3f7bff33a..2fc15b4df 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -21,5 +21,7 @@ jobs: run: dotnet restore - name: Build run: dotnet build --no-restore /p:WarningsAsErrors=nullable - - name: Test + - name: Test Engine run: dotnet test --no-build Robust.UnitTesting/Robust.UnitTesting.csproj -v n + - name: Test Lidgren + run: dotnet test --no-build Lidgren.Network.UnitTests/Lidgren.Network.UnitTests.csproj -v n diff --git a/Lidgren.Network.UnitTests/Lidgren.Network.UnitTests.csproj b/Lidgren.Network.UnitTests/Lidgren.Network.UnitTests.csproj new file mode 100644 index 000000000..17889f534 --- /dev/null +++ b/Lidgren.Network.UnitTests/Lidgren.Network.UnitTests.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + + + + + diff --git a/Lidgren.Network.UnitTests/ReadWriteTests.cs b/Lidgren.Network.UnitTests/ReadWriteTests.cs new file mode 100644 index 000000000..e6fd6938a --- /dev/null +++ b/Lidgren.Network.UnitTests/ReadWriteTests.cs @@ -0,0 +1,229 @@ +using System; +using System.Text; +using NUnit.Framework; +using static System.Reflection.BindingFlags; + +namespace Lidgren.Network.UnitTests +{ + + public class ReadWriteTests : TestsBase + { + + [Test] + public void TestSmallMessage1() + { + var peer = Peer; + var msg = peer.CreateMessage(); + + msg.Write(false); + msg.Write(-3, 6); + msg.Write(true); + + msg.WritePadBits(); + + var data = msg.Buffer; + + var inc = CreateIncomingMessage(data, msg.LengthBits); + + var boolean = inc.ReadBoolean(); + Assert.IsFalse(boolean); + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + + var value = inc.ReadInt32(6); + Assert.AreEqual(-3, value); + + boolean = inc.ReadBoolean(); + Assert.IsTrue(boolean); + + inc.SkipPadBits(); + } + + [Test] + public void TestSmallMessage2() + { + var peer = Peer; + var msg = peer.CreateMessage(); + + msg.Write(false); + msg.Write(-3, 6); + msg.Write(42); + msg.Write("duke of earl"); + msg.Write((byte) 43); + msg.Write((ushort) 44); + msg.Write(UInt64.MaxValue, 64); + var c = msg.WriteVariableInt64(long.MaxValue >> 16); + msg.Write(true); + Assert.AreEqual(7, c); + + msg.WritePadBits(); + + var data = msg.Buffer; + + var inc = CreateIncomingMessage(data, msg.LengthBits); + + var boolean = inc.ReadBoolean(); + Assert.IsFalse(boolean); + + var value = inc.ReadInt32(6); + Assert.AreEqual(-3, value); + + value = inc.ReadInt32(); + Assert.AreEqual(42, value); + + var ok = inc.ReadString(out var strResult); + Assert.IsTrue(ok, "Read/write failure"); + Assert.AreEqual("duke of earl", strResult); + + var byteVal = inc.ReadByte(); + Assert.AreEqual(43,byteVal); + + Assert.AreEqual((ushort) 44, inc.ReadUInt16(), "Read/write failure"); + + Assert.AreEqual(UInt64.MaxValue, inc.ReadUInt64(64), "Read/write failure"); + + var longVal = inc.ReadVariableInt64(); + Assert.AreEqual(long.MaxValue >> 16, longVal); + + boolean = inc.ReadBoolean(); + Assert.IsTrue(boolean); + + inc.SkipPadBits(); + } + + [Test] + public void TestComplexMessage() + { + var peer = Peer; + var msg = peer.CreateMessage(); + + msg.Write(false); + msg.Write(-3, 6); + msg.Write(42); + msg.Write("duke of earl"); + msg.Write((byte) 43); + msg.Write((ushort) 44); + msg.Write(UInt64.MaxValue, 64); + msg.Write(true); + + msg.WritePadBits(); + + var bcnt = 0; + + msg.Write(567845.0f); + msg.WriteVariableInt32(2115998022); + msg.Write(46.0); + msg.Write((ushort) 14, 9); + bcnt += msg.WriteVariableInt32(-47); + msg.WriteVariableInt32(470000); + msg.WriteVariableUInt32(48); + bcnt += msg.WriteVariableInt64(-49); + + Assert.AreEqual(2, bcnt, "WriteVariable* wrote too many bytes!"); + + var data = msg.Buffer; + + var inc = CreateIncomingMessage(data, msg.LengthBits); + + var boolean = inc.ReadBoolean(); + Assert.IsFalse(boolean); + var value = inc.ReadInt32(6); + Assert.AreEqual(-3, value); + value = inc.ReadInt32(); + Assert.AreEqual(42, value); + + var ok = inc.ReadString(out var strResult); + Assert.IsTrue(ok, "Read/write failure"); + Assert.AreEqual("duke of earl", strResult); + + var byteVal = inc.ReadByte(); + Assert.AreEqual(43, byteVal); + + Assert.AreEqual((ushort) 44, inc.ReadUInt16(), "Read/write failure"); + + Assert.AreEqual(UInt64.MaxValue, inc.ReadUInt64(64), "Read/write failure"); + + boolean = inc.ReadBoolean(); + Assert.IsTrue(boolean); + + inc.SkipPadBits(); + + Assert.AreEqual(567845.0f, inc.ReadSingle()); + Assert.AreEqual(2115998022, inc.ReadVariableInt32()); + Assert.AreEqual(46.0, inc.ReadDouble()); + Assert.AreEqual(14, inc.ReadUInt32(9)); + Assert.AreEqual(-47, inc.ReadVariableInt32()); + Assert.AreEqual(470000, inc.ReadVariableInt32()); + Assert.AreEqual(48, inc.ReadVariableUInt32()); + Assert.AreEqual(-49, inc.ReadVariableInt64()); + } + + [Test] + public void TestWriteAllFields() + { + var peer = Peer; + + var msg = peer.CreateMessage(); + + var tmp = peer.CreateMessage(); + tmp.Write((int) 42, 14); + + msg.Write(tmp); + msg.Write(tmp); + + Assert.AreEqual(tmp.LengthBits * 2, msg.LengthBits, "NetOutgoingMessage.Write(NetOutgoingMessage) failed!"); + + tmp = peer.CreateMessage(); + + var testItem = new TestItem + { + Number = 42, + Name = "Hallon", + Age = 8.2f + }; + + tmp.WriteAllFields(testItem, Public | Instance); + + var data = tmp.Buffer; + + var inc = CreateIncomingMessage(data, tmp.LengthBits); + + var readTestItem = new TestItem(); + inc.ReadAllFields(readTestItem, Public | Instance); + + Assert.AreEqual(42, readTestItem.Number); + Assert.AreEqual("Hallon", readTestItem.Name); + Assert.AreEqual(8.2f, readTestItem.Age); + + // test aligned WriteBytes/ReadBytes + msg = peer.CreateMessage(); + var tmparr = new byte[] {5, 6, 7, 8, 9}; + msg.Write(tmparr); + + inc = CreateIncomingMessage(msg.Buffer, msg.LengthBits); + var result = inc.ReadBytes(stackalloc byte[tmparr.Length]); + + for (var i = 0; i < tmparr.Length; i++) + { + Assert.AreEqual(result[i], tmparr[i], "readbytes fail"); + } + } + + public class TestItemBase + { + + public int Number; + + } + + public class TestItem : TestItemBase + { + + public float Age; + + public string Name; + + } + + } + +} diff --git a/Lidgren.Network.UnitTests/TestsBase.cs b/Lidgren.Network.UnitTests/TestsBase.cs new file mode 100644 index 000000000..37149be9e --- /dev/null +++ b/Lidgren.Network.UnitTests/TestsBase.cs @@ -0,0 +1,76 @@ +using System; +using System.Text; +using NUnit.Framework; +using static System.Reflection.BindingFlags; + +namespace Lidgren.Network.UnitTests +{ + + public abstract class TestsBase + { + + protected NetPeerConfiguration Config; + + protected NetPeer Peer; + + [SetUp] + public void Setup() + { + Config = new NetPeerConfiguration(GetType().Name) + { + EnableUPnP = true + }; + Peer = new NetPeer(Config); + Peer.Start(); + + TestContext.Out.WriteLine("Unique identifier is " + NetUtility.ToHexString(Peer.UniqueIdentifier)); + } + + [TearDown] + public void TearDown() + => Peer.Shutdown("Unit test teardown."); + + protected static NetIncomingMessage CreateIncomingMessage(byte[] fromData, int bitLength) + { + var inc = (NetIncomingMessage) Activator.CreateInstance(typeof(NetIncomingMessage), true); + typeof(NetIncomingMessage).GetField("m_buf", NonPublic | Instance)! + .SetValue(inc, fromData); + typeof(NetIncomingMessage).GetField("m_bitLength", NonPublic | Instance)! + .SetValue(inc, bitLength); + return inc; + } + + protected static string ToBinaryString(ulong value, int bits, bool includeSpaces) + { + var numSpaces = Math.Max(0, bits / 8 - 1); + if (includeSpaces == false) + { + numSpaces = 0; + } + + var bdr = new StringBuilder(bits + numSpaces); + for (var i = 0; i < bits + numSpaces; i++) + { + bdr.Append(' '); + } + + for (var i = 0; i < bits; i++) + { + var shifted = value >> i; + var isSet = (shifted & 1) != 0; + + var pos = bits - 1 - i; + if (includeSpaces) + { + pos += Math.Max(0, pos / 8); + } + + bdr[pos] = isSet ? '1' : '0'; + } + + return bdr.ToString(); + } + + } + +} diff --git a/Lidgren.Network/Encryption/NetAESEncryption.cs b/Lidgren.Network/Encryption/NetAESEncryption.cs index 04bbe8b26..8f64d7b00 100644 --- a/Lidgren.Network/Encryption/NetAESEncryption.cs +++ b/Lidgren.Network/Encryption/NetAESEncryption.cs @@ -25,7 +25,7 @@ namespace Lidgren.Network SetKey(key); } - public NetAESEncryption(NetPeer peer, byte[] data, int offset, int count) + public NetAESEncryption(NetPeer peer, ReadOnlySpan data, int offset, int count) #if UNITY : base(peer, new RijndaelManaged()) #else diff --git a/Lidgren.Network/Encryption/NetBlockEncryptionBase.cs b/Lidgren.Network/Encryption/NetBlockEncryptionBase.cs index f16aeba1e..8a6d7b2c7 100644 --- a/Lidgren.Network/Encryption/NetBlockEncryptionBase.cs +++ b/Lidgren.Network/Encryption/NetBlockEncryptionBase.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.Collections.Generic; namespace Lidgren.Network @@ -8,8 +9,6 @@ namespace Lidgren.Network /// public abstract class NetBlockEncryptionBase : NetEncryption { - // temporary space for one block to avoid reallocating every time - private byte[] m_tmp; /// /// Block size in bytes for this cipher @@ -22,7 +21,6 @@ namespace Lidgren.Network public NetBlockEncryptionBase(NetPeer peer) : base(peer) { - m_tmp = new byte[BlockSize]; } /// @@ -33,16 +31,20 @@ namespace Lidgren.Network int payloadBitLength = msg.LengthBits; int numBytes = msg.LengthBytes; int blockSize = BlockSize; - int numBlocks = (int)Math.Ceiling((double)numBytes / (double)blockSize); + Span tmp = stackalloc byte[BlockSize]; + int numBlocks = (int)Math.Ceiling(numBytes / (double)blockSize); int dstSize = numBlocks * blockSize; msg.EnsureBufferSize(dstSize * 8 + (4 * 8)); // add 4 bytes for payload length at end msg.LengthBits = dstSize * 8; // length will automatically adjust +4 bytes when payload length is written + var dataSpan = msg.m_data; for(int i=0;i tmp = stackalloc byte[BlockSize]; int numBlocks = numEncryptedBytes / blockSize; if (numBlocks * blockSize != numEncryptedBytes) return false; + var dataSpan = msg.m_data; for (int i = 0; i < numBlocks; i++) { - DecryptBlock(msg.m_data, (i * blockSize), m_tmp); - Buffer.BlockCopy(m_tmp, 0, msg.m_data, (i * blockSize), m_tmp.Length); + // TODO: honestly is tmp even necessary here? + DecryptBlock(dataSpan, (i * blockSize), tmp); + //Buffer.BlockCopy(m_tmp, 0, dataSpan, (i * blockSize), m_tmp.Length); + tmp.CopyTo(dataSpan.Slice(i * blockSize, tmp.Length)); } // read 32 bits of true payload length - uint realSize = NetBitWriter.ReadUInt32(msg.m_data, 32, (numEncryptedBytes * 8)); + uint realSize = NetBitWriter.ReadUInt32(dataSpan, 32, (numEncryptedBytes * 8)); msg.m_bitLength = (int)realSize; return true; } @@ -79,11 +85,13 @@ namespace Lidgren.Network /// /// Encrypt a block of bytes /// - protected abstract void EncryptBlock(byte[] source, int sourceOffset, byte[] destination); + protected abstract void EncryptBlock(ReadOnlySpan source, int sourceOffset, Span destination); /// /// Decrypt a block of bytes /// - protected abstract void DecryptBlock(byte[] source, int sourceOffset, byte[] destination); + protected abstract void DecryptBlock(ReadOnlySpan source, int sourceOffset, Span destination); + + } } diff --git a/Lidgren.Network/Encryption/NetCryptoProviderBase.cs b/Lidgren.Network/Encryption/NetCryptoProviderBase.cs index e3ff3f4d5..60d39dc3a 100644 --- a/Lidgren.Network/Encryption/NetCryptoProviderBase.cs +++ b/Lidgren.Network/Encryption/NetCryptoProviderBase.cs @@ -4,8 +4,10 @@ using System.Security.Cryptography; namespace Lidgren.Network { + public abstract class NetCryptoProviderBase : NetEncryption { + protected SymmetricAlgorithm m_algorithm; public NetCryptoProviderBase(NetPeer peer, SymmetricAlgorithm algo) @@ -16,9 +18,10 @@ namespace Lidgren.Network m_algorithm.GenerateIV(); } - public override void SetKey(byte[] data, int offset, int count) + public override void SetKey(ReadOnlySpan data, int offset, int count) { int len = m_algorithm.Key.Length; + // ReSharper disable once SuggestVarOrType_Elsewhere var key = new byte[len]; for (int i = 0; i < len; i++) key[i] = data[offset + (i % count)]; @@ -35,9 +38,9 @@ namespace Lidgren.Network { int unEncLenBits = msg.LengthBits; - var ms = new MemoryStream(); + using var ms = new MemoryStream(msg.LengthBytes); var cs = new CryptoStream(ms, m_algorithm.CreateEncryptor(), CryptoStreamMode.Write); - cs.Write(msg.m_data, 0, msg.LengthBytes); + cs.Write(msg.m_data.Slice(0, msg.LengthBytes)); cs.Close(); // get results @@ -46,7 +49,7 @@ namespace Lidgren.Network msg.EnsureBufferSize((arr.Length + 4) * 8); msg.LengthBits = 0; // reset write pointer - msg.Write((uint)unEncLenBits); + msg.Write((uint) unEncLenBits); msg.Write(arr); msg.LengthBits = (arr.Length + 4) * 8; @@ -55,23 +58,26 @@ namespace Lidgren.Network public override bool Decrypt(NetIncomingMessage msg) { - int unEncLenBits = (int)msg.ReadUInt32(); + int unEncLenBits = (int) msg.ReadUInt32(); - var ms = new MemoryStream(msg.m_data, 4, msg.LengthBytes - 4); + byte[] result; + using var ms = new MemoryStream(msg.m_buf, 4, msg.LengthBytes - 4); var cs = new CryptoStream(ms, m_algorithm.CreateDecryptor(), CryptoStreamMode.Read); var byteLen = NetUtility.BytesToHoldBits(unEncLenBits); - var result = m_peer.GetStorage(byteLen); + result = m_peer.GetStorage(byteLen); cs.Read(result, 0, byteLen); cs.Close(); // TODO: recycle existing msg - msg.m_data = result; + msg.m_buf = result; msg.m_bitLength = unEncLenBits; msg.m_readPosition = 0; return true; } + } + } diff --git a/Lidgren.Network/Encryption/NetCryptoProviderEncryption.cs b/Lidgren.Network/Encryption/NetCryptoProviderEncryption.cs index d1120465b..a1b3b1b45 100644 --- a/Lidgren.Network/Encryption/NetCryptoProviderEncryption.cs +++ b/Lidgren.Network/Encryption/NetCryptoProviderEncryption.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Runtime.InteropServices; using System.Security.Cryptography; namespace Lidgren.Network @@ -11,17 +12,18 @@ namespace Lidgren.Network { } - protected abstract CryptoStream GetEncryptStream(MemoryStream ms); + protected abstract CryptoStream GetEncryptStream(Stream ms); - protected abstract CryptoStream GetDecryptStream(MemoryStream ms); + protected abstract CryptoStream GetDecryptStream(Stream ms); public override bool Encrypt(NetOutgoingMessage msg) { int unEncLenBits = msg.LengthBits; - var ms = new MemoryStream(); + using var ms = new MemoryStream(msg.LengthBytes); var cs = GetEncryptStream(ms); - cs.Write(msg.m_data, 0, msg.LengthBytes); + var dataSpan = msg.m_data; + cs.Write(dataSpan.Slice(0,msg.LengthBytes)); cs.Close(); // get results @@ -41,8 +43,8 @@ namespace Lidgren.Network { int unEncLenBits = (int)msg.ReadUInt32(); - var ms = new MemoryStream(msg.m_data, 4, msg.LengthBytes - 4); - var cs = GetDecryptStream(ms); + using var ums = new MemoryStream(msg.m_buf, 4, msg.LengthBytes - 4); + var cs = GetDecryptStream(ums); var result = m_peer.GetStorage(unEncLenBits); cs.Read(result, 0, NetUtility.BytesToHoldBits(unEncLenBits)); @@ -50,7 +52,7 @@ namespace Lidgren.Network // TODO: recycle existing msg - msg.m_data = result; + msg.m_buf = result; msg.m_bitLength = unEncLenBits; return true; diff --git a/Lidgren.Network/Encryption/NetEncryption.cs b/Lidgren.Network/Encryption/NetEncryption.cs index fea1e7c33..83569f018 100644 --- a/Lidgren.Network/Encryption/NetEncryption.cs +++ b/Lidgren.Network/Encryption/NetEncryption.cs @@ -30,7 +30,7 @@ namespace Lidgren.Network SetKey(bytes, 0, bytes.Length); } - public abstract void SetKey(byte[] data, int offset, int count); + public abstract void SetKey(ReadOnlySpan data, int offset, int count); /// /// Encrypt an outgoing message in place diff --git a/Lidgren.Network/Encryption/NetXorEncryption.cs b/Lidgren.Network/Encryption/NetXorEncryption.cs index 04d8e50fd..5a646f0a2 100644 --- a/Lidgren.Network/Encryption/NetXorEncryption.cs +++ b/Lidgren.Network/Encryption/NetXorEncryption.cs @@ -9,21 +9,21 @@ namespace Lidgren.Network /// public class NetXorEncryption : NetEncryption { - private byte[] m_key; + private Memory m_key; /// /// NetXorEncryption constructor /// - public NetXorEncryption(NetPeer peer, byte[] key) + public NetXorEncryption(NetPeer peer, Memory key) : base(peer) { m_key = key; } - public override void SetKey(byte[] data, int offset, int count) + public override void SetKey(ReadOnlySpan data, int offset, int count) { m_key = new byte[count]; - Array.Copy(data, offset, m_key, 0, count); + data.CopyTo(m_key.Span); } /// @@ -41,10 +41,12 @@ namespace Lidgren.Network public override bool Encrypt(NetOutgoingMessage msg) { int numBytes = msg.LengthBytes; + var dataSpan = msg.m_data; + var keySpan = m_key.Span; for (int i = 0; i < numBytes; i++) { - int offset = i % m_key.Length; - msg.m_data[i] = (byte)(msg.m_data[i] ^ m_key[offset]); + int offset = i % keySpan.Length; + dataSpan[i] = (byte)(dataSpan[i] ^ keySpan[offset]); } return true; } @@ -55,10 +57,12 @@ namespace Lidgren.Network public override bool Decrypt(NetIncomingMessage msg) { int numBytes = msg.LengthBytes; + var keySpan = m_key.Span; + var dataSpan = msg.m_data; for (int i = 0; i < numBytes; i++) { - int offset = i % m_key.Length; - msg.m_data[i] = (byte)(msg.m_data[i] ^ m_key[offset]); + int offset = i % keySpan.Length; + dataSpan[i] = (byte)(dataSpan[i] ^ keySpan[offset]); } return true; } diff --git a/Lidgren.Network/Encryption/NetXteaEncryption.cs b/Lidgren.Network/Encryption/NetXteaEncryption.cs index 600e408e6..7de298854 100644 --- a/Lidgren.Network/Encryption/NetXteaEncryption.cs +++ b/Lidgren.Network/Encryption/NetXteaEncryption.cs @@ -79,17 +79,11 @@ namespace Lidgren.Network { } - /// - /// String to hash for key - /// - public NetXtea(NetPeer peer, string key) - : this(peer, NetUtility.ComputeSHAHash(Encoding.UTF8.GetBytes(key)), 32) + public override void SetKey(ReadOnlySpan data, int offset, int length) { - } - - public override void SetKey(byte[] data, int offset, int length) - { - var key = NetUtility.ComputeSHAHash(data, offset, length); + // ReSharper disable once SuggestVarOrType_Elsewhere + Span key = stackalloc byte[32]; + NetUtility.ComputeSHAHash(data.Slice( offset, length), key); NetException.Assert(key.Length >= 16); SetKey(key, 0, 16); } @@ -97,7 +91,7 @@ namespace Lidgren.Network /// /// Encrypts a block of bytes /// - protected override void EncryptBlock(byte[] source, int sourceOffset, byte[] destination) + protected override void EncryptBlock(ReadOnlySpan source, int sourceOffset, Span destination) { uint v0 = BytesToUInt(source, sourceOffset); uint v1 = BytesToUInt(source, sourceOffset + 4); @@ -117,7 +111,7 @@ namespace Lidgren.Network /// /// Decrypts a block of bytes /// - protected override void DecryptBlock(byte[] source, int sourceOffset, byte[] destination) + protected override void DecryptBlock(ReadOnlySpan source, int sourceOffset, Span destination) { // Pack bytes into integers uint v0 = BytesToUInt(source, sourceOffset); @@ -135,7 +129,7 @@ namespace Lidgren.Network return; } - private static uint BytesToUInt(byte[] bytes, int offset) + private static uint BytesToUInt(ReadOnlySpan bytes, int offset) { uint retval = (uint)(bytes[offset] << 24); retval |= (uint)(bytes[++offset] << 16); @@ -143,7 +137,7 @@ namespace Lidgren.Network return (retval | bytes[++offset]); } - private static void UIntToBytes(uint value, byte[] destination, int destinationOffset) + private static void UIntToBytes(uint value, Span destination, int destinationOffset) { destination[destinationOffset++] = (byte)(value >> 24); destination[destinationOffset++] = (byte)(value >> 16); diff --git a/Lidgren.Network/Lidgren.Network.csproj b/Lidgren.Network/Lidgren.Network.csproj index d8519b33b..a1768290a 100644 --- a/Lidgren.Network/Lidgren.Network.csproj +++ b/Lidgren.Network/Lidgren.Network.csproj @@ -1,11 +1,13 @@  - netstandard2.0 + netstandard2.1 false false Debug;Release AnyCPU true - $(DefineConstants);USE_RELEASE_STATISTICS + $(DefineConstants);USE_RELEASE_STATISTICS;UNSAFE + 8 + true diff --git a/Lidgren.Network/NetBigInteger.cs b/Lidgren.Network/NetBigInteger.cs index f9a607d85..302b7909e 100644 --- a/Lidgren.Network/NetBigInteger.cs +++ b/Lidgren.Network/NetBigInteger.cs @@ -256,13 +256,13 @@ namespace Lidgren.Network } public NetBigInteger( - byte[] bytes) + ReadOnlySpan bytes) : this(bytes, 0, bytes.Length) { } public NetBigInteger( - byte[] bytes, + ReadOnlySpan bytes, int offset, int length) { @@ -287,7 +287,7 @@ namespace Lidgren.Network else { int numBytes = end - iBval; - byte[] inverse = new byte[numBytes]; + Span inverse = stackalloc byte[numBytes]; int index = 0; while (index < numBytes) @@ -316,7 +316,7 @@ namespace Lidgren.Network } private static int[] MakeMagnitude( - byte[] bytes, + ReadOnlySpan bytes, int offset, int length) { @@ -374,14 +374,14 @@ namespace Lidgren.Network public NetBigInteger( int sign, - byte[] bytes) + ReadOnlySpan bytes) : this(sign, bytes, 0, bytes.Length) { } public NetBigInteger( int sign, - byte[] bytes, + ReadOnlySpan bytes, int offset, int length) { diff --git a/Lidgren.Network/NetBitWriter.cs b/Lidgren.Network/NetBitWriter.cs index c641c49c8..516bbfbbf 100644 --- a/Lidgren.Network/NetBitWriter.cs +++ b/Lidgren.Network/NetBitWriter.cs @@ -19,10 +19,11 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR TH USE OR OTHER DEALINGS IN THE SOFTWARE. */ -using System; -using System.Collections.Generic; +using System; +using System.Buffers.Binary; using System.Diagnostics; +using System.Runtime.InteropServices; namespace Lidgren.Network { @@ -34,7 +35,7 @@ namespace Lidgren.Network /// /// Read 1-8 bits from a buffer into a byte /// - public static byte ReadByte(byte[] fromBuffer, int numberOfBits, int readBitOffset) + public static byte ReadByte(ReadOnlySpan fromBuffer, int numberOfBits, int readBitOffset) { NetException.Assert(((numberOfBits > 0) && (numberOfBits < 9)), "Read() can only read between 1 and 8 bits"); @@ -67,14 +68,16 @@ namespace Lidgren.Network /// /// Read several bytes from a buffer /// - public static void ReadBytes(byte[] fromBuffer, int numberOfBytes, int readBitOffset, byte[] destination, int destinationByteOffset) + public static void ReadBytes(ReadOnlySpan fromBuffer, int numberOfBytes, int readBitOffset, Span destination, int destinationByteOffset) { int readPtr = readBitOffset >> 3; int startReadAtIndex = readBitOffset - (readPtr * 8); // (readBitOffset % 8); if (startReadAtIndex == 0) { - Buffer.BlockCopy(fromBuffer, readPtr, destination, destinationByteOffset, numberOfBytes); + //Buffer.BlockCopy(fromBuffer, readPtr, destination, destinationByteOffset, numberOfBytes); + fromBuffer.Slice(readPtr,numberOfBytes) + .CopyTo(destination.Slice(destinationByteOffset, numberOfBytes)); return; } @@ -93,14 +96,48 @@ namespace Lidgren.Network destination[destinationByteOffset++] = (byte)(b | (second << secondPartLen)); } + } - return; + + /// + /// Read several bytes from a buffer + /// + public static void ReadBytes(ReadOnlySpan fromBuffer, int readBitOffset, Span destination) + { + var destinationByteOffset = 0; + var numberOfBytes = (fromBuffer.Length << 3 - readBitOffset) >> 3; + int readPtr = readBitOffset >> 3; + int startReadAtIndex = readBitOffset - (readPtr * 8); // (readBitOffset % 8); + + if (startReadAtIndex == 0) + { + //Buffer.BlockCopy(fromBuffer, readPtr, destination, destinationByteOffset, numberOfBytes); + fromBuffer.Slice(readPtr,numberOfBytes) + .CopyTo(destination.Slice(0, numberOfBytes)); + return; + } + + int secondPartLen = 8 - startReadAtIndex; + int secondMask = 255 >> secondPartLen; + + for (int i = 0; i < numberOfBytes; i++) + { + // mask away unused bits lower than (right of) relevant bits in byte + int b = fromBuffer[readPtr] >> startReadAtIndex; + + readPtr++; + + // mask away unused bits higher than (left of) relevant bits in second byte + int second = fromBuffer[readPtr] & secondMask; + + destination[destinationByteOffset++] = (byte)(b | (second << secondPartLen)); + } } /// /// Write 0-8 bits of data to buffer /// - public static void WriteByte(byte source, int numberOfBits, byte[] destination, int destBitOffset) + public static void WriteByte(byte source, int numberOfBits, Span destination, int destBitOffset) { if (numberOfBits == 0) return; @@ -149,18 +186,63 @@ namespace Lidgren.Network (source >> bitsFree) ); } + + /// + /// Zero a number of bits + /// + public static void Zero(int numberOfBits, Span destination, int destBitOffset) + { + var dstBytePtr = destBitOffset >> 3; + var firstPartLen = destBitOffset & 7; + var numberOfBytes = numberOfBits >> 3; + var endBits = numberOfBits & 7; + + if (firstPartLen == 0) + { + destination.Slice(destBitOffset / 8, numberOfBytes).Fill(0); + + if (endBits <= 0) + { + return; + } + + var endByteSpan = destination.Slice(numberOfBytes, 1); + + endByteSpan[0] = (byte) (endByteSpan[0] & ~(byte.MaxValue >> (8 - endBits))); + + return; + } + + + var lastPartLen = 8 - firstPartLen; + + destination[dstBytePtr] &= (byte)(255 >> lastPartLen); + + ++dstBytePtr; + + destination.Slice(dstBytePtr, numberOfBytes-2).Fill(0); + + dstBytePtr = numberOfBytes - 2; + + destination[dstBytePtr] &= (byte)(255 << firstPartLen); + } + + + /// /// /// Write several whole bytes /// - public static void WriteBytes(byte[] source, int sourceByteOffset, int numberOfBytes, byte[] destination, int destBitOffset) + public static void WriteBytes(ReadOnlySpan source, int sourceByteOffset, int numberOfBytes, Span destination, int destBitOffset) { int dstBytePtr = destBitOffset >> 3; - int firstPartLen = (destBitOffset % 8); + int firstPartLen = (destBitOffset & 7); if (firstPartLen == 0) { - Buffer.BlockCopy(source, sourceByteOffset, destination, dstBytePtr, numberOfBytes); + //Buffer.BlockCopy(source, sourceByteOffset, destination, dstBytePtr, numberOfBytes); + source.Slice(sourceByteOffset,numberOfBytes) + .CopyTo(destination.Slice(dstBytePtr, numberOfBytes)); return; } @@ -180,31 +262,57 @@ namespace Lidgren.Network destination[dstBytePtr] &= (byte)(255 << firstPartLen); // clear before writing destination[dstBytePtr] |= (byte)(src >> lastPartLen); // write second half } + } - return; + + /// + /// Write several whole bytes + /// + public static void WriteBytes(ReadOnlySpan source, Span destination, int destBitOffset) + { + int sourceByteOffset = 0; + int numberOfBytes = source.Length; + int dstBytePtr = destBitOffset >> 3; + int firstPartLen = destBitOffset & 7; + + if (firstPartLen == 0) + { + //Buffer.BlockCopy(source, sourceByteOffset, destination, dstBytePtr, numberOfBytes); + source + .CopyTo(destination.Slice(dstBytePtr, numberOfBytes)); + return; + } + + int lastPartLen = 8 - firstPartLen; + + for (int i = 0; i < numberOfBytes; i++) + { + byte src = source[sourceByteOffset + i]; + + // write last part of this byte + destination[dstBytePtr] &= (byte)(255 >> lastPartLen); // clear before writing + destination[dstBytePtr] |= (byte)(src << firstPartLen); // write first half + + dstBytePtr++; + + // write first part of next byte + destination[dstBytePtr] &= (byte)(255 << firstPartLen); // clear before writing + destination[dstBytePtr] |= (byte)(src >> lastPartLen); // write second half + } } /// /// Reads an unsigned 16 bit integer /// [CLSCompliant(false)] -#if UNSAFE - public static unsafe ushort ReadUInt16(byte[] fromBuffer, int numberOfBits, int readBitOffset) + public static ushort ReadUInt16(ReadOnlySpan fromBuffer, int numberOfBits, int readBitOffset) { Debug.Assert(((numberOfBits > 0) && (numberOfBits <= 16)), "ReadUInt16() can only read between 1 and 16 bits"); - if (numberOfBits == 16 && ((readBitOffset % 8) == 0)) + if (numberOfBits == 16 && ((readBitOffset & 7) == 0)) { - fixed (byte* ptr = &(fromBuffer[readBitOffset / 8])) - { - return *(((ushort*)ptr)); - } + return MemoryMarshal.Read(fromBuffer.Slice(readBitOffset / 8)); } -#else - public static ushort ReadUInt16(byte[] fromBuffer, int numberOfBits, int readBitOffset) - { - Debug.Assert(((numberOfBits > 0) && (numberOfBits <= 16)), "ReadUInt16() can only read between 1 and 16 bits"); -#endif ushort returnValue; if (numberOfBits <= 8) { @@ -220,38 +328,27 @@ namespace Lidgren.Network returnValue |= (ushort)(ReadByte(fromBuffer, numberOfBits, readBitOffset) << 8); } -#if BIGENDIAN - // reorder bytes - uint retVal = returnValue; - retVal = ((retVal & 0x0000ff00) >> 8) | ((retVal & 0x000000ff) << 8); - return (ushort)retVal; -#else + if (!BitConverter.IsLittleEndian) + { + return BinaryPrimitives.ReverseEndianness(returnValue); + } + return returnValue; -#endif } /// /// Reads the specified number of bits into an UInt32 /// [CLSCompliant(false)] -#if UNSAFE - public static unsafe uint ReadUInt32(byte[] fromBuffer, int numberOfBits, int readBitOffset) + public static uint ReadUInt32(ReadOnlySpan fromBuffer, int numberOfBits, int readBitOffset) { NetException.Assert(((numberOfBits > 0) && (numberOfBits <= 32)), "ReadUInt32() can only read between 1 and 32 bits"); - if (numberOfBits == 32 && ((readBitOffset % 8) == 0)) + if (numberOfBits == 32 && ((readBitOffset & 7) == 0)) { - fixed (byte* ptr = &(fromBuffer[readBitOffset / 8])) - { - return *(((uint*)ptr)); - } + return MemoryMarshal.Read(fromBuffer.Slice(readBitOffset / 8)); } -#else - - public static uint ReadUInt32(byte[] fromBuffer, int numberOfBits, int readBitOffset) - { - NetException.Assert(((numberOfBits > 0) && (numberOfBits <= 32)), "ReadUInt32() can only read between 1 and 32 bits"); -#endif + uint returnValue; if (numberOfBits <= 8) { @@ -284,16 +381,12 @@ namespace Lidgren.Network returnValue |= (uint)(ReadByte(fromBuffer, numberOfBits, readBitOffset) << 24); -#if BIGENDIAN - // reorder bytes - return - ((returnValue & 0xff000000) >> 24) | - ((returnValue & 0x00ff0000) >> 8) | - ((returnValue & 0x0000ff00) << 8) | - ((returnValue & 0x000000ff) << 24); -#else + if (!BitConverter.IsLittleEndian) + { + return BinaryPrimitives.ReverseEndianness(returnValue); + } + return returnValue; -#endif } //[CLSCompliant(false)] @@ -303,74 +396,76 @@ namespace Lidgren.Network /// Writes an unsigned 16 bit integer /// [CLSCompliant(false)] - public static void WriteUInt16(ushort source, int numberOfBits, byte[] destination, int destinationBitOffset) + public static void WriteUInt16(ushort source, int numberOfBits, Span destination, int destinationBitOffset) { if (numberOfBits == 0) - return; - - NetException.Assert((numberOfBits >= 0 && numberOfBits <= 16), "numberOfBits must be between 0 and 16"); -#if BIGENDIAN - // reorder bytes - uint intSource = source; - intSource = ((intSource & 0x0000ff00) >> 8) | ((intSource & 0x000000ff) << 8); - source = (ushort)intSource; -#endif - if (numberOfBits <= 8) { - NetBitWriter.WriteByte((byte)source, numberOfBits, destination, destinationBitOffset); return; } - NetBitWriter.WriteByte((byte)source, 8, destination, destinationBitOffset); + NetException.Assert((numberOfBits >= 0 && numberOfBits <= 16), "numberOfBits must be between 0 and 16"); + if (!BitConverter.IsLittleEndian) + { + source = BinaryPrimitives.ReverseEndianness(source); + } + + + if (numberOfBits <= 8) + { + WriteByte((byte)source, numberOfBits, destination, destinationBitOffset); + return; + } + + WriteByte((byte)source, 8, destination, destinationBitOffset); numberOfBits -= 8; - if (numberOfBits > 0) - NetBitWriter.WriteByte((byte)(source >> 8), numberOfBits, destination, destinationBitOffset + 8); + + WriteByte((byte)(source >> 8), numberOfBits, destination, destinationBitOffset + 8); } /// /// Writes the specified number of bits into a byte array /// [CLSCompliant(false)] - public static int WriteUInt32(uint source, int numberOfBits, byte[] destination, int destinationBitOffset) + public static int WriteUInt32(uint source, int numberOfBits, Span destination, int destinationBitOffset) { -#if BIGENDIAN - // reorder bytes - source = ((source & 0xff000000) >> 24) | - ((source & 0x00ff0000) >> 8) | - ((source & 0x0000ff00) << 8) | - ((source & 0x000000ff) << 24); -#endif + if (!BitConverter.IsLittleEndian) + { + source = BinaryPrimitives.ReverseEndianness(source); + } int returnValue = destinationBitOffset + numberOfBits; if (numberOfBits <= 8) { - NetBitWriter.WriteByte((byte)source, numberOfBits, destination, destinationBitOffset); + WriteByte((byte)source, numberOfBits, destination, destinationBitOffset); return returnValue; } - NetBitWriter.WriteByte((byte)source, 8, destination, destinationBitOffset); + + WriteByte((byte)source, 8, destination, destinationBitOffset); destinationBitOffset += 8; numberOfBits -= 8; if (numberOfBits <= 8) { - NetBitWriter.WriteByte((byte)(source >> 8), numberOfBits, destination, destinationBitOffset); + WriteByte((byte)(source >> 8), numberOfBits, destination, destinationBitOffset); return returnValue; } - NetBitWriter.WriteByte((byte)(source >> 8), 8, destination, destinationBitOffset); + + WriteByte((byte)(source >> 8), 8, destination, destinationBitOffset); destinationBitOffset += 8; numberOfBits -= 8; if (numberOfBits <= 8) { - NetBitWriter.WriteByte((byte)(source >> 16), numberOfBits, destination, destinationBitOffset); + WriteByte((byte)(source >> 16), numberOfBits, destination, destinationBitOffset); return returnValue; } - NetBitWriter.WriteByte((byte)(source >> 16), 8, destination, destinationBitOffset); + + WriteByte((byte)(source >> 16), 8, destination, destinationBitOffset); destinationBitOffset += 8; numberOfBits -= 8; - NetBitWriter.WriteByte((byte)(source >> 24), numberOfBits, destination, destinationBitOffset); + WriteByte((byte)(source >> 24), numberOfBits, destination, destinationBitOffset); return returnValue; } @@ -378,89 +473,90 @@ namespace Lidgren.Network /// Writes the specified number of bits into a byte array /// [CLSCompliant(false)] - public static int WriteUInt64(ulong source, int numberOfBits, byte[] destination, int destinationBitOffset) + public static int WriteUInt64(ulong source, int numberOfBits, Span destination, int destinationBitOffset) { -#if BIGENDIAN - source = ((source & 0xff00000000000000L) >> 56) | - ((source & 0x00ff000000000000L) >> 40) | - ((source & 0x0000ff0000000000L) >> 24) | - ((source & 0x000000ff00000000L) >> 8) | - ((source & 0x00000000ff000000L) << 8) | - ((source & 0x0000000000ff0000L) << 24) | - ((source & 0x000000000000ff00L) << 40) | - ((source & 0x00000000000000ffL) << 56); -#endif + if (!BitConverter.IsLittleEndian) + source = BinaryPrimitives.ReverseEndianness(source); + int returnValue = destinationBitOffset + numberOfBits; if (numberOfBits <= 8) { - NetBitWriter.WriteByte((byte)source, numberOfBits, destination, destinationBitOffset); + WriteByte((byte)source, numberOfBits, destination, destinationBitOffset); return returnValue; } - NetBitWriter.WriteByte((byte)source, 8, destination, destinationBitOffset); + + WriteByte((byte)source, 8, destination, destinationBitOffset); destinationBitOffset += 8; numberOfBits -= 8; if (numberOfBits <= 8) { - NetBitWriter.WriteByte((byte)(source >> 8), numberOfBits, destination, destinationBitOffset); + WriteByte((byte)(source >> 8), numberOfBits, destination, destinationBitOffset); return returnValue; } - NetBitWriter.WriteByte((byte)(source >> 8), 8, destination, destinationBitOffset); + + WriteByte((byte)(source >> 8), 8, destination, destinationBitOffset); destinationBitOffset += 8; numberOfBits -= 8; if (numberOfBits <= 8) { - NetBitWriter.WriteByte((byte)(source >> 16), numberOfBits, destination, destinationBitOffset); + WriteByte((byte)(source >> 16), numberOfBits, destination, destinationBitOffset); return returnValue; } - NetBitWriter.WriteByte((byte)(source >> 16), 8, destination, destinationBitOffset); + + WriteByte((byte)(source >> 16), 8, destination, destinationBitOffset); destinationBitOffset += 8; numberOfBits -= 8; if (numberOfBits <= 8) { - NetBitWriter.WriteByte((byte)(source >> 24), numberOfBits, destination, destinationBitOffset); + WriteByte((byte)(source >> 24), numberOfBits, destination, destinationBitOffset); return returnValue; } - NetBitWriter.WriteByte((byte)(source >> 24), 8, destination, destinationBitOffset); + + WriteByte((byte)(source >> 24), 8, destination, destinationBitOffset); destinationBitOffset += 8; numberOfBits -= 8; if (numberOfBits <= 8) { - NetBitWriter.WriteByte((byte)(source >> 32), numberOfBits, destination, destinationBitOffset); + WriteByte((byte)(source >> 32), numberOfBits, destination, destinationBitOffset); return returnValue; } - NetBitWriter.WriteByte((byte)(source >> 32), 8, destination, destinationBitOffset); + + WriteByte((byte)(source >> 32), 8, destination, destinationBitOffset); destinationBitOffset += 8; numberOfBits -= 8; if (numberOfBits <= 8) { - NetBitWriter.WriteByte((byte)(source >> 40), numberOfBits, destination, destinationBitOffset); + WriteByte((byte)(source >> 40), numberOfBits, destination, destinationBitOffset); return returnValue; } - NetBitWriter.WriteByte((byte)(source >> 40), 8, destination, destinationBitOffset); + + WriteByte((byte)(source >> 40), 8, destination, destinationBitOffset); destinationBitOffset += 8; numberOfBits -= 8; if (numberOfBits <= 8) { - NetBitWriter.WriteByte((byte)(source >> 48), numberOfBits, destination, destinationBitOffset); + WriteByte((byte)(source >> 48), numberOfBits, destination, destinationBitOffset); return returnValue; } - NetBitWriter.WriteByte((byte)(source >> 48), 8, destination, destinationBitOffset); + + WriteByte((byte)(source >> 48), 8, destination, destinationBitOffset); destinationBitOffset += 8; numberOfBits -= 8; if (numberOfBits <= 8) { - NetBitWriter.WriteByte((byte)(source >> 56), numberOfBits, destination, destinationBitOffset); + WriteByte((byte)(source >> 56), numberOfBits, destination, destinationBitOffset); return returnValue; } - NetBitWriter.WriteByte((byte)(source >> 56), 8, destination, destinationBitOffset); + + WriteByte((byte)(source >> 56), 8, destination, destinationBitOffset); destinationBitOffset += 8; numberOfBits -= 8; @@ -476,10 +572,10 @@ namespace Lidgren.Network /// /// number of bytes written [CLSCompliant(false)] - public static int WriteVariableUInt32(byte[] intoBuffer, int offset, uint value) + public static int WriteVariableUInt32(Span intoBuffer, int offset, uint value) { int retval = 0; - uint num1 = (uint)value; + uint num1 = value; while (num1 >= 0x80) { intoBuffer[offset + retval] = (byte)(num1 | 0x80); @@ -494,7 +590,7 @@ namespace Lidgren.Network /// Reads a UInt32 written using WriteUnsignedVarInt(); will increment offset! /// [CLSCompliant(false)] - public static uint ReadVariableUInt32(byte[] buffer, ref int offset) + public static uint ReadVariableUInt32(Span buffer, ref int offset) { int num1 = 0; int num2 = 0; diff --git a/Lidgren.Network/NetBuffer.Peek.cs b/Lidgren.Network/NetBuffer.Peek.cs index 818f94fc1..f5764b7ee 100644 --- a/Lidgren.Network/NetBuffer.Peek.cs +++ b/Lidgren.Network/NetBuffer.Peek.cs @@ -28,7 +28,7 @@ namespace Lidgren.Network /// /// Gets the internal data buffer /// - public byte[] PeekDataBuffer() { return m_data; } + public Span PeekDataBuffer() { return m_data; } // // 1 bit @@ -79,25 +79,25 @@ namespace Lidgren.Network /// /// Reads the specified number of bytes without advancing the read pointer /// - public byte[] PeekBytes(int numberOfBytes) + public Span PeekBytes(Span bytes) { + var numberOfBytes = bytes.Length; NetException.Assert(m_bitLength - m_readPosition >= (numberOfBytes * 8), c_readOverflowError); - byte[] retval = new byte[numberOfBytes]; - NetBitWriter.ReadBytes(m_data, numberOfBytes, m_readPosition, retval, 0); - return retval; + NetBitWriter.ReadBytes(m_data, numberOfBytes, m_readPosition, bytes, 0); + return bytes; } /// /// Reads the specified number of bytes without advancing the read pointer /// - public void PeekBytes(byte[] into, int offset, int numberOfBytes) + public Span PeekBytes(Span into, int offset, int numberOfBytes) { NetException.Assert(m_bitLength - m_readPosition >= (numberOfBytes * 8), c_readOverflowError); NetException.Assert(offset + numberOfBytes <= into.Length); NetBitWriter.ReadBytes(m_data, numberOfBytes, m_readPosition, into, offset); - return; + return into; } // @@ -271,12 +271,12 @@ namespace Lidgren.Network if ((m_readPosition & 7) == 0) // read directly { - float retval = BitConverter.ToSingle(m_data, m_readPosition >> 3); + float retval = BitConverter.ToSingle(m_data.Slice( m_readPosition >> 3)); return retval; } - byte[] bytes = PeekBytes(4); - return BitConverter.ToSingle(bytes, 0); + var bytes = PeekBytes(stackalloc byte[4]); + return BitConverter.ToSingle(bytes); } /// @@ -289,12 +289,12 @@ namespace Lidgren.Network if ((m_readPosition & 7) == 0) // read directly { // read directly - double retval = BitConverter.ToDouble(m_data, m_readPosition >> 3); + double retval = BitConverter.ToDouble(m_data.Slice( m_readPosition >> 3)); return retval; } - byte[] bytes = PeekBytes(8); - return BitConverter.ToDouble(bytes, 0); + var bytes = PeekBytes(stackalloc byte[8]); + return BitConverter.ToDouble(bytes); } /// diff --git a/Lidgren.Network/NetBuffer.Read.cs b/Lidgren.Network/NetBuffer.Read.cs index 1b9f3e034..447c277f5 100644 --- a/Lidgren.Network/NetBuffer.Read.cs +++ b/Lidgren.Network/NetBuffer.Read.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Text; using System.Reflection; using System.Net; @@ -17,18 +18,16 @@ namespace Lidgren.Network public partial class NetBuffer { private const string c_readOverflowError = "Trying to read past the buffer size - likely caused by mismatching Write/Reads, different size or order."; - private const int c_bufferSize = 64; // Min 8 to hold anything but strings. Increase it if readed strings usally don't fit inside the buffer - private static object s_buffer; + private const string c_writeOverflowError = "Trying to write past the end of a constrained buffer - likely caused by mismatching Write/Reads, different size or order."; /// /// Reads a boolean value (stored as a single bit) written using Write(bool) /// public bool ReadBoolean() { - NetException.Assert(m_bitLength - m_readPosition >= 1, c_readOverflowError); - byte retval = NetBitWriter.ReadByte(m_data, 1, m_readPosition); + var retval = PeekBoolean(); m_readPosition += 1; - return (retval > 0 ? true : false); + return retval; } /// @@ -36,8 +35,7 @@ namespace Lidgren.Network /// public byte ReadByte() { - NetException.Assert(m_bitLength - m_readPosition >= 8, c_readOverflowError); - byte retval = NetBitWriter.ReadByte(m_data, 8, m_readPosition); + var retval = PeekByte(); m_readPosition += 8; return retval; } @@ -52,7 +50,8 @@ namespace Lidgren.Network result = 0; return false; } - result = NetBitWriter.ReadByte(m_data, 8, m_readPosition); + + result = PeekByte(); m_readPosition += 8; return true; } @@ -63,10 +62,9 @@ namespace Lidgren.Network [CLSCompliant(false)] public sbyte ReadSByte() { - NetException.Assert(m_bitLength - m_readPosition >= 8, c_readOverflowError); - byte retval = NetBitWriter.ReadByte(m_data, 8, m_readPosition); + sbyte retval = PeekSByte(); m_readPosition += 8; - return (sbyte)retval; + return retval; } /// @@ -74,65 +72,76 @@ namespace Lidgren.Network /// public byte ReadByte(int numberOfBits) { - NetException.Assert(numberOfBits > 0 && numberOfBits <= 8, "ReadByte(bits) can only read between 1 and 8 bits"); - byte retval = NetBitWriter.ReadByte(m_data, numberOfBits, m_readPosition); + byte retval = PeekByte(numberOfBits); m_readPosition += numberOfBits; return retval; } /// - /// Reads the specified number of bytes + /// Creates a stream out of a constrained region of the buffer for reading. /// - public byte[] ReadBytes(int numberOfBytes) + /// The length of the constrained region in bytes. + /// A readable constrained buffer region wrapped by a stream. + public Stream ReadAsStream(int byteLength) { - NetException.Assert(m_bitLength - m_readPosition + 7 >= (numberOfBytes * 8), c_readOverflowError); - - byte[] retval = new byte[numberOfBytes]; - NetBitWriter.ReadBytes(m_data, numberOfBytes, m_readPosition, retval, 0); - m_readPosition += (8 * numberOfBytes); - return retval; + var bitLength = byteLength*8; + var stream = new ReadOnlyWrapperStream(this, m_readPosition, bitLength); + m_readPosition += bitLength; + return stream; } - + /// - /// Reads the specified number of bytes and returns true for success - /// - public bool ReadBytes(int numberOfBytes, out byte[] result) - { - if (m_bitLength - m_readPosition + 7 < (numberOfBytes * 8)) - { - result = null; - return false; - } - - result = new byte[numberOfBytes]; - NetBitWriter.ReadBytes(m_data, numberOfBytes, m_readPosition, result, 0); - m_readPosition += (8 * numberOfBytes); - return true; - } - - /// - /// Reads the specified number of bytes into a preallocated array + /// Prefer using instead. + /// Reads the specified number of bytes into a allocated array. /// /// The destination array /// The offset where to start writing in the destination array /// The number of bytes to read - public void ReadBytes(byte[] into, int offset, int numberOfBytes) + /// The same as + //[Obsolete("Use the Span parameter version of ReadBytes instead")] + public byte[] ReadBytes(int length) { - NetException.Assert(m_bitLength - m_readPosition + 7 >= (numberOfBytes * 8), c_readOverflowError); - NetException.Assert(offset + numberOfBytes <= into.Length); - - NetBitWriter.ReadBytes(m_data, numberOfBytes, m_readPosition, into, offset); - m_readPosition += (8 * numberOfBytes); - return; + var into = new byte[length]; + PeekBytes(into); + m_readPosition += 8 * into.Length; + return into; } /// - /// Reads the specified number of bits into a preallocated array + /// Reads the specified number of bytes into a pre-allocated array. + /// + /// The destination + /// The same as + public Span ReadBytes(Span into) + { + var bytes = PeekBytes(into); + m_readPosition += 8 * into.Length; + return bytes; + } + + /// + /// Reads the specified number of bytes into a pre-allocated array. + /// + /// The destination array + /// The offset where to start writing in the destination array + /// The number of bytes to read + /// The same as + //[Obsolete("Use Span alternative instead with slicing.")] + public Span ReadBytes(Span into, int offset, int numberOfBytes) + { + var bytes = PeekBytes(into, offset, numberOfBytes); + m_readPosition += 8 * numberOfBytes; + return bytes; + } + + /// + /// Reads the specified number of bits into a pre-allocated array. /// /// The destination array /// The offset where to start writing in the destination array /// The number of bits to read - public void ReadBits(byte[] into, int offset, int numberOfBits) + //[Obsolete("Use Span alternative instead with slicing.")] + public Span ReadBits(Span into, int offset, int numberOfBits) { NetException.Assert(m_bitLength - m_readPosition >= numberOfBits, c_readOverflowError); NetException.Assert(offset + NetUtility.BytesToHoldBits(numberOfBits) <= into.Length); @@ -146,7 +155,7 @@ namespace Lidgren.Network if (extraBits > 0) into[offset + numberOfWholeBytes] = ReadByte(extraBits); - return; + return into.Slice(offset,(numberOfBits+extraBits) >> 3); } /// @@ -154,10 +163,9 @@ namespace Lidgren.Network /// public Int16 ReadInt16() { - NetException.Assert(m_bitLength - m_readPosition >= 16, c_readOverflowError); - uint retval = NetBitWriter.ReadUInt16(m_data, 16, m_readPosition); + var retval = PeekInt16(); m_readPosition += 16; - return (short)retval; + return retval; } /// @@ -166,8 +174,7 @@ namespace Lidgren.Network [CLSCompliant(false)] public UInt16 ReadUInt16() { - NetException.Assert(m_bitLength - m_readPosition >= 16, c_readOverflowError); - uint retval = NetBitWriter.ReadUInt16(m_data, 16, m_readPosition); + var retval = PeekUInt16(); m_readPosition += 16; return (ushort)retval; } @@ -177,8 +184,7 @@ namespace Lidgren.Network /// public Int32 ReadInt32() { - NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError); - uint retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + var retval = PeekInt32(); m_readPosition += 32; return (Int32)retval; } @@ -195,7 +201,7 @@ namespace Lidgren.Network return false; } - result = (Int32)NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + result = PeekInt32(); m_readPosition += 32; return true; } @@ -205,26 +211,9 @@ namespace Lidgren.Network /// public Int32 ReadInt32(int numberOfBits) { - NetException.Assert(numberOfBits > 0 && numberOfBits <= 32, "ReadInt32(bits) can only read between 1 and 32 bits"); - NetException.Assert(m_bitLength - m_readPosition >= numberOfBits, c_readOverflowError); - - uint retval = NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition); + var retval = PeekInt32(numberOfBits); m_readPosition += numberOfBits; - - if (numberOfBits == 32) - return (int)retval; - - int signBit = 1 << (numberOfBits - 1); - if ((retval & signBit) == 0) - return (int)retval; // positive - - // negative - unchecked - { - uint mask = ((uint)-1) >> (33 - numberOfBits); - uint tmp = (retval & mask) + 1; - return -((int)tmp); - } + return retval; } /// @@ -233,8 +222,7 @@ namespace Lidgren.Network [CLSCompliant(false)] public UInt32 ReadUInt32() { - NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError); - uint retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + var retval = PeekUInt32(); m_readPosition += 32; return retval; } @@ -250,7 +238,7 @@ namespace Lidgren.Network result = 0; return false; } - result = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + result = PeekUInt32(); m_readPosition += 32; return true; } @@ -261,10 +249,7 @@ namespace Lidgren.Network [CLSCompliant(false)] public UInt32 ReadUInt32(int numberOfBits) { - NetException.Assert(numberOfBits > 0 && numberOfBits <= 32, "ReadUInt32(bits) can only read between 1 and 32 bits"); - //NetException.Assert(m_bitLength - m_readBitPtr >= numberOfBits, "tried to read past buffer size"); - - UInt32 retval = NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition); + var retval = PeekUInt32(numberOfBits); m_readPosition += numberOfBits; return retval; } @@ -275,15 +260,8 @@ namespace Lidgren.Network [CLSCompliant(false)] public UInt64 ReadUInt64() { - NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError); - - ulong low = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); - m_readPosition += 32; - ulong high = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); - - ulong retval = low + (high << 32); - - m_readPosition += 32; + var retval = PeekUInt64(); + m_readPosition += 64; return retval; } @@ -307,19 +285,7 @@ namespace Lidgren.Network [CLSCompliant(false)] public UInt64 ReadUInt64(int numberOfBits) { - NetException.Assert(numberOfBits > 0 && numberOfBits <= 64, "ReadUInt64(bits) can only read between 1 and 64 bits"); - NetException.Assert(m_bitLength - m_readPosition >= numberOfBits, c_readOverflowError); - - ulong retval; - if (numberOfBits <= 32) - { - retval = (ulong)NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition); - } - else - { - retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); - retval |= (UInt64)NetBitWriter.ReadUInt32(m_data, numberOfBits - 32, m_readPosition + 32) << 32; - } + var retval = PeekUInt64(numberOfBits); m_readPosition += numberOfBits; return retval; } @@ -348,18 +314,9 @@ namespace Lidgren.Network { NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError); - if ((m_readPosition & 7) == 0) // read directly - { - float retval = BitConverter.ToSingle(m_data, m_readPosition >> 3); - m_readPosition += 32; - return retval; - } - - byte[] bytes = (byte[]) Interlocked.Exchange(ref s_buffer, null) ?? new byte[c_bufferSize]; - ReadBytes(bytes, 0, 4); - float res = BitConverter.ToSingle(bytes, 0); - s_buffer = bytes; - return res; + var retval = PeekSingle(); + m_readPosition += 32; + return retval; } /// @@ -373,17 +330,7 @@ namespace Lidgren.Network return false; } - if ((m_readPosition & 7) == 0) // read directly - { - result = BitConverter.ToSingle(m_data, m_readPosition >> 3); - m_readPosition += 32; - return true; - } - - byte[] bytes = (byte[]) Interlocked.Exchange(ref s_buffer, null) ?? new byte[c_bufferSize]; - ReadBytes(bytes, 0, 4); - result = BitConverter.ToSingle(bytes, 0); - s_buffer = bytes; + result = ReadSingle(); return true; } @@ -394,18 +341,8 @@ namespace Lidgren.Network { NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError); - if ((m_readPosition & 7) == 0) // read directly - { - // read directly - double retval = BitConverter.ToDouble(m_data, m_readPosition >> 3); - m_readPosition += 64; - return retval; - } - - byte[] bytes = (byte[]) Interlocked.Exchange(ref s_buffer, null) ?? new byte[c_bufferSize]; - ReadBytes(bytes, 0, 8); - double res = BitConverter.ToDouble(bytes, 0); - s_buffer = bytes; + var res = PeekDouble(); + m_readPosition += 64; return res; } @@ -599,21 +536,13 @@ namespace Lidgren.Network if ((m_readPosition & 7) == 0) { // read directly - string retval = System.Text.Encoding.UTF8.GetString(m_data, m_readPosition >> 3, byteLen); + string retval = Encoding.UTF8.GetString(m_data.Slice( m_readPosition >> 3, byteLen)); m_readPosition += (8 * byteLen); return retval; } - if (byteLen <= c_bufferSize) { - byte[] buffer = (byte[]) Interlocked.Exchange(ref s_buffer, null) ?? new byte[c_bufferSize]; - ReadBytes(buffer, 0, byteLen); - string retval = Encoding.UTF8.GetString(buffer, 0, byteLen); - s_buffer = buffer; - return retval; - } else { - byte[] bytes = ReadBytes(byteLen); - return Encoding.UTF8.GetString(bytes, 0, bytes.Length); - } + var bytes = ReadBytes(stackalloc byte[byteLen]); + return Encoding.UTF8.GetString(bytes); } /// @@ -643,19 +572,13 @@ namespace Lidgren.Network if ((m_readPosition & 7) == 0) { // read directly - result = System.Text.Encoding.UTF8.GetString(m_data, m_readPosition >> 3, (int)byteLen); + result = Encoding.UTF8.GetString(m_data.Slice(m_readPosition >> 3, (int)byteLen)); m_readPosition += (8 * (int)byteLen); return true; } - byte[] bytes; - if (ReadBytes((int)byteLen, out bytes) == false) - { - result = String.Empty; - return false; - } - - result = System.Text.Encoding.UTF8.GetString(bytes, 0, bytes.Length); + var bytes = ReadBytes(stackalloc byte[(int)byteLen]); + result = Encoding.UTF8.GetString(bytes); return true; } @@ -679,7 +602,7 @@ namespace Lidgren.Network public NetEndPoint ReadIPEndPoint() { byte len = ReadByte(); - byte[] addressBytes = ReadBytes(len); + var addressBytes = ReadBytes(stackalloc byte[len]); int port = (int)ReadUInt16(); var address = NetUtility.CreateAddressFromBytes(addressBytes); diff --git a/Lidgren.Network/NetBuffer.Write.cs b/Lidgren.Network/NetBuffer.Write.cs index 9673594e5..5bad1a458 100644 --- a/Lidgren.Network/NetBuffer.Write.cs +++ b/Lidgren.Network/NetBuffer.Write.cs @@ -19,7 +19,10 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR TH USE OR OTHER DEALINGS IN THE SOFTWARE. */ using System; +using System.Buffers; +using System.Buffers.Binary; using System.Collections.Generic; +using System.IO; using System.Net; using System.Reflection; using System.Text; @@ -55,14 +58,20 @@ namespace Lidgren.Network public void EnsureBufferSize(int numberOfBits) { int byteLen = ((numberOfBits + 7) >> 3); - if (m_data == null) + if (m_buf == null) { - m_data = new byte[byteLen + c_overAllocateAmount]; + m_buf = ArrayPool.Shared.Rent(byteLen + c_overAllocateAmount); return; } - if (m_data.Length < byteLen) - Array.Resize(ref m_data, byteLen + c_overAllocateAmount); - return; + + if (m_buf.Length < byteLen) + { + var pool = ArrayPool.Shared; + var oldBuf = m_buf; + m_buf = pool.Rent(byteLen + c_overAllocateAmount); + new Span(oldBuf).CopyTo(m_buf); + pool.Return(oldBuf); + } } /// @@ -71,14 +80,20 @@ namespace Lidgren.Network internal void InternalEnsureBufferSize(int numberOfBits) { int byteLen = ((numberOfBits + 7) >> 3); - if (m_data == null) + if (m_buf == null) { - m_data = new byte[byteLen]; + m_buf = ArrayPool.Shared.Rent(byteLen); return; } - if (m_data.Length < byteLen) - Array.Resize(ref m_data, byteLen); - return; + + if (m_buf.Length < byteLen) + { + var pool = ArrayPool.Shared; + var oldBuf = m_buf; + m_buf = pool.Rent(byteLen); + new Span(oldBuf).CopyTo(m_buf); + pool.Return(oldBuf); + } } /// @@ -134,12 +149,49 @@ namespace Lidgren.Network } /// - /// Writes all bytes in an array + /// Writes a number of zeroed bytes /// - public void Write(byte[] source) + public void Zero(int length) + { + if (length < 0) + throw new ArgumentOutOfRangeException(nameof(length), length, "Must be positive."); + int bits = length * 8; + EnsureBufferSize(m_bitLength + bits); + NetBitWriter.Zero(bits, m_data, m_bitLength); + m_bitLength += bits; + } + + + /// + /// Creates a stream out of a constrained region of the buffer for writing. + /// + /// The length of the constrained region in bytes. + /// A writable constrained buffer region wrapped by a stream. + public Stream WriteAsStream(int byteLength) + { + var bitLength = byteLength*8; + EnsureBufferSize(m_bitLength + byteLength); + var stream = new WriteOnlyWrapperStream(this, m_bitLength, bitLength); + m_bitLength += bitLength; + return stream; + } + + /// + /// Creates a stream for appending to the end of the buffer. + /// + /// A writable stream that appends to the buffer. + public Stream AppendViaStream() + { + return new AppenderStream(this); + } + + /// + /// Writes all bytes in an array. + /// + public void Write(ReadOnlySpan source) { if (source == null) - throw new ArgumentNullException("source"); + throw new ArgumentNullException(nameof(source)); int bits = source.Length * 8; EnsureBufferSize(m_bitLength + bits); NetBitWriter.WriteBytes(source, 0, source.Length, m_data, m_bitLength); @@ -149,10 +201,10 @@ namespace Lidgren.Network /// /// Writes the specified number of bytes from an array /// - public void Write(byte[] source, int offsetInBytes, int numberOfBytes) + public void Write(Span source, int offsetInBytes, int numberOfBytes) { if (source == null) - throw new ArgumentNullException("source"); + throw new ArgumentNullException(nameof(source)); int bits = numberOfBytes * 8; EnsureBufferSize(m_bitLength + bits); NetBitWriter.WriteBytes(source, offsetInBytes, numberOfBytes, m_data, m_bitLength); @@ -216,39 +268,24 @@ namespace Lidgren.Network m_bitLength = newBitLength; } -#if UNSAFE - /// - /// Writes a 32 bit signed integer - /// - public unsafe void Write(Int32 source) - { - EnsureBufferSize(m_bitLength + 32); - - // can write fast? - if (m_bitLength % 8 == 0) - { - fixed (byte* numRef = &Data[m_bitLength / 8]) - { - *((int*)numRef) = source; - } - } - else - { - NetBitWriter.WriteUInt32((UInt32)source, 32, Data, m_bitLength); - } - m_bitLength += 32; - } -#else /// /// Writes a 32 bit signed integer /// public void Write(Int32 source) { EnsureBufferSize(m_bitLength + 32); - NetBitWriter.WriteUInt32((UInt32)source, 32, m_data, m_bitLength); + + // can write fast? + if ((m_bitLength & 7) == 0) + { + MemoryMarshal.Write(new Span(Buffer, m_bitLength / 8, 4), ref source); + } + else + { + NetBitWriter.WriteUInt32((UInt32)source, 32, Buffer, m_bitLength); + } m_bitLength += 32; } -#endif /// /// Writes a 32 bit signed integer at a given offset in the buffer @@ -261,41 +298,25 @@ namespace Lidgren.Network m_bitLength = newBitLength; } -#if UNSAFE /// /// Writes a 32 bit unsigned integer /// - public unsafe void Write(UInt32 source) + public void Write(UInt32 source) { EnsureBufferSize(m_bitLength + 32); // can write fast? - if (m_bitLength % 8 == 0) + if ((m_bitLength & 7) == 0) { - fixed (byte* numRef = &Data[m_bitLength / 8]) - { - *((uint*)numRef) = source; - } + MemoryMarshal.Write(new Span(Buffer, m_bitLength / 8, 4), ref source); } else { - NetBitWriter.WriteUInt32(source, 32, Data, m_bitLength); + NetBitWriter.WriteUInt32(source, 32, Buffer, m_bitLength); } m_bitLength += 32; } -#else - /// - /// Writes a 32 bit unsigned integer - /// - [CLSCompliant(false)] - public void Write(UInt32 source) - { - EnsureBufferSize(m_bitLength + 32); - NetBitWriter.WriteUInt32(source, 32, m_data, m_bitLength); - m_bitLength += 32; - } -#endif /// /// Writes a 32 bit unsigned integer at a given offset in the buffer @@ -403,79 +424,33 @@ namespace Lidgren.Network // // Floating point // -#if UNSAFE - /// - /// Writes a 32 bit floating point value - /// - public unsafe void Write(float source) - { - uint val = *((uint*)&source); -#if BIGENDIAN - val = NetUtility.SwapByteOrder(val); -#endif - Write(val); - } -#else /// /// Writes a 32 bit floating point value /// public void Write(float source) { - // Use union to avoid BitConverter.GetBytes() which allocates memory on the heap - SingleUIntUnion su; - su.UIntValue = 0; // must initialize every member of the union to avoid warning - su.SingleValue = source; - -#if BIGENDIAN - // swap byte order - su.UIntValue = NetUtility.SwapByteOrder(su.UIntValue); -#endif - Write(su.UIntValue); - } -#endif - -#if UNSAFE - /// - /// Writes a 64 bit floating point value - /// - public unsafe void Write(double source) - { - ulong val = *((ulong*)&source); -#if BIGENDIAN - val = NetUtility.SwapByteOrder(val); -#endif + uint val = MemoryMarshal.Cast(MemoryMarshal.CreateReadOnlySpan(ref source, 1))[0]; + if (!BitConverter.IsLittleEndian) + { + val = BinaryPrimitives.ReverseEndianness(val); + } Write(val); } -#else + /// /// Writes a 64 bit floating point value /// public void Write(double source) { - byte[] val = BitConverter.GetBytes(source); -#if BIGENDIAN - // 0 1 2 3 4 5 6 7 + ulong val = MemoryMarshal.Cast(MemoryMarshal.CreateReadOnlySpan(ref source, 1))[0]; - // swap byte order - byte tmp = val[7]; - val[7] = val[0]; - val[0] = tmp; + if (!BitConverter.IsLittleEndian) + { + val = BinaryPrimitives.ReverseEndianness(val); + } - tmp = val[6]; - val[6] = val[1]; - val[1] = tmp; - - tmp = val[5]; - val[5] = val[2]; - val[2] = tmp; - - tmp = val[4]; - val[4] = val[3]; - val[3] = tmp; -#endif Write(val); } -#endif // // Variable bits @@ -624,7 +599,9 @@ namespace Lidgren.Network return; } - byte[] bytes = Encoding.UTF8.GetBytes(source); + // ReSharper disable once SuggestVarOrType_Elsewhere + Span bytes = stackalloc byte[Encoding.UTF8.GetByteCount(source)]; + Encoding.UTF8.GetBytes(source, bytes); EnsureBufferSize(m_bitLength + 8 + (bytes.Length * 8)); WriteVariableUInt32((uint)bytes.Length); Write(bytes); @@ -692,7 +669,7 @@ namespace Lidgren.Network Write(buffer.m_data, 0, buffer.LengthBytes); // did we write excessive bits? - int bitsInLastByte = (buffer.m_bitLength % 8); + int bitsInLastByte = (buffer.m_bitLength & 7); if (bitsInLastByte != 0) { int excessBits = 8 - bitsInLastByte; diff --git a/Lidgren.Network/NetBuffer.cs b/Lidgren.Network/NetBuffer.cs index 34c783fde..e52cfdd94 100644 --- a/Lidgren.Network/NetBuffer.cs +++ b/Lidgren.Network/NetBuffer.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Reflection; @@ -14,17 +15,17 @@ namespace Lidgren.Network private static readonly Dictionary s_readMethods; private static readonly Dictionary s_writeMethods; - internal byte[] m_data; + internal byte[] m_buf; + internal Span m_data => m_buf; internal int m_bitLength; internal int m_readPosition; /// /// Gets or sets the internal data buffer /// - public byte[] Data + public byte[] Buffer { - get { return m_data; } - set { m_data = value; } + get { return m_buf; } } /// @@ -94,5 +95,6 @@ namespace Lidgren.Network } } } + } } diff --git a/Lidgren.Network/NetConnection.Handshake.cs b/Lidgren.Network/NetConnection.Handshake.cs index c84605741..32906eda2 100644 --- a/Lidgren.Network/NetConnection.Handshake.cs +++ b/Lidgren.Network/NetConnection.Handshake.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.Collections.Generic; using System.Text; using System.Threading; @@ -200,12 +201,12 @@ namespace Lidgren.Network { if (m_localHailMessage != null) { - byte[] hi = m_localHailMessage.Data; + var hi = m_localHailMessage.Buffer; if (hi != null && hi.Length >= m_localHailMessage.LengthBytes) { if (om.LengthBytes + m_localHailMessage.LengthBytes > m_peerConfiguration.m_maximumTransmissionUnit - 10) m_peer.ThrowOrLog("Hail message too large; can maximally be " + (m_peerConfiguration.m_maximumTransmissionUnit - 10 - om.LengthBytes)); - om.Write(m_localHailMessage.Data, 0, m_localHailMessage.LengthBytes); + om.Write(m_localHailMessage.Buffer, 0, m_localHailMessage.LengthBytes); } } } @@ -283,20 +284,19 @@ namespace Lidgren.Network { m_peer.VerifyNetworkThread(); - byte[] hail; switch (tp) { case NetMessageType.Connect: if (m_status == NetConnectionStatus.ReceivedInitiation) { // Whee! Server full has already been checked - bool ok = ValidateHandshakeData(ptr, payloadLength, out hail); + bool ok = ValidateHandshakeData(ptr, payloadLength, out var hail); if (ok) { if (hail != null) { m_remoteHailMessage = m_peer.CreateIncomingMessage(NetIncomingMessageType.Data, hail); - m_remoteHailMessage.LengthBits = (hail.Length * 8); + m_remoteHailMessage.LengthBits = (hail.Count * 8); } else { @@ -402,18 +402,17 @@ namespace Lidgren.Network private void HandleConnectResponse(double now, NetMessageType tp, int ptr, int payloadLength) { - byte[] hail; switch (m_status) { case NetConnectionStatus.InitiatedConnect: // awesome - bool ok = ValidateHandshakeData(ptr, payloadLength, out hail); + bool ok = ValidateHandshakeData(ptr, payloadLength, out var hail); if (ok) { if (hail != null) { m_remoteHailMessage = m_peer.CreateIncomingMessage(NetIncomingMessageType.Data, hail); - m_remoteHailMessage.LengthBits = (hail.Length * 8); + m_remoteHailMessage.LengthBits = (hail.Count * 8); } else { @@ -441,7 +440,7 @@ namespace Lidgren.Network } } - private bool ValidateHandshakeData(int ptr, int payloadLength, out byte[] hail) + private bool ValidateHandshakeData(int ptr, int payloadLength, out ArraySegment hail) { hail = null; @@ -455,7 +454,7 @@ namespace Lidgren.Network int remainingBytes = payloadLength - (msg.PositionInBytes - ptr); if (remainingBytes > 0) - hail = msg.ReadBytes(remainingBytes); + msg.ReadBytes(hail = new ArraySegment(ArrayPool.Shared.Rent(remainingBytes),0, remainingBytes)); if (remoteAppIdentifier != m_peer.m_configuration.AppIdentifier) { diff --git a/Lidgren.Network/NetConnection.MTU.cs b/Lidgren.Network/NetConnection.MTU.cs index d5f64da04..a30ebec96 100644 --- a/Lidgren.Network/NetConnection.MTU.cs +++ b/Lidgren.Network/NetConnection.MTU.cs @@ -104,8 +104,7 @@ namespace Lidgren.Network private void SendExpandMTU(double now, int size) { NetOutgoingMessage om = m_peer.CreateMessage(size); - byte[] tmp = new byte[size]; - om.Write(tmp); + om.Zero(size); om.m_messageType = NetMessageType.ExpandMTURequest; int len = om.Encode(m_peer.m_sendBuffer, 0, 0); diff --git a/Lidgren.Network/NetConnectionStatistics.cs b/Lidgren.Network/NetConnectionStatistics.cs index 7f3145464..4ee84e448 100644 --- a/Lidgren.Network/NetConnectionStatistics.cs +++ b/Lidgren.Network/NetConnectionStatistics.cs @@ -76,45 +76,113 @@ namespace Lidgren.Network /// /// Gets the number of sent packets for this connection /// - public long SentPackets { get { return m_sentPackets; } } + public long SentPackets => m_sentPackets; /// /// Gets the number of received packets for this connection /// - public long ReceivedPackets { get { return m_receivedPackets; } } + public long ReceivedPackets => m_receivedPackets; /// /// Gets the number of sent bytes for this connection /// - public long SentBytes { get { return m_sentBytes; } } + public long SentBytes => m_sentBytes; /// /// Gets the number of received bytes for this connection /// - public long ReceivedBytes { get { return m_receivedBytes; } } + public long ReceivedBytes => m_receivedBytes; - /// + /// /// Gets the number of sent messages for this connection /// - public long SentMessages { get { return m_sentMessages; } } + public long SentMessages => m_sentMessages; - /// + /// /// Gets the number of received messages for this connection /// - public long ReceivedMessages { get { return m_receivedMessages; } } + public long ReceivedMessages => m_receivedMessages; /// /// Gets the number of resent reliable messages for this connection /// - public long ResentMessages { get { return m_resentMessagesDueToHole + m_resentMessagesDueToDelay; } } + public long ResentMessages => m_resentMessagesDueToHole + m_resentMessagesDueToDelay; - /// + /// + /// Gets the number of resent reliable messages for this connection due to holes + /// + public long ResentMessagesDueToHoles => m_resentMessagesDueToHole; + + /// + /// Gets the number of resent reliable messages for this connection due to delays + /// + public long ResentMessagesDueToDelays => m_resentMessagesDueToDelay; + + /// /// Gets the number of dropped messages for this connection /// - public long DroppedMessages { get { return m_droppedMessages; } } + public long DroppedMessages => m_droppedMessages; + + /// + /// Gets the number of dropped messages for this connection + /// + public long ReceivedFragments => m_receivedFragments; + // public double LastSendRespondedTo { get { return m_connection.m_lastSendRespondedTo; } } + /// + /// Gets the number of withheld messages for this connection + /// + private int WithheldMessages + { + get + { + int numWithheld = 0; + foreach (NetReceiverChannelBase recChan in m_connection.m_receiveChannels) + { + var relRecChan = recChan as NetReliableOrderedReceiver; + if (relRecChan == null) + { + continue; + } + + for (int i = 0; i < relRecChan.m_withheldMessages.Length; i++) + if (relRecChan.m_withheldMessages[i] != null) + numWithheld++; + } + + return numWithheld; + } + } + + /// + /// Gets the number of unsent and stored messages for this connection + /// + private void GetUnsentAndStoredMessages(out int numUnsent, out int numStored) + { + numUnsent = 0; + numStored = 0; + foreach (NetSenderChannelBase sendChan in m_connection.m_sendChannels) + { + if (sendChan == null) + continue; + + numUnsent += sendChan.QueuedSendsCount; + + var relSendChan = sendChan as NetReliableSenderChannel; + if (relSendChan == null) + { + continue; + } + + for (int i = 0; i < relSendChan.m_storedMessages.Length; i++) + if (relSendChan.m_storedMessages[i].Message != null) + numStored++; + } + } + + #if !USE_RELEASE_STATISTICS [Conditional("DEBUG")] #endif @@ -157,57 +225,26 @@ namespace Lidgren.Network m_droppedMessages++; } + /// /// Returns a string that represents this object /// public override string ToString() { - StringBuilder bdr = new StringBuilder(); - //bdr.AppendLine("Average roundtrip time: " + NetTime.ToReadable(m_connection.m_averageRoundtripTime)); - bdr.AppendLine("Current MTU: " + m_connection.m_currentMTU); - bdr.AppendLine("Sent " + m_sentBytes + " bytes in " + m_sentMessages + " messages in " + m_sentPackets + " packets"); - bdr.AppendLine("Received " + m_receivedBytes + " bytes in " + m_receivedMessages + " messages (of which " + m_receivedFragments + " fragments) in " + m_receivedPackets + " packets"); - bdr.AppendLine("Dropped " + m_droppedMessages + " messages (dupes/late/early)"); + GetUnsentAndStoredMessages(out var numUnsent, out var numStored); - if (m_resentMessagesDueToDelay > 0) - bdr.AppendLine("Resent messages (delay): " + m_resentMessagesDueToDelay); - if (m_resentMessagesDueToHole > 0) - bdr.AppendLine("Resent messages (holes): " + m_resentMessagesDueToHole); + var numWithheld = WithheldMessages; - int numUnsent = 0; - int numStored = 0; - foreach (NetSenderChannelBase sendChan in m_connection.m_sendChannels) - { - if (sendChan == null) - continue; - numUnsent += sendChan.QueuedSendsCount; - - var relSendChan = sendChan as NetReliableSenderChannel; - if (relSendChan != null) - { - for (int i = 0; i < relSendChan.m_storedMessages.Length; i++) - if (relSendChan.m_storedMessages[i].Message != null) - numStored++; - } - } - - int numWithheld = 0; - foreach (NetReceiverChannelBase recChan in m_connection.m_receiveChannels) - { - var relRecChan = recChan as NetReliableOrderedReceiver; - if (relRecChan != null) - { - for (int i = 0; i < relRecChan.m_withheldMessages.Length; i++) - if (relRecChan.m_withheldMessages[i] != null) - numWithheld++; - } - } - - bdr.AppendLine("Unsent messages: " + numUnsent); - bdr.AppendLine("Stored messages: " + numStored); - bdr.AppendLine("Withheld messages: " + numWithheld); - - return bdr.ToString(); + return @$"Current MTU: {m_connection.m_currentMTU} +Sent {m_sentBytes} bytes in {m_sentMessages} messages in {m_sentPackets} packets +Received {m_receivedBytes} bytes in {m_receivedMessages} messages (of which {m_receivedFragments} fragments) in {m_receivedPackets} packets +Dropped {m_droppedMessages} messages (dupes/late/early) +Resent messages (delay): {m_resentMessagesDueToDelay} +Resent messages (holes): {m_resentMessagesDueToHole} +Unsent messages: {numUnsent} +Stored messages: {numStored} +Withheld messages: {numWithheld}"; } + } } \ No newline at end of file diff --git a/Lidgren.Network/NetFragmentationHelper.cs b/Lidgren.Network/NetFragmentationHelper.cs index 85efe4d13..cd82f9cb6 100644 --- a/Lidgren.Network/NetFragmentationHelper.cs +++ b/Lidgren.Network/NetFragmentationHelper.cs @@ -5,7 +5,7 @@ namespace Lidgren.Network internal static class NetFragmentationHelper { internal static int WriteHeader( - byte[] destination, + Span destination, int ptr, int group, int totalBits, @@ -50,7 +50,7 @@ namespace Lidgren.Network return ptr; } - internal static int ReadHeader(byte[] buffer, int ptr, out int group, out int totalBits, out int chunkByteSize, out int chunkNumber) + internal static int ReadHeader(Span buffer, int ptr, out int group, out int totalBits, out int chunkByteSize, out int chunkNumber) { int num1 = 0; int num2 = 0; diff --git a/Lidgren.Network/NetOutgoingMessage.cs b/Lidgren.Network/NetOutgoingMessage.cs index d982cfdba..9f302b25a 100644 --- a/Lidgren.Network/NetOutgoingMessage.cs +++ b/Lidgren.Network/NetOutgoingMessage.cs @@ -57,7 +57,7 @@ namespace Lidgren.Network m_fragmentGroup = 0; } - internal int Encode(byte[] intoBuffer, int ptr, int sequenceNumber) + internal int Encode(Span intoBuffer, int ptr, int sequenceNumber) { // 8 bits - NetMessageType // 1 bit - Fragment? @@ -78,7 +78,9 @@ namespace Lidgren.Network int byteLen = NetUtility.BytesToHoldBits(m_bitLength); if (byteLen > 0) { - Buffer.BlockCopy(m_data, 0, intoBuffer, ptr, byteLen); + m_data.Slice(0,byteLen) + .CopyTo(intoBuffer.Slice(ptr,byteLen)); + //Buffer.BlockCopy(m_data, 0, intoBuffer, ptr, byteLen); ptr += byteLen; } } @@ -102,7 +104,9 @@ namespace Lidgren.Network int byteLen = NetUtility.BytesToHoldBits(m_bitLength); if (byteLen > 0) { - Buffer.BlockCopy(m_data, (int)(m_fragmentChunkNumber * m_fragmentChunkByteSize), intoBuffer, ptr, byteLen); + m_data.Slice(m_fragmentChunkNumber * m_fragmentChunkByteSize,byteLen) + .CopyTo(intoBuffer.Slice(ptr, byteLen)); + //Buffer.BlockCopy(m_data, (int)(m_fragmentChunkNumber * m_fragmentChunkByteSize), intoBuffer, ptr, byteLen); ptr += byteLen; } } diff --git a/Lidgren.Network/NetPeer.Fragmentation.cs b/Lidgren.Network/NetPeer.Fragmentation.cs index 2628b8e68..b486e528c 100644 --- a/Lidgren.Network/NetPeer.Fragmentation.cs +++ b/Lidgren.Network/NetPeer.Fragmentation.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.Threading; using System.Collections.Generic; @@ -53,7 +54,7 @@ namespace Lidgren.Network NetOutgoingMessage chunk = CreateMessage(0); chunk.m_bitLength = (bitsLeft > bitsPerChunk ? bitsPerChunk : bitsLeft); - chunk.m_data = msg.m_data; + chunk.m_buf = msg.m_buf; chunk.m_fragmentGroup = group; chunk.m_fragmentGroupTotalBits = totalBytes * 8; chunk.m_fragmentChunkByteSize = bytesPerChunk; @@ -127,9 +128,11 @@ namespace Lidgren.Network ReceivedFragmentGroup info; if (!groups.TryGetValue(group, out info)) { - info = new ReceivedFragmentGroup(); - info.Data = new byte[totalBytes]; - info.ReceivedChunks = new NetBitVector(totalNumChunks); + info = new ReceivedFragmentGroup + { + Data = ArrayPool.Shared.Rent(totalBytes), + ReceivedChunks = new NetBitVector(totalNumChunks) + }; groups[group] = info; } @@ -138,7 +141,9 @@ namespace Lidgren.Network // copy to data int offset = (chunkNumber * chunkByteSize); - Buffer.BlockCopy(im.m_data, ptr, info.Data, offset, im.LengthBytes - ptr); + //Buffer.BlockCopy(im.m_data, ptr, info.Data, offset, im.LengthBytes - ptr); + im.m_data.Slice(ptr,im.LengthBytes - ptr) + .CopyTo(new Span(info.Data,offset,im.LengthBytes - ptr)); int cnt = info.ReceivedChunks.Count(); //LogVerbose("Found fragment #" + chunkNumber + " in group " + group + " offset " + offset + " of total bits " + totalBits + " (total chunks done " + cnt + ")"); @@ -148,7 +153,7 @@ namespace Lidgren.Network if (info.ReceivedChunks.Count() == totalNumChunks) { // Done! Transform this incoming message - im.m_data = info.Data; + im.m_buf = info.Data; im.m_bitLength = (int)totalBits; im.m_isFragment = false; diff --git a/Lidgren.Network/NetPeer.Internal.cs b/Lidgren.Network/NetPeer.Internal.cs index feb7c56da..3d0ffd899 100644 --- a/Lidgren.Network/NetPeer.Internal.cs +++ b/Lidgren.Network/NetPeer.Internal.cs @@ -1,11 +1,12 @@ using System; +using System.Buffers; using System.Net; using System.Threading; using System.Diagnostics; using System.Security.Cryptography; using System.Net.Sockets; using System.Collections.Generic; - +using System.Runtime.InteropServices; #if !__NOIPENDPOINT__ using NetEndPoint = System.Net.IPEndPoint; #endif @@ -152,7 +153,7 @@ namespace Lidgren.Network const uint IOC_IN = 0x80000000; const uint IOC_VENDOR = 0x18000000; uint SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12; - m_socket.IOControl((int)SIO_UDP_CONNRESET, new byte[] { Convert.ToByte(false) }, null); + m_socket.IOControl((int)SIO_UDP_CONNRESET, new[] { Convert.ToByte(false) }, null); } catch { @@ -191,19 +192,25 @@ namespace Lidgren.Network // bind to socket BindSocket(false); - m_receiveBuffer = new byte[m_configuration.ReceiveBufferSize]; - m_sendBuffer = new byte[m_configuration.SendBufferSize]; - m_readHelperMessage = new NetIncomingMessage(NetIncomingMessageType.Error); - m_readHelperMessage.m_data = m_receiveBuffer; + m_receiveBuffer = ArrayPool.Shared.Rent(m_configuration.ReceiveBufferSize); + m_sendBuffer = ArrayPool.Shared.Rent(m_configuration.SendBufferSize); + m_readHelperMessage = new NetIncomingMessage(NetIncomingMessageType.Error) + { + m_buf = m_receiveBuffer + }; - byte[] macBytes = NetUtility.GetMacAddressBytes(); + var macBytes = NetUtility.GetMacAddressBytes(); var boundEp = m_socket.LocalEndPoint as NetEndPoint; - byte[] epBytes = BitConverter.GetBytes(boundEp.GetHashCode()); - byte[] combined = new byte[epBytes.Length + macBytes.Length]; - Array.Copy(epBytes, 0, combined, 0, epBytes.Length); - Array.Copy(macBytes, 0, combined, epBytes.Length, macBytes.Length); - m_uniqueIdentifier = BitConverter.ToInt64(NetUtility.ComputeSHAHash(combined), 0); + var hashCode = boundEp!.GetHashCode(); + var epBytes = MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref hashCode,1)); + // ReSharper disable once SuggestVarOrType_Elsewhere + Span combined = stackalloc byte[epBytes.Length + macBytes.Length]; + //Array.Copy(epBytes, 0, combined, 0, epBytes.Length); + epBytes.CopyTo(combined.Slice(0,epBytes.Length)); + //Array.Copy(macBytes, 0, combined, epBytes.Length, macBytes.Length); + macBytes.CopyTo(combined.Slice(epBytes.Length, macBytes.Length)); + m_uniqueIdentifier = MemoryMarshal.Cast(NetUtility.ComputeSHAHash(combined, stackalloc byte[32]))[0]; m_status = NetPeerStatus.Running; } @@ -556,7 +563,9 @@ namespace Lidgren.Network msg.m_senderEndPoint = ipsender; msg.m_bitLength = payloadBitLength; - Buffer.BlockCopy(m_receiveBuffer, ptr, msg.m_data, 0, payloadByteLength); + //Buffer.BlockCopy(m_receiveBuffer, ptr, msg.m_data, 0, payloadByteLength); + new Memory(m_receiveBuffer,ptr,payloadByteLength).Span + .CopyTo(msg.m_data.Slice(0, payloadByteLength)); if (sender != null) { if (tp == NetMessageType.Unconnected) @@ -606,7 +615,12 @@ namespace Lidgren.Network { NetIncomingMessage dm = CreateIncomingMessage(NetIncomingMessageType.DiscoveryRequest, payloadByteLength); if (payloadByteLength > 0) - Buffer.BlockCopy(m_receiveBuffer, ptr, dm.m_data, 0, payloadByteLength); + { + //Buffer.BlockCopy(m_receiveBuffer, ptr, dm.m_data, 0, payloadByteLength); + new Memory(m_receiveBuffer, ptr, payloadByteLength).Span + .CopyTo(dm.m_data.Slice(0, payloadByteLength)); + } + dm.m_receiveTime = now; dm.m_bitLength = payloadByteLength * 8; dm.m_senderEndPoint = senderEndPoint; @@ -620,7 +634,11 @@ namespace Lidgren.Network { NetIncomingMessage dr = CreateIncomingMessage(NetIncomingMessageType.DiscoveryResponse, payloadByteLength); if (payloadByteLength > 0) - Buffer.BlockCopy(m_receiveBuffer, ptr, dr.m_data, 0, payloadByteLength); + { + //Buffer.BlockCopy(m_receiveBuffer, ptr, dr.m_data, 0, payloadByteLength); + new Memory(m_receiveBuffer, ptr, payloadByteLength).Span + .CopyTo(dr.m_data.Slice(0, payloadByteLength)); + } dr.m_receiveTime = now; dr.m_bitLength = payloadByteLength * 8; dr.m_senderEndPoint = senderEndPoint; diff --git a/Lidgren.Network/NetPeer.LatencySimulation.cs b/Lidgren.Network/NetPeer.LatencySimulation.cs index 6ef891f99..a3ad69a64 100644 --- a/Lidgren.Network/NetPeer.LatencySimulation.cs +++ b/Lidgren.Network/NetPeer.LatencySimulation.cs @@ -20,6 +20,7 @@ USE OR OTHER DEALINGS IN THE SOFTWARE. //#define USE_RELEASE_STATISTICS using System; +using System.Buffers; using System.Collections.Generic; using System.Net; using System.Net.Sockets; @@ -89,8 +90,10 @@ namespace Lidgren.Network // Enqueue delayed packet DelayedPacket p = new DelayedPacket(); p.Target = target; - p.Data = new byte[numBytes]; - Buffer.BlockCopy(m_sendBuffer, 0, p.Data, 0, numBytes); + p.Data = ArrayPool.Shared.Rent(numBytes); + //Buffer.BlockCopy(m_sendBuffer, 0, p.Data, 0, numBytes); + new Memory(m_sendBuffer, 0, numBytes) + .CopyTo(p.Data); p.DelayedUntil = NetTime.Now + delay; m_delayedPackets.Add(p); @@ -113,8 +116,9 @@ namespace Lidgren.Network { if (now > p.DelayedUntil) { - ActuallySendPacket(p.Data, p.Data.Length, p.Target, out connectionReset); + ActuallySendPacket(new Span(p.Data), p.Data.Length, p.Target, out connectionReset); m_delayedPackets.Remove(p); + ArrayPool.Shared.Return(p.Data); goto RestartDelaySending; } } @@ -126,7 +130,10 @@ namespace Lidgren.Network { bool connectionReset; foreach (DelayedPacket p in m_delayedPackets) - ActuallySendPacket(p.Data, p.Data.Length, p.Target, out connectionReset); + { + ActuallySendPacket(new Span(p.Data), p.Data.Length, p.Target, out connectionReset); + ArrayPool.Shared.Return(p.Data); + } m_delayedPackets.Clear(); } catch { } @@ -135,7 +142,7 @@ namespace Lidgren.Network //Avoids allocation on mapping to IPv6 private IPEndPoint targetCopy = new IPEndPoint(IPAddress.Any, 0); - internal bool ActuallySendPacket(byte[] data, int numBytes, NetEndPoint target, out bool connectionReset) + internal bool ActuallySendPacket(ReadOnlySpan data, int numBytes, NetEndPoint target, out bool connectionReset) { connectionReset = false; IPAddress ba = default(IPAddress); @@ -161,7 +168,7 @@ namespace Lidgren.Network targetCopy.Address = target.Address; } - int bytesSent = m_socket.SendTo(data, 0, numBytes, SocketFlags.None, targetCopy); + int bytesSent = m_socket.SendTo(data.Slice( 0, numBytes ), 0, targetCopy); if (numBytes != bytesSent) LogWarning("Failed to send the full " + numBytes + "; only " + bytesSent + " bytes sent in packet!"); diff --git a/Lidgren.Network/NetPeer.MessagePools.cs b/Lidgren.Network/NetPeer.MessagePools.cs index ea7020884..02d30a09c 100644 --- a/Lidgren.Network/NetPeer.MessagePools.cs +++ b/Lidgren.Network/NetPeer.MessagePools.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.Collections.Generic; using System.Text; @@ -6,27 +7,21 @@ namespace Lidgren.Network { public partial class NetPeer { - internal List m_storagePool; private NetQueue m_outgoingMessagesPool; private NetQueue m_incomingMessagesPool; - internal int m_storagePoolBytes; - internal int m_storageSlotsUsedCount; private int m_maxCacheCount; private void InitializePools() { - m_storageSlotsUsedCount = 0; if (m_configuration.UseMessageRecycling) { - m_storagePool = new List(16); m_outgoingMessagesPool = new NetQueue(4); m_incomingMessagesPool = new NetQueue(4); } else { - m_storagePool = null; m_outgoingMessagesPool = null; m_incomingMessagesPool = null; } @@ -36,63 +31,12 @@ namespace Lidgren.Network internal byte[] GetStorage(int minimumCapacityInBytes) { - if (m_storagePool == null) - return new byte[minimumCapacityInBytes]; - - lock (m_storagePool) - { - for (int i = 0; i < m_storagePool.Count; i++) - { - byte[] retval = m_storagePool[i]; - if (retval != null && retval.Length >= minimumCapacityInBytes) - { - m_storagePool[i] = null; - m_storageSlotsUsedCount--; - m_storagePoolBytes -= retval.Length; - return retval; - } - } - } - m_statistics.m_bytesAllocated += minimumCapacityInBytes; - return new byte[minimumCapacityInBytes]; + return ArrayPool.Shared.Rent(minimumCapacityInBytes); } internal void Recycle(byte[] storage) { - if (m_storagePool == null || storage == null) - return; - - lock (m_storagePool) - { - int cnt = m_storagePool.Count; - for (int i = 0; i < cnt; i++) - { - if (m_storagePool[i] == null) - { - m_storageSlotsUsedCount++; - m_storagePoolBytes += storage.Length; - m_storagePool[i] = storage; - return; - } - } - - if (m_storagePool.Count >= m_maxCacheCount) - { - // pool is full; replace randomly chosen entry to keep size distribution - var idx = NetRandom.Instance.Next(m_storagePool.Count); - - m_storagePoolBytes -= m_storagePool[idx].Length; - m_storagePoolBytes += storage.Length; - - m_storagePool[idx] = storage; // replace - } - else - { - m_storageSlotsUsedCount++; - m_storagePoolBytes += storage.Length; - m_storagePool.Add(storage); - } - } + ArrayPool.Shared.Return(storage); } /// @@ -137,19 +81,19 @@ namespace Lidgren.Network NetException.Assert(retval.m_recyclingCount == 0, "Wrong recycling count! Should be zero" + retval.m_recyclingCount); if (initialCapacity > 0) - retval.m_data = GetStorage(initialCapacity); + retval.m_buf = GetStorage(initialCapacity); return retval; } - internal NetIncomingMessage CreateIncomingMessage(NetIncomingMessageType tp, byte[] useStorageData) + internal NetIncomingMessage CreateIncomingMessage(NetIncomingMessageType tp, ArraySegment useStorageData) { NetIncomingMessage retval; if (m_incomingMessagesPool == null || !m_incomingMessagesPool.TryDequeue(out retval)) retval = new NetIncomingMessage(tp); else retval.m_incomingMessageType = tp; - retval.m_data = useStorageData; + retval.m_buf = useStorageData.Array; return retval; } @@ -160,7 +104,7 @@ namespace Lidgren.Network retval = new NetIncomingMessage(tp); else retval.m_incomingMessageType = tp; - retval.m_data = GetStorage(minimumByteSize); + retval.m_buf = GetStorage(minimumByteSize); return retval; } @@ -174,8 +118,8 @@ namespace Lidgren.Network NetException.Assert(m_incomingMessagesPool.Contains(msg) == false, "Recyling already recycled incoming message! Thread race?"); - byte[] storage = msg.m_data; - msg.m_data = null; + byte[] storage = msg.m_buf; + msg.m_buf = null; Recycle(storage); msg.Reset(); @@ -207,8 +151,8 @@ namespace Lidgren.Network // however, in RELEASE, we'll just have to accept this and move on with life msg.m_recyclingCount = 0; - byte[] storage = msg.m_data; - msg.m_data = null; + byte[] storage = msg.m_buf; + msg.m_buf = null; // message fragments cannot be recycled // TODO: find a way to recycle large message after all fragments has been acknowledged; or? possibly better just to garbage collect them diff --git a/Lidgren.Network/NetPeerStatistics.cs b/Lidgren.Network/NetPeerStatistics.cs index c55dafc73..a3bcbd863 100644 --- a/Lidgren.Network/NetPeerStatistics.cs +++ b/Lidgren.Network/NetPeerStatistics.cs @@ -44,8 +44,6 @@ namespace Lidgren.Network internal int m_sentBytes; internal int m_receivedBytes; - internal long m_bytesAllocated; - internal NetPeerStatistics(NetPeer peer) { m_peer = peer; @@ -63,56 +61,42 @@ namespace Lidgren.Network m_sentBytes = 0; m_receivedBytes = 0; - - m_bytesAllocated = 0; } /// /// Gets the number of sent packets since the NetPeer was initialized /// - public int SentPackets { get { return m_sentPackets; } } + public int SentPackets => m_sentPackets; /// /// Gets the number of received packets since the NetPeer was initialized /// - public int ReceivedPackets { get { return m_receivedPackets; } } + public int ReceivedPackets => m_receivedPackets; /// /// Gets the number of sent messages since the NetPeer was initialized /// - public int SentMessages { get { return m_sentMessages; } } + public int SentMessages => m_sentMessages; /// /// Gets the number of received messages since the NetPeer was initialized /// - public int ReceivedMessages { get { return m_receivedMessages; } } + public int ReceivedMessages => m_receivedMessages; + + /// + /// Gets the number of received fragments since the NetPeer was initialized + /// + public int ReceivedFragments => m_receivedFragments; /// /// Gets the number of sent bytes since the NetPeer was initialized /// - public int SentBytes { get { return m_sentBytes; } } + public int SentBytes => m_sentBytes; /// /// Gets the number of received bytes since the NetPeer was initialized /// - public int ReceivedBytes { get { return m_receivedBytes; } } - - /// - /// Gets the number of bytes allocated (and possibly garbage collected) for message storage - /// - public long StorageBytesAllocated { get { return m_bytesAllocated; } } - - /// - /// Gets the number of bytes in the recycled pool - /// - public int BytesInRecyclePool - { - get - { - lock (m_peer.m_storagePool) - return m_peer.m_storagePoolBytes; - } - } + public int ReceivedBytes => m_receivedBytes; #if !USE_RELEASE_STATISTICS [Conditional("DEBUG")] @@ -140,19 +124,15 @@ namespace Lidgren.Network /// public override string ToString() { - StringBuilder bdr = new StringBuilder(); - bdr.AppendLine(m_peer.ConnectionsCount.ToString() + " connections"); #if DEBUG || USE_RELEASE_STATISTICS - bdr.AppendLine("Sent " + m_sentBytes + " bytes in " + m_sentMessages + " messages in " + m_sentPackets + " packets"); - bdr.AppendLine("Received " + m_receivedBytes + " bytes in " + m_receivedMessages + " messages (of which " + m_receivedFragments + " fragments) in " + m_receivedPackets + " packets"); + return @$"{m_peer.ConnectionsCount} connections +Sent {m_sentBytes} bytes in {m_sentMessages} messages in {m_sentPackets} packets +Received {m_receivedBytes} bytes in {m_receivedMessages} messages (of which {m_receivedFragments} fragments) in {m_receivedPackets} packets"; #else - bdr.AppendLine("Sent (n/a) bytes in (n/a) messages in (n/a) packets"); - bdr.AppendLine("Received (n/a) bytes in (n/a) messages in (n/a) packets"); + return @$"{m_peer.ConnectionsCount} connections +Sent (n/a) bytes in (n/a) messages in (n/a) packets +Received (n/a) bytes in (n/a) messages in (n/a) packets"; #endif - bdr.AppendLine("Storage allocated " + m_bytesAllocated + " bytes"); - if (m_peer.m_storagePool != null) - bdr.AppendLine("Recycled pool " + m_peer.m_storagePoolBytes + " bytes (" + m_peer.m_storageSlotsUsedCount + " entries)"); - return bdr.ToString(); } } } diff --git a/Lidgren.Network/NetSRP.cs b/Lidgren.Network/NetSRP.cs index ffbfc65fc..662daf8a0 100644 --- a/Lidgren.Network/NetSRP.cs +++ b/Lidgren.Network/NetSRP.cs @@ -24,9 +24,9 @@ namespace Lidgren.Network string two = NetUtility.ToHexString(g.ToByteArrayUnsigned()); string ccstr = one + two.PadLeft(one.Length, '0'); - byte[] cc = NetUtility.ToByteArray(ccstr); + var cc = NetUtility.HexToBytes(ccstr, stackalloc byte[ccstr.Length*2]); - var ccHashed = NetUtility.ComputeSHAHash(cc); + var ccHashed = NetUtility.ComputeSHAHash(cc, stackalloc byte[NetUtility.SHAHashSize]); return new NetBigInteger(NetUtility.ToHexString(ccHashed), 16); } @@ -53,23 +53,29 @@ namespace Lidgren.Network /// /// Computer private key (x) /// - public static byte[] ComputePrivateKey(string username, string password, byte[] salt) + public static byte[] ComputePrivateKey(string username, string password, ReadOnlySpan salt) { - byte[] tmp = Encoding.UTF8.GetBytes(username + ":" + password); - byte[] innerHash = NetUtility.ComputeSHAHash(tmp); + var tmpStr = username + ":" + password; + // ReSharper disable once SuggestVarOrType_Elsewhere + Span tmp = stackalloc byte[Encoding.UTF8.GetByteCount(tmpStr)]; + Encoding.UTF8.GetBytes(tmpStr, tmp); + var innerHash = NetUtility.ComputeSHAHash(tmp, stackalloc byte[32]); - byte[] total = new byte[innerHash.Length + salt.Length]; - Buffer.BlockCopy(salt, 0, total, 0, salt.Length); - Buffer.BlockCopy(innerHash, 0, total, salt.Length, innerHash.Length); + // ReSharper disable once SuggestVarOrType_Elsewhere + Span total = stackalloc byte[innerHash.Length + salt.Length]; + //Buffer.BlockCopy(salt, 0, total, 0, salt.Length); + salt.CopyTo(total); + //Buffer.BlockCopy(innerHash, 0, total, salt.Length, innerHash.Length); + innerHash.CopyTo(total.Slice(salt.Length, innerHash.Length)); // x ie. H(salt || H(username || ":" || password)) - return new NetBigInteger(NetUtility.ToHexString(NetUtility.ComputeSHAHash(total)), 16).ToByteArrayUnsigned(); + return new NetBigInteger(NetUtility.ToHexString(NetUtility.ComputeSHAHash(total, stackalloc byte[32])), 16).ToByteArrayUnsigned(); } /// /// Creates a verifier that the server can later use to authenticate users later on (v) /// - public static byte[] ComputeServerVerifier(byte[] privateKey) + public static byte[] ComputeServerVerifier(ReadOnlySpan privateKey) { NetBigInteger x = new NetBigInteger(NetUtility.ToHexString(privateKey), 16); @@ -82,7 +88,7 @@ namespace Lidgren.Network /// /// Compute client public ephemeral value (A) /// - public static byte[] ComputeClientEphemeral(byte[] clientPrivateEphemeral) // a + public static byte[] ComputeClientEphemeral(ReadOnlySpan clientPrivateEphemeral) // a { // A= g^a (mod N) NetBigInteger a = new NetBigInteger(NetUtility.ToHexString(clientPrivateEphemeral), 16); @@ -94,7 +100,7 @@ namespace Lidgren.Network /// /// Compute server ephemeral value (B) /// - public static byte[] ComputeServerEphemeral(byte[] serverPrivateEphemeral, byte[] verifier) // b + public static byte[] ComputeServerEphemeral(ReadOnlySpan serverPrivateEphemeral, ReadOnlySpan verifier) // b { var b = new NetBigInteger(NetUtility.ToHexString(serverPrivateEphemeral), 16); var v = new NetBigInteger(NetUtility.ToHexString(verifier), 16); @@ -110,7 +116,7 @@ namespace Lidgren.Network /// /// Compute intermediate value (u) /// - public static byte[] ComputeU(byte[] clientPublicEphemeral, byte[] serverPublicEphemeral) + public static byte[] ComputeU(ReadOnlySpan clientPublicEphemeral, ReadOnlySpan serverPublicEphemeral) { // u = SHA-1(A || B) string one = NetUtility.ToHexString(clientPublicEphemeral); @@ -119,9 +125,9 @@ namespace Lidgren.Network int len = 66; // Math.Max(one.Length, two.Length); string ccstr = one.PadLeft(len, '0') + two.PadLeft(len, '0'); - byte[] cc = NetUtility.ToByteArray(ccstr); + var cc = NetUtility.HexToBytes(ccstr, stackalloc byte[ccstr.Length*2]); - var ccHashed = NetUtility.ComputeSHAHash(cc); + var ccHashed = NetUtility.ComputeSHAHash(cc, stackalloc byte[32]); return new NetBigInteger(NetUtility.ToHexString(ccHashed), 16).ToByteArrayUnsigned(); } @@ -129,7 +135,7 @@ namespace Lidgren.Network /// /// Computes the server session value /// - public static byte[] ComputeServerSessionValue(byte[] clientPublicEphemeral, byte[] verifier, byte[] udata, byte[] serverPrivateEphemeral) + public static byte[] ComputeServerSessionValue(ReadOnlySpan clientPublicEphemeral, ReadOnlySpan verifier, ReadOnlySpan udata, ReadOnlySpan serverPrivateEphemeral) { // S = (Av^u) ^ b (mod N) var A = new NetBigInteger(NetUtility.ToHexString(clientPublicEphemeral), 16); @@ -161,9 +167,9 @@ namespace Lidgren.Network /// /// Create XTEA symmetrical encryption object from sessionValue /// - public static NetXtea CreateEncryption(NetPeer peer, byte[] sessionValue) + public static NetXtea CreateEncryption(NetPeer peer, ReadOnlySpan sessionValue) { - var hash = NetUtility.ComputeSHAHash(sessionValue); + var hash = NetUtility.ComputeSHAHash(sessionValue, stackalloc byte[32]); var key = new byte[16]; for(int i=0;i<16;i++) diff --git a/Lidgren.Network/NetUtility.cs b/Lidgren.Network/NetUtility.cs index 600447a43..f43898940 100644 --- a/Lidgren.Network/NetUtility.cs +++ b/Lidgren.Network/NetUtility.cs @@ -23,12 +23,14 @@ using NetAddress = System.Net.IPAddress; #endif using System; +using System.Buffers.Binary; using System.Net; using System.Net.Sockets; using System.Text; using System.Text.RegularExpressions; using System.Collections.Generic; +using System.Runtime.InteropServices; using System.Security.Cryptography; namespace Lidgren.Network @@ -78,6 +80,13 @@ namespace Lidgren.Network } private static IPAddress s_broadcastAddress; + +#if NON_IEC_UNITS + private static readonly string[] ByteStringSuffixes = { "B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" }; +#else + private static readonly string[] ByteStringSuffixes = { "B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB" }; +#endif + public static IPAddress GetCachedBroadcastAddress() { if (s_broadcastAddress == null) @@ -219,27 +228,62 @@ namespace Lidgren.Network /// /// Create a hex string from an array of bytes /// - public static string ToHexString(byte[] data) + public static string ToHexString(ReadOnlySpan data, int offset, int length) { - return ToHexString(data, 0, data.Length); + return ToHexString(data.Slice(offset, length)); } /// /// Create a hex string from an array of bytes /// - public static string ToHexString(byte[] data, int offset, int length) +#if UNSAFE + public static unsafe string ToHexString(ReadOnlySpan data) { - char[] c = new char[length * 2]; - byte b; - for (int i = 0; i < length; ++i) + var l = data.Length; + fixed (void* p = data) { - b = ((byte)(data[offset + i] >> 4)); - c[i * 2] = (char)(b > 9 ? b + 0x37 : b + 0x30); - b = ((byte)(data[offset + i] & 0xF)); - c[i * 2 + 1] = (char)(b > 9 ? b + 0x37 : b + 0x30); + return string.Create(data.Length * 2, (p: (IntPtr) p, l), (c, d) => + { + var s = new ReadOnlySpan((void*) d.p, d.l); + var u = MemoryMarshal.Cast(c); + for (var i = 0; i < l; ++i) + { + var b = s[i]; + var nibLo = b >> 4; + var isDigLo = (nibLo - 10) >> 31; + var chLo = 55 + nibLo + (isDigLo & -7); + var nibHi = b & 0xF; + var isDigHi = (nibHi - 10) >> 31; + var chHi = 55 + nibHi + (isDigHi & -7); + u[i] = (chHi << 16) | chLo; + } + }); } + } +#else + + public static string ToHexString(ReadOnlySpan data) + { + var l = data.Length; + // ReSharper disable once SuggestVarOrType_Elsewhere + Span c = stackalloc char[l*2]; + var u = MemoryMarshal.Cast(c); + + for (var i = 0; i < l; ++i) + { + var b = data[i]; + var nibLo = b >> 4; + var isDigLo = (nibLo - 10) >> 31; + var chLo = 55 + nibLo + (isDigLo & -7); + var nibHi = b & 0xF; + var isDigHi = (nibHi - 10) >> 31; + var chHi = 55 + nibHi + (isDigHi & -7); + u[i] = (chHi << 16) | chLo; + } + return new string(c); } +#endif /// /// Returns true if the endpoint supplied is on the same subnet as this host @@ -276,10 +320,20 @@ namespace Lidgren.Network [CLSCompliant(false)] public static int BitsToHoldUInt(uint value) { - int bits = 1; - while ((value >>= 1) != 0) - bits++; - return bits; +#if NETCOREAPP3_1 + return 32 - System.Numerics.BitOperations.LeadingZeroCount(value); +#else + value |= value >> 1; + value |= value >> 2; + value |= value >> 4; + value |= value >> 8; + value |= value >> 16; + value -= ((value >> 1) & 0x55555555U); + value = (value & 0x33333333U) + ((value >> 2) & 0x33333333U); + value = (value + (value >> 4)) & 0x0F0F0F0FU; + value = (value * 0x01010101U) >> 24; + return (int)(value & 127); +#endif } /// @@ -288,10 +342,22 @@ namespace Lidgren.Network [CLSCompliant(false)] public static int BitsToHoldUInt64(ulong value) { - int bits = 1; - while ((value >>= 1) != 0) - bits++; - return bits; +#if NETCOREAPP3_1 + return 64 - System.Numerics.BitOperations.LeadingZeroCount(value); +#else + value |= value >> 1; + value |= value >> 2; + value |= value >> 4; + value |= value >> 8; + value |= value >> 16; + value |= value >> 32; + value -= ((value >> 1) & 0x5555555555555555U); + value = (value & 0x3333333333333333U) + + ((value >> 2) & 0x3333333333333333U); + value = (value + (value >> 4)) & 0x0F0F0F0F0F0F0F0FU; + value = (value * 0x0101010101010101U) >> 56; + return (int)(value & 127); +#endif } /// @@ -302,28 +368,6 @@ namespace Lidgren.Network return (numBits + 7) / 8; } - internal static UInt32 SwapByteOrder(UInt32 value) - { - return - ((value & 0xff000000) >> 24) | - ((value & 0x00ff0000) >> 8) | - ((value & 0x0000ff00) << 8) | - ((value & 0x000000ff) << 24); - } - - internal static UInt64 SwapByteOrder(UInt64 value) - { - return - ((value & 0xff00000000000000L) >> 56) | - ((value & 0x00ff000000000000L) >> 40) | - ((value & 0x0000ff0000000000L) >> 24) | - ((value & 0x000000ff00000000L) >> 8) | - ((value & 0x00000000ff000000L) << 8) | - ((value & 0x0000000000ff0000L) << 24) | - ((value & 0x000000000000ff00L) << 40) | - ((value & 0x00000000000000ffL) << 56); - } - internal static bool CompareElements(byte[] one, byte[] two) { if (one.Length != two.Length) @@ -337,12 +381,25 @@ namespace Lidgren.Network /// /// Convert a hexadecimal string to a byte array /// - public static byte[] ToByteArray(String hexString) + public static Span HexToBytes(String hexString, Span buffer) { - byte[] retval = new byte[hexString.Length / 2]; - for (int i = 0; i < hexString.Length; i += 2) - retval[i / 2] = Convert.ToByte(hexString.Substring(i, 2), 16); - return retval; + if (buffer.Length < hexString.Length/2) + throw new ArgumentOutOfRangeException(nameof(buffer), buffer.Length,"Buffer too small"); + + var hexStrEnum = hexString.GetEnumerator(); + for (var i = 0; i+1 < hexString.Length; i += 2) + { + hexStrEnum.MoveNext(); + var chHi = hexStrEnum.Current; + hexStrEnum.MoveNext(); + var chLo = hexStrEnum.Current; + buffer[i / 2] = (byte)( + (((chHi & 0xF) << 4) + ((chHi & 0x40)>>2) * 9) + |((chLo & 0xF) + ((chLo & 0x40)>>6) * 9) + ); + } + + return buffer; } /// @@ -350,11 +407,10 @@ namespace Lidgren.Network /// public static string ToHumanReadable(long bytes) { - if (bytes < 4000) // 1-4 kb is printed in bytes - return bytes + " bytes"; - if (bytes < 1000 * 1000) // 4-999 kb is printed in kb - return Math.Round(((double)bytes / 1000.0), 2) + " kilobytes"; - return Math.Round(((double)bytes / (1000.0 * 1000.0)), 2) + " megabytes"; // else megabytes + var index = (long)Math.Round(Math.Max(0,Math.Log(bytes, 1024) - 2/3d), MidpointRounding.AwayFromZero); + var denominator = Math.Pow(1024, index); + + return $"{bytes / denominator:0.##} {ByteStringSuffixes[index]}"; } internal static int RelativeSequenceNumber(int nr, int expected) @@ -460,13 +516,7 @@ namespace Lidgren.Network return bdr.ToString(); } - public static byte[] ComputeSHAHash(byte[] bytes) - { - // this is defined in the platform specific files - return ComputeSHAHash(bytes, 0, bytes.Length); - } - - /// + /// /// Copies from to . Maps to an IPv6 address /// /// Source. diff --git a/Lidgren.Network/Platform/NativeSocket.Structs.cs b/Lidgren.Network/Platform/NativeSocket.Structs.cs new file mode 100644 index 000000000..71804db53 --- /dev/null +++ b/Lidgren.Network/Platform/NativeSocket.Structs.cs @@ -0,0 +1,52 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; + +#pragma warning disable 649 +namespace Lidgren.Network { + + public static partial class NativeSockets { + + [SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Local")] + [StructLayout(LayoutKind.Sequential, Size = SockAddrStorageSize)] + private struct SockAddr { + + public ushort SocketAddressFamily; + + } + + [SuppressMessage("ReSharper", "NotAccessedField.Local")] + private struct SockAddrIn { + + public ushort SocketAddressFamily; + + public ushort Port; + + public uint InAddr; + + public ulong Zero; + + } + + [StructLayout(LayoutKind.Sequential, Size = 16)] + private struct InAddr6 { + + } + + [SuppressMessage("ReSharper", "NotAccessedField.Local")] + private struct SockAddrIn6 { + + public ushort SocketAddressFamily; + + public ushort Port; + + public uint FlowInfo; + + public InAddr6 InAddr; + + public uint InScopeId; + + } + + } + +} \ No newline at end of file diff --git a/Lidgren.Network/Platform/NativeSockets.cs b/Lidgren.Network/Platform/NativeSockets.cs new file mode 100644 index 000000000..428060ca2 --- /dev/null +++ b/Lidgren.Network/Platform/NativeSockets.cs @@ -0,0 +1,195 @@ +#nullable enable +using System; +using System.Buffers.Binary; +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Lidgren.Network +{ + + public static partial class NativeSockets + { + + private const int SockAddrStorageSize = 128; // sockaddr_storage + + private static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + + internal static unsafe int _SendTo(int socket, byte* buf, int len, int flags, void* to, int toLen) + => IsWindows + ? WindowsSockets.SendTo(socket, buf, len, flags, to, toLen) + : PosixSockets.SendTo(socket, buf, len, flags, to, toLen); + + internal static unsafe int _RecvFrom(int socket, byte* buf, int len, int flags, void* from, int* fromLen) + => IsWindows + ? WindowsSockets.RecvFrom(socket, buf, len, flags, from, fromLen) + : PosixSockets.RecvFrom(socket, buf, len, flags, from, fromLen); + + public static int GetError(Socket socket) + => (int) socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Error); + + public static unsafe int SendTo(this Socket socket, ReadOnlySpan buffer, int flags, IPEndPoint to) + { + switch (to.AddressFamily) + { + default: + throw new NotImplementedException(to.AddressFamily.ToString()); + + case AddressFamily.InterNetwork: + { + var error = 0; + var addrIn = new SockAddrIn(); + var port = (ushort) to.Port; + addrIn.SocketAddressFamily = (ushort) to.AddressFamily; + addrIn.Port = BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(port) : port; + if (!to.Address.TryWriteBytes(MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref addrIn.InAddr, 1)), out _)) + throw new NotImplementedException("Can't write address."); + + int result; + fixed (byte* pBuf = buffer) + { + result = _SendTo(socket.Handle.ToInt32(), pBuf, buffer.Length, flags, &addrIn, sizeof(SockAddrIn)); + } + + if (result == -1) + { + error = GetError(socket); + if (error == 0) + error = Marshal.GetLastWin32Error(); + } + + if (result == -1) + throw new SocketException(error); + + return result; + } + + case AddressFamily.InterNetworkV6: + { + var error = 0; + var addrIn6 = new SockAddrIn6(); + var port = (ushort) to.Port; + addrIn6.SocketAddressFamily = (ushort) to.AddressFamily; + addrIn6.Port = BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(port) : port; + if (!to.Address.TryWriteBytes(MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref addrIn6.InAddr, 1)), out _)) + throw new NotImplementedException("Can't write address."); + + addrIn6.InScopeId = checked((uint) to.Address.ScopeId); + int result; + fixed (byte* pBuf = buffer) + { + result = _SendTo(socket.Handle.ToInt32(), pBuf, buffer.Length, flags, &addrIn6, sizeof(SockAddrIn6)); + } + + if (result == -1) + { + error = GetError(socket); + if (error == 0) + error = Marshal.GetLastWin32Error(); + } + + if (result == -1) + throw new SocketException(error); + + return result; + } + } + } + + public static unsafe int ReceiveFrom(this Socket socket, Span buffer, int flags, out IPEndPoint? from) + { + var error = 0; + int result; + var fromSockAddrSize = SockAddrStorageSize; + var pFrom = stackalloc byte[SockAddrStorageSize]; + + fixed (byte* pBuf = buffer) + { + result = _RecvFrom(socket.Handle.ToInt32(), pBuf, buffer.Length, flags, pFrom, &fromSockAddrSize); + } + + if (result == -1) + { + error = GetError(socket); + if (error == 0) + { + error = Marshal.GetLastWin32Error(); + } + } + + ReadIp(out from, (SockAddr*) pFrom); + + if (result == -1) + { + throw new SocketException(error); + } + + return result; + } + + private static unsafe void ReadIp(out IPEndPoint? from, SockAddr* pFrom) + { + switch (pFrom->SocketAddressFamily) + { + default: + throw new NotSupportedException(((AddressFamily) pFrom->SocketAddressFamily).ToString()); + case (ushort) AddressFamily.Unspecified: + from = null; + break; + case (ushort) AddressFamily.InterNetwork: + ReadIPv4(out from, (SockAddrIn*) pFrom); + break; + case (ushort) AddressFamily.InterNetworkV6: + ReadIPv6(out from, (SockAddrIn6*) pFrom); + break; + } + } + + private static unsafe void ReadIPv4(out IPEndPoint from, SockAddrIn* addr) + { + var port = BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(addr->Port) : addr->Port; + var ip = BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(addr->InAddr) : addr->InAddr; + + from = new IPEndPoint( + ip, + port + ); + } + + private static unsafe void ReadIPv6(out IPEndPoint from, SockAddrIn6* addr) + { + var port = BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(addr->Port) : addr->Port; + + var ip = new IPAddress(MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref addr->InAddr, 1)), addr->InScopeId); + + from = new IPEndPoint(ip, port); + } + + } + + internal static class PosixSockets + { + + [DllImport("libc", EntryPoint = "sendto")] + internal static extern unsafe int SendTo(int socket, byte* buf, int len, int flags, void* to, int toLen); + + [DllImport("libc", EntryPoint = "recvfrom")] + internal static extern unsafe int RecvFrom(int socket, byte* buf, int len, int flags, void* from, int* fromLen); + + } + + internal static class WindowsSockets + { + + [DllImport("ws2_32", EntryPoint = "sendto")] + internal static extern unsafe int SendTo(int socket, byte* buf, int len, int flags, void* to, int toLen); + + [DllImport("ws2_32", EntryPoint = "recvfrom")] + internal static extern unsafe int RecvFrom(int socket, byte* buf, int len, int flags, void* from, int* fromLen); + + } + +} diff --git a/Lidgren.Network/Platform/PlatformConstrained.cs b/Lidgren.Network/Platform/PlatformConstrained.cs index 47888e961..b5e917178 100644 --- a/Lidgren.Network/Platform/PlatformConstrained.cs +++ b/Lidgren.Network/Platform/PlatformConstrained.cs @@ -69,10 +69,11 @@ namespace Lidgren.Network return new IPAddress(bytes); } - private static readonly SHA1 s_sha = SHA1.Create(); - public static byte[] ComputeSHAHash(byte[] bytes, int offset, int count) + private static readonly SHA256 s_sha = SHA256.Create(); + + public static bool ComputeSHAHash(ReadOnlySpan src, Span dest) { - return s_sha.ComputeHash(bytes, offset, count); + return s_sha.TryComputeHash(src, dest, out _); } } diff --git a/Lidgren.Network/Platform/PlatformWin32.cs b/Lidgren.Network/Platform/PlatformWin32.cs index 79f9ced34..3bf9cdfeb 100644 --- a/Lidgren.Network/Platform/PlatformWin32.cs +++ b/Lidgren.Network/Platform/PlatformWin32.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; @@ -130,15 +131,21 @@ namespace Lidgren.Network System.Threading.Thread.Sleep(milliseconds); } - public static IPAddress CreateAddressFromBytes(byte[] bytes) + public static IPAddress CreateAddressFromBytes(ReadOnlySpan bytes) { return new IPAddress(bytes); } private static readonly SHA256 s_sha = SHA256.Create(); - public static byte[] ComputeSHAHash(byte[] bytes, int offset, int count) + + public static readonly int SHAHashSize = 256; + + public static Span ComputeSHAHash(ReadOnlySpan src, Span dest) { - return s_sha.ComputeHash(bytes, offset, count); + if (!s_sha.TryComputeHash(src, dest, out _)) + throw new InvalidOperationException("Can't compute hash"); + + return dest; } } diff --git a/Lidgren.Network/Properties/AssemblyInfo.cs b/Lidgren.Network/Properties/AssemblyInfo.cs index d70056050..57842c429 100644 --- a/Lidgren.Network/Properties/AssemblyInfo.cs +++ b/Lidgren.Network/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Lidgren.Network")] -[assembly: AssemblyCopyright("Copyright © 2012")] +[assembly: AssemblyCopyright("Copyright © Lidgren 2020")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -32,6 +32,6 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("2012.1.7.0")] -[assembly: AssemblyFileVersion("2012.1.7.0")] -[assembly: System.CLSCompliant(true)] \ No newline at end of file +[assembly: AssemblyVersion("2020.7.0.0")] +[assembly: AssemblyFileVersion("2020.7.0.0")] +[assembly: System.CLSCompliant(false)] \ No newline at end of file diff --git a/Lidgren.Network/RobustToolbox/Lidgren.Network/NetBuffer.Streams.cs b/Lidgren.Network/RobustToolbox/Lidgren.Network/NetBuffer.Streams.cs new file mode 100644 index 000000000..0d21c0df7 --- /dev/null +++ b/Lidgren.Network/RobustToolbox/Lidgren.Network/NetBuffer.Streams.cs @@ -0,0 +1,216 @@ +using System; +using System.IO; + +namespace Lidgren.Network +{ + + public partial class NetBuffer + { + + public class AppenderStream : Stream + { + + internal AppenderStream(NetBuffer netBuffer) + { + Buffer = netBuffer; + } + + protected NetBuffer Buffer; + + protected override void Dispose(bool _) + => Buffer = null; + + protected void DisposedCheck() + { + if (Buffer == null) throw new ObjectDisposedException("Stream"); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + public override long Length => throw new NotSupportedException(); + + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public override void Flush() => DisposedCheck(); + + public override int Read(byte[] buffer, int offset, int count) + => throw new NotSupportedException(); + + public override int Read(Span buffer) + => throw new NotSupportedException(); + + public override void SetLength(long value) + => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) + => Write(new ReadOnlySpan(buffer, offset, count)); + + public override void Write(ReadOnlySpan buffer) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + Buffer.Write(buffer); + } + + public override bool CanRead => false; + + public override bool CanSeek => false; + + public override bool CanWrite => false; + + } + + public abstract class WrapperStream : Stream, IDisposable + { + + protected WrapperStream(NetBuffer netBuffer, in int start, int length, bool isReadMode) + { + NetException.Assert(netBuffer.m_bitLength - start >= length, + isReadMode ? c_readOverflowError : c_writeOverflowError); + + Buffer = netBuffer; + BitOffsetStart = start; + BitOffset = start; + BitOffsetEnd = start + length; + } + + protected NetBuffer Buffer; + + protected int BitOffsetStart; + + protected int BitOffsetEnd; + + protected int BitOffset; + + protected override void Dispose(bool _) + => Buffer = null; + + protected void DisposedCheck() + { + if (Buffer == null) throw new ObjectDisposedException("Stream"); + } + + public override long Seek(long offset, SeekOrigin origin) + { + DisposedCheck(); + switch (origin) + { + case SeekOrigin.Begin: break; + case SeekOrigin.Current: + offset += Position; + break; + case SeekOrigin.End: + offset = Position - offset; + break; + default: throw new ArgumentOutOfRangeException(nameof(origin), origin, "SeekOrigin invalid."); + } + + return Position = offset; + } + + public override long Length => (BitOffsetStart - BitOffsetEnd) >> 3; + + public override long Position + { + get => (BitOffset - BitOffsetStart) >> 3; + set => BitOffset = BitOffsetStart + (checked((int)value) << 3); + } + + } + + public class ReadOnlyWrapperStream : WrapperStream + { + + internal ReadOnlyWrapperStream(NetBuffer netBuffer, int start, int length) + : base(netBuffer, start, length, true) + { + } + + public override void Flush() => DisposedCheck(); + + public override int Read(byte[] buffer, int offset, int count) + => Read(new Span(buffer, offset, count)); + + public override int Read(Span buffer) + { + DisposedCheck(); + var numberOfBytes = buffer.Length; + var numberOfBits = numberOfBytes * 8; + NetException.Assert(BitOffsetEnd - BitOffset >= numberOfBits, c_readOverflowError); + NetBitWriter.ReadBytes(Buffer.m_data, numberOfBytes, BitOffset, buffer, 0); + BitOffset += numberOfBits; + return buffer.Length; + } + + public override void SetLength(long value) + => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) + => throw new NotSupportedException(); + + public override bool CanRead => BitOffset < BitOffsetEnd; + + public override bool CanSeek => true; + + public override bool CanWrite => false; + + } + + + public class WriteOnlyWrapperStream : WrapperStream + { + + internal WriteOnlyWrapperStream(NetBuffer netBuffer, int start, int length) + : base(netBuffer, start, length, false) + { + } + + public override void Flush() => DisposedCheck(); + + public override int Read(byte[] buffer, int offset, int count) + => throw new NotSupportedException(); + + public override int Read(Span buffer) + => throw new NotSupportedException(); + + public override void SetLength(long value) + => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) + => Write(new ReadOnlySpan(buffer, offset, count)); + + public override void Write(ReadOnlySpan buffer) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + var numberOfBytes = buffer.Length; + var numberOfBits = numberOfBytes * 8; + NetException.Assert(BitOffsetEnd - BitOffset >= numberOfBits, c_writeOverflowError); + NetBitWriter.WriteBytes(buffer, 0, numberOfBytes, Buffer.m_data, BitOffset); + BitOffset += numberOfBits; + } + + public override bool CanRead => false; + + public override bool CanSeek => true; + + public override bool CanWrite => BitOffset < BitOffsetEnd; + + } + + } + +} diff --git a/Robust.Server/GameObjects/Components/Appearance/AppearanceComponent.cs b/Robust.Server/GameObjects/Components/Appearance/AppearanceComponent.cs index 516c2c5fc..5b957acf6 100644 --- a/Robust.Server/GameObjects/Components/Appearance/AppearanceComponent.cs +++ b/Robust.Server/GameObjects/Components/Appearance/AppearanceComponent.cs @@ -50,7 +50,7 @@ namespace Robust.Server.GameObjects return true; } - data = default; + data = default!; return false; } diff --git a/Robust.Server/GameObjects/ServerEntityManager.cs b/Robust.Server/GameObjects/ServerEntityManager.cs index 42e39b9b7..9cab62d58 100644 --- a/Robust.Server/GameObjects/ServerEntityManager.cs +++ b/Robust.Server/GameObjects/ServerEntityManager.cs @@ -272,7 +272,7 @@ namespace Robust.Server.GameObjects { foreach (var child in children) { - var ent = child; + var ent = child!; do { @@ -280,7 +280,7 @@ namespace Robust.Server.GameObjects { AddContainedRecursive(ent, set); - ent = ent.Transform.Parent?.Owner; + ent = ent.Transform.Parent?.Owner!; } else { diff --git a/Robust.Shared/Network/Messages/MsgEntity.cs b/Robust.Shared/Network/Messages/MsgEntity.cs index bba9fd127..7890f8f84 100644 --- a/Robust.Shared/Network/Messages/MsgEntity.cs +++ b/Robust.Shared/Network/Messages/MsgEntity.cs @@ -42,11 +42,9 @@ namespace Robust.Shared.Network.Messages case EntityMessageType.SystemMessage: { var serializer = IoCManager.Resolve(); - int messageLength = buffer.ReadInt32(); - using (var stream = new MemoryStream(buffer.ReadBytes(messageLength))) - { - SystemMessage = serializer.Deserialize(stream); - } + int length = buffer.ReadInt32(); + using var stream = buffer.ReadAsStream(length); + SystemMessage = serializer.Deserialize(stream); } break; @@ -56,11 +54,9 @@ namespace Robust.Shared.Network.Messages NetId = buffer.ReadUInt32(); var serializer = IoCManager.Resolve(); - int messageLength = buffer.ReadInt32(); - using (var stream = new MemoryStream(buffer.ReadBytes(messageLength))) - { - ComponentMessage = serializer.Deserialize(stream); - } + int length = buffer.ReadInt32(); + using var stream = buffer.ReadAsStream(length); + ComponentMessage = serializer.Deserialize(stream); } break; } @@ -188,6 +184,7 @@ namespace Robust.Shared.Network.Messages } } +#if false private List UnPackParams(NetIncomingMessage message) { var messageParams = new List(); @@ -236,12 +233,15 @@ namespace Robust.Shared.Network.Messages break; case NetworkDataType.d_byteArray: int length = message.ReadInt32(); - messageParams.Add(message.ReadBytes(length)); + var buf = new byte[length]; + message.ReadBytes(buf); + messageParams.Add(buf) break; } } return messageParams; } +#endif #endregion Parameter Packing diff --git a/Robust.Shared/Network/Messages/MsgScriptResponse.cs b/Robust.Shared/Network/Messages/MsgScriptResponse.cs index 0057159b8..1ed5a34d9 100644 --- a/Robust.Shared/Network/Messages/MsgScriptResponse.cs +++ b/Robust.Shared/Network/Messages/MsgScriptResponse.cs @@ -39,11 +39,9 @@ namespace Robust.Shared.Network.Messages var serializer = IoCManager.Resolve(); var length = buffer.ReadVariableInt32(); - var stateData = buffer.ReadBytes(length); - - using var memoryStream = new MemoryStream(stateData); - Echo = serializer.Deserialize(memoryStream); - Response = serializer.Deserialize(memoryStream); + using var stream = buffer.ReadAsStream(length); + Echo = serializer.Deserialize(stream); + Response = serializer.Deserialize(stream); } } diff --git a/Robust.Shared/Network/Messages/MsgState.cs b/Robust.Shared/Network/Messages/MsgState.cs index b377afdf8..f85b1dac8 100644 --- a/Robust.Shared/Network/Messages/MsgState.cs +++ b/Robust.Shared/Network/Messages/MsgState.cs @@ -37,12 +37,9 @@ namespace Robust.Shared.Network.Messages { MsgSize = buffer.LengthBytes; var length = buffer.ReadVariableInt32(); - var stateData = buffer.ReadBytes(length); - using (var stateStream = new MemoryStream(stateData)) - { - var serializer = IoCManager.Resolve(); - State = serializer.Deserialize(stateStream); - } + using var stream = buffer.ReadAsStream(length); + var serializer = IoCManager.Resolve(); + State = serializer.Deserialize(stream); State.PayloadSize = length; } diff --git a/Robust.Shared/Network/Messages/MsgViewVariablesModifyRemote.cs b/Robust.Shared/Network/Messages/MsgViewVariablesModifyRemote.cs index ca966ab0a..ddb8ffeea 100644 --- a/Robust.Shared/Network/Messages/MsgViewVariablesModifyRemote.cs +++ b/Robust.Shared/Network/Messages/MsgViewVariablesModifyRemote.cs @@ -42,19 +42,13 @@ namespace Robust.Shared.Network.Messages SessionId = buffer.ReadUInt32(); { var length = buffer.ReadInt32(); - var bytes = buffer.ReadBytes(length); - using (var stream = new MemoryStream(bytes)) - { - PropertyIndex = serializer.Deserialize(stream); - } + using var stream = buffer.ReadAsStream(length); + PropertyIndex = serializer.Deserialize(stream); } { var length = buffer.ReadInt32(); - var bytes = buffer.ReadBytes(length); - using (var stream = new MemoryStream(bytes)) - { - Value = serializer.Deserialize(stream); - } + using var stream = buffer.ReadAsStream(length); + Value = serializer.Deserialize(stream); } } diff --git a/Robust.Shared/Network/Messages/MsgViewVariablesRemoteData.cs b/Robust.Shared/Network/Messages/MsgViewVariablesRemoteData.cs index e0e7591e2..c5d84c407 100644 --- a/Robust.Shared/Network/Messages/MsgViewVariablesRemoteData.cs +++ b/Robust.Shared/Network/Messages/MsgViewVariablesRemoteData.cs @@ -41,11 +41,8 @@ namespace Robust.Shared.Network.Messages RequestId = buffer.ReadUInt32(); var serializer = IoCManager.Resolve(); var length = buffer.ReadInt32(); - var bytes = buffer.ReadBytes(length); - using (var stream = new MemoryStream(bytes)) - { - Blob = serializer.Deserialize(stream); - } + using var stream = buffer.ReadAsStream(length); + Blob = serializer.Deserialize(stream); } public override void WriteToBuffer(NetOutgoingMessage buffer) diff --git a/Robust.Shared/Network/Messages/MsgViewVariablesReqData.cs b/Robust.Shared/Network/Messages/MsgViewVariablesReqData.cs index b355897e9..2add4282d 100644 --- a/Robust.Shared/Network/Messages/MsgViewVariablesReqData.cs +++ b/Robust.Shared/Network/Messages/MsgViewVariablesReqData.cs @@ -47,11 +47,8 @@ namespace Robust.Shared.Network.Messages SessionId = buffer.ReadUInt32(); var serializer = IoCManager.Resolve(); var length = buffer.ReadInt32(); - var bytes = buffer.ReadBytes(length); - using (var stream = new MemoryStream(bytes)) - { - RequestMeta = serializer.Deserialize(stream); - } + using var stream = buffer.ReadAsStream(length); + RequestMeta = serializer.Deserialize(stream); } public override void WriteToBuffer(NetOutgoingMessage buffer) @@ -68,4 +65,3 @@ namespace Robust.Shared.Network.Messages } } } - diff --git a/Robust.Shared/Network/Messages/MsgViewVariablesReqSession.cs b/Robust.Shared/Network/Messages/MsgViewVariablesReqSession.cs index 6c00d08e7..61b372267 100644 --- a/Robust.Shared/Network/Messages/MsgViewVariablesReqSession.cs +++ b/Robust.Shared/Network/Messages/MsgViewVariablesReqSession.cs @@ -43,11 +43,8 @@ namespace Robust.Shared.Network.Messages RequestId = buffer.ReadUInt32(); var serializer = IoCManager.Resolve(); var length = buffer.ReadInt32(); - var bytes = buffer.ReadBytes(length); - using (var stream = new MemoryStream(bytes)) - { - Selector = serializer.Deserialize(stream); - } + using var stream = buffer.ReadAsStream(length); + Selector = serializer.Deserialize(stream); } public override void WriteToBuffer(NetOutgoingMessage buffer) diff --git a/Robust.Shared/Serialization/Messages/MsgRobustMappedStringsSerializerServerHandshake.cs b/Robust.Shared/Serialization/Messages/MsgRobustMappedStringsSerializerServerHandshake.cs index 7ab23887b..1ec2a40ee 100644 --- a/Robust.Shared/Serialization/Messages/MsgRobustMappedStringsSerializerServerHandshake.cs +++ b/Robust.Shared/Serialization/Messages/MsgRobustMappedStringsSerializerServerHandshake.cs @@ -6,6 +6,7 @@ using Robust.Shared.Network; namespace Robust.Shared.Serialization { + /// /// The server part of the string-exchange handshake. Sent as the /// first message in the handshake. Tells the client the hash of @@ -35,7 +36,7 @@ namespace Robust.Shared.Serialization throw new InvalidOperationException("Hash too long."); } - Hash = buffer.ReadBytes(len); + buffer.ReadBytes(Hash = new byte[len]); } public override void WriteToBuffer(NetOutgoingMessage buffer) diff --git a/Robust.Shared/Serialization/Messages/MsgRobustMappedStringsSerializerStrings.cs b/Robust.Shared/Serialization/Messages/MsgRobustMappedStringsSerializerStrings.cs index c4eb187a4..26c59358c 100644 --- a/Robust.Shared/Serialization/Messages/MsgRobustMappedStringsSerializerStrings.cs +++ b/Robust.Shared/Serialization/Messages/MsgRobustMappedStringsSerializerStrings.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.IO; using JetBrains.Annotations; using Lidgren.Network; @@ -7,6 +8,7 @@ using Robust.Shared.Network; namespace Robust.Shared.Serialization { + /// /// The meat of the string-exchange handshake sandwich. Sent by the /// server after the client requests an updated copy of the mapping. @@ -22,6 +24,8 @@ namespace Robust.Shared.Serialization { } + public int PackageSize { get; set; } + /// /// The raw bytes of the string mapping held by the server. /// @@ -29,14 +33,8 @@ namespace Robust.Shared.Serialization public override void ReadFromBuffer(NetIncomingMessage buffer) { - var l = buffer.ReadVariableInt32(); - var success = buffer.ReadBytes(l, out var buf); - if (!success) - { - throw new InvalidDataException("Not all of the bytes were available in the message."); - } - - Package = buf; + PackageSize = buffer.ReadVariableInt32(); + buffer.ReadBytes(Package = new byte[PackageSize]); } public override void WriteToBuffer(NetOutgoingMessage buffer)