]> git.deb.at Git - rhonda/impostor.hazel.git/commitdiff
Get rid of the dependency on Impostor.Api
authorjs6pak <kubastaron@hotmail.com>
Sun, 18 Dec 2022 13:59:22 +0000 (14:59 +0100)
committerjs6pak <kubastaron@hotmail.com>
Sun, 18 Dec 2022 18:43:35 +0000 (19:43 +0100)
21 files changed:
Hazel.sln
Hazel/Connection.cs
Hazel/ConnectionListener.cs
Hazel/DataReceivedEventArgs.cs
Hazel/Hazel.csproj
Hazel/MessageReader.cs
Hazel/MessageWriter.cs
Hazel/NewConnectionEventArgs.cs
Hazel/Udp/UdpClientConnection.cs
Hazel/Udp/UdpConnection.Reliable.cs
Hazel/Udp/UdpConnection.cs
Hazel/Udp/UdpServerConnection.cs
Impostor.Hazel.Abstractions/IMessageReader.cs [new file with mode: 0644]
Impostor.Hazel.Abstractions/IMessageWriter.cs [new file with mode: 0644]
Impostor.Hazel.Abstractions/IMessageWriterProvider.cs [new file with mode: 0644]
Impostor.Hazel.Abstractions/Impostor.Hazel.Abstractions.csproj [new file with mode: 0644]
Impostor.Hazel.Abstractions/MessageType.cs [new file with mode: 0644]
Impostor.Hazel.Tests/Impostor.Hazel.Tests.csproj [new file with mode: 0644]
Impostor.Hazel.Tests/MessageReaderTests.cs [new file with mode: 0644]
Impostor.Hazel.Tests/MessageWriterTests.cs [new file with mode: 0644]
Impostor.Hazel.Tests/Usings.cs [new file with mode: 0644]

index e18d32f23d3c004e6bd3fa425a1bb212e237d9b0..20d3480111f72661085a86e90e482f38aa371d72 100644 (file)
--- a/Hazel.sln
+++ b/Hazel.sln
@@ -5,7 +5,9 @@ VisualStudioVersion = 16.0.30523.141
 MinimumVisualStudioVersion = 10.0.40219.1
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hazel", "Hazel\Hazel.csproj", "{02CFBD30-D77D-400F-94B2-700F60EFDD7F}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Api", "..\Impostor.Api\Impostor.Api.csproj", "{9932ED5B-4B76-451D-84C6-BE060DF1FD20}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Hazel.Abstractions", "Impostor.Hazel.Abstractions\Impostor.Hazel.Abstractions.csproj", "{51FD3233-44D5-43BC-A618-A381703EDAD3}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Hazel.Tests", "Impostor.Hazel.Tests\Impostor.Hazel.Tests.csproj", "{6CC1050D-4142-454E-BA09-974DF77D2C10}"
 EndProject
 Global
        GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -17,10 +19,14 @@ Global
                {02CFBD30-D77D-400F-94B2-700F60EFDD7F}.Debug|Any CPU.Build.0 = Debug|Any CPU
                {02CFBD30-D77D-400F-94B2-700F60EFDD7F}.Release|Any CPU.ActiveCfg = Release|Any CPU
                {02CFBD30-D77D-400F-94B2-700F60EFDD7F}.Release|Any CPU.Build.0 = Release|Any CPU
-               {9932ED5B-4B76-451D-84C6-BE060DF1FD20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-               {9932ED5B-4B76-451D-84C6-BE060DF1FD20}.Debug|Any CPU.Build.0 = Debug|Any CPU
-               {9932ED5B-4B76-451D-84C6-BE060DF1FD20}.Release|Any CPU.ActiveCfg = Release|Any CPU
-               {9932ED5B-4B76-451D-84C6-BE060DF1FD20}.Release|Any CPU.Build.0 = Release|Any CPU
+               {51FD3233-44D5-43BC-A618-A381703EDAD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {51FD3233-44D5-43BC-A618-A381703EDAD3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {51FD3233-44D5-43BC-A618-A381703EDAD3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {51FD3233-44D5-43BC-A618-A381703EDAD3}.Release|Any CPU.Build.0 = Release|Any CPU
+               {6CC1050D-4142-454E-BA09-974DF77D2C10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {6CC1050D-4142-454E-BA09-974DF77D2C10}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {6CC1050D-4142-454E-BA09-974DF77D2C10}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {6CC1050D-4142-454E-BA09-974DF77D2C10}.Release|Any CPU.Build.0 = Release|Any CPU
        EndGlobalSection
        GlobalSection(SolutionProperties) = preSolution
                HideSolutionNode = FALSE
index 4d99a2b83655453de5c1f4b52c1dfbbcfa5fdd60..a76878aeadba5bc52de7ad74bb01a1f58967192d 100644 (file)
@@ -1,7 +1,7 @@
 using System;
 using System.Net;
 using System.Threading.Tasks;
-using Impostor.Api.Net.Messages;
+using Impostor.Hazel.Abstractions;
 using Serilog;
 
 namespace Impostor.Hazel
index 0e231f9cab81c144b4f6ac53c0c6ed15654f7277..731fd6059909121bfbf1f7a00c6c8bfca5eec4eb 100644 (file)
@@ -1,6 +1,6 @@
 using System;
 using System.Threading.Tasks;
-using Impostor.Api.Net.Messages;
+using Impostor.Hazel.Abstractions;
 using Serilog;
 
 namespace Impostor.Hazel
index 6e7d8802810d8f2a9f4e6daf566fee994e0a200f..1322c99417700aa955992f7c32c6a956fec8e5bf 100644 (file)
@@ -1,4 +1,4 @@
-using Impostor.Api.Net.Messages;
+using Impostor.Hazel.Abstractions;
 
 namespace Impostor.Hazel
 {
index 08bb33ed545031bd538ab73ff88ee0d910d24df2..e3f17261ad8c6666632b875146f8af0d49915712 100644 (file)
@@ -5,6 +5,7 @@
     <TargetFramework>net7.0</TargetFramework>
     <DefineConstants>HAZEL_BAG</DefineConstants>
     <RootNamespace>Impostor.Hazel</RootNamespace>
+    <AssemblyName>Impostor.Hazel</AssemblyName>
   </PropertyGroup>
 
   <ItemGroup>
   </ItemGroup>
 
   <ItemGroup>
+    <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
     <PackageReference Include="Microsoft.Extensions.ObjectPool" Version="7.0.1" />
     <PackageReference Include="Serilog" Version="2.12.0" />
   </ItemGroup>
 
   <ItemGroup>
-    <ProjectReference Include="..\..\Impostor.Api\Impostor.Api.csproj" />
+    <ProjectReference Include="..\Impostor.Hazel.Abstractions\Impostor.Hazel.Abstractions.csproj" />
   </ItemGroup>
 
 </Project>
index 03b0a994f8d679086073d98d39fb3e8264cf1544..7be34d0aa1b323628b9fed2746bf545431eca431 100644 (file)
@@ -1,13 +1,8 @@
 using System;
 using System.Buffers.Binary;
-using System.Numerics;
 using System.Runtime.CompilerServices;
 using System.Text;
-using Impostor.Api;
-using Impostor.Api.Games;
-using Impostor.Api.Net.Inner;
-using Impostor.Api.Net.Messages;
-using Impostor.Api.Unity;
+using Impostor.Hazel.Abstractions;
 using Microsoft.Extensions.ObjectPool;
 
 namespace Impostor.Hazel
@@ -78,7 +73,7 @@ namespace Impostor.Hazel
         {
             if (message.Buffer != Buffer)
             {
-                throw new ImpostorProtocolException("Tried to remove message from a message that does not have the same buffer.");
+                throw new InvalidOperationException("Tried to remove message from a message that does not have the same buffer.");
             }
 
             // Offset of where to start removing.
@@ -223,13 +218,11 @@ namespace Impostor.Hazel
             return output;
         }
 
-        ///
         public int ReadPackedInt32()
         {
             return (int)this.ReadPackedUInt32();
         }
 
-        ///
         public uint ReadPackedUInt32()
         {
             bool readMore = true;
@@ -256,21 +249,6 @@ namespace Impostor.Hazel
             return output;
         }
 
-        public T ReadNetObject<T>(IGame game) where T : IInnerNetObject
-        {
-            return game.FindObjectByNetId<T>(ReadPackedUInt32());
-        }
-
-        public Vector2 ReadVector2()
-        {
-            const float range = 50f;
-
-            var x = ReadUInt16() / (float)ushort.MaxValue;
-            var y = ReadUInt16() / (float)ushort.MaxValue;
-
-            return new Vector2(Mathf.Lerp(-range, range, x), Mathf.Lerp(-range, range, y));
-        }
-
         #endregion
 
         public void CopyTo(IMessageWriter writer)
index b0377f3b70e5bbd5c953843bf44dc2f5adeb86e9..99bd91ddd8e317ea4524882a11d248ee8749223a 100644 (file)
@@ -1,12 +1,8 @@
 using System;
 using System.Collections.Generic;
 using System.Net;
-using System.Numerics;
 using System.Text;
-using Impostor.Api.Games;
-using Impostor.Api.Net.Inner;
-using Impostor.Api.Net.Messages;
-using Impostor.Api.Unity;
+using Impostor.Hazel.Abstractions;
 
 namespace Impostor.Hazel
 {
@@ -18,19 +14,18 @@ namespace Impostor.Hazel
         public MessageType SendOption { get; private set; }
 
         private Stack<int> messageStarts = new Stack<int>();
-        
+
         public MessageWriter(byte[] buffer)
         {
             this.Buffer = buffer;
             this.Length = this.Buffer.Length;
         }
 
-        ///
         public MessageWriter(int bufferSize)
         {
             this.Buffer = new byte[bufferSize];
         }
-        
+
         public byte[] Buffer { get; }
         public int Length { get; set; }
         public int Position { get; set; }
@@ -48,24 +43,23 @@ namespace Impostor.Hazel
                 switch (this.SendOption)
                 {
                     case MessageType.Reliable:
-                        {
-                            byte[] output = new byte[this.Length - 3];
-                            System.Buffer.BlockCopy(this.Buffer, 3, output, 0, this.Length - 3);
-                            return output;
-                        }
+                    {
+                        byte[] output = new byte[this.Length - 3];
+                        System.Buffer.BlockCopy(this.Buffer, 3, output, 0, this.Length - 3);
+                        return output;
+                    }
                     case MessageType.Unreliable:
-                        {
-                            byte[] output = new byte[this.Length - 1];
-                            System.Buffer.BlockCopy(this.Buffer, 1, output, 0, this.Length - 1);
-                            return output;
-                        }
+                    {
+                        byte[] output = new byte[this.Length - 1];
+                        System.Buffer.BlockCopy(this.Buffer, 1, output, 0, this.Length - 1);
+                        return output;
+                    }
                 }
             }
 
             throw new NotImplementedException();
         }
 
-        ///
         /// <param name="sendOption">The option specifying how the message should be sent.</param>
         public static MessageWriter Get(MessageType sendOption = MessageType.Unreliable)
         {
@@ -85,7 +79,6 @@ namespace Impostor.Hazel
             return this.Length > 3 + expected;
         }
 
-        ///
         public void StartMessage(byte typeFlag)
         {
             messageStarts.Push(this.Position);
@@ -93,7 +86,6 @@ namespace Impostor.Hazel
             this.Write(typeFlag);
         }
 
-        ///
         public void EndMessage()
         {
             var lastMessageStart = messageStarts.Pop();
@@ -102,7 +94,6 @@ namespace Impostor.Hazel
             this.Buffer[lastMessageStart + 1] = (byte)(length >> 8);
         }
 
-        ///
         public void CancelMessage()
         {
             this.Position = this.messageStarts.Pop();
@@ -127,7 +118,6 @@ namespace Impostor.Hazel
             }
         }
 
-        ///
         public void Recycle()
         {
             this.Position = this.Length = 0;
@@ -287,13 +277,11 @@ namespace Impostor.Hazel
             if (this.Position > this.Length) this.Length = this.Position;
         }
 
-        ///
         public void WritePacked(int value)
         {
             this.WritePacked((uint)value);
         }
 
-        ///
         public void WritePacked(uint value)
         {
             do
@@ -308,7 +296,7 @@ namespace Impostor.Hazel
                 value >>= 7;
             } while (value > 0);
         }
-        
+
         public void Write(MessageWriter msg, bool includeHeader)
         {
             int offset = 0;
@@ -333,44 +321,8 @@ namespace Impostor.Hazel
             this.Write(value.GetAddressBytes());
         }
 
-        public void Write(GameCode value)
-        {
-            this.Write(value.Value);
-        }
-
-        public void Write(IInnerNetObject innerNetObject)
-        {
-            if (innerNetObject == null)
-            {
-                this.Write(0);
-            }
-            else
-            {
-                this.WritePacked(innerNetObject.NetId);
-            }
-        }
-
-        public void Write(Vector2 vector)
-        {
-            Write((ushort)(Mathf.ReverseLerp(vector.X) * (double) ushort.MaxValue));
-            Write((ushort)(Mathf.ReverseLerp(vector.Y) * (double) ushort.MaxValue));
-        }
-
         #endregion
 
-        public unsafe static bool IsLittleEndian()
-        {
-            byte b;
-            unsafe
-            {
-                int i = 1;
-                byte* bp = (byte*)&i;
-                b = *bp;
-            }
-
-            return b == 1;
-        }
-
         public void Dispose()
         {
             Recycle();
index be9e7a214c98f43c92f192660668545c32e7497b..a50a29cabedf9ee2da7c36057fe54447af224665 100644 (file)
@@ -1,4 +1,4 @@
-using Impostor.Api.Net.Messages;
+using Impostor.Hazel.Abstractions;
 
 namespace Impostor.Hazel
 {
index 01f003f5d7c43bb04be15cdc5b13eb4910945eb5..75d7ed8c0d03547a7463ae376913c5d0c9f2727a 100644 (file)
@@ -3,7 +3,7 @@ using System.Net;
 using System.Net.Sockets;
 using System.Threading;
 using System.Threading.Tasks;
-using Impostor.Api.Net.Messages;
+using Impostor.Hazel.Abstractions;
 using Microsoft.Extensions.ObjectPool;
 
 namespace Impostor.Hazel.Udp
index 7cf38582c2e72b0b4daa975903bacd6d0d28e7f7..12b6a767ffcae37986c6e133c7199030af4f839b 100644 (file)
@@ -4,7 +4,7 @@ using System.Collections.Generic;
 using System.Diagnostics;
 using System.Threading;
 using System.Threading.Tasks;
-using Impostor.Api.Net.Messages;
+using Impostor.Hazel.Abstractions;
 
 namespace Impostor.Hazel.Udp
 {
index 5221d882e21085f063d6dc8815aa2497b7e1c88f..0c8c86353bef906976c28f883d6f511dca0d1c4c 100644 (file)
@@ -2,7 +2,7 @@ using System;
 using System.Threading;
 using System.Threading.Channels;
 using System.Threading.Tasks;
-using Impostor.Api.Net.Messages;
+using Impostor.Hazel.Abstractions;
 using Microsoft.Extensions.ObjectPool;
 using Serilog;
 
index 6511375133b4151780c204d2f74edc1635bb721a..7cc37dbda85c075c6415227b3c44299c90dfeea5 100644 (file)
@@ -1,7 +1,7 @@
 using System;
 using System.Net;
 using System.Threading.Tasks;
-using Impostor.Api.Net.Messages;
+using Impostor.Hazel.Abstractions;
 using Microsoft.Extensions.ObjectPool;
 
 namespace Impostor.Hazel.Udp
diff --git a/Impostor.Hazel.Abstractions/IMessageReader.cs b/Impostor.Hazel.Abstractions/IMessageReader.cs
new file mode 100644 (file)
index 0000000..dc2fdc9
--- /dev/null
@@ -0,0 +1,71 @@
+namespace Impostor.Hazel.Abstractions;
+
+public interface IMessageReader : IDisposable
+{
+    /// <summary>
+    ///     Gets the tag of the message.
+    /// </summary>
+    byte Tag { get; }
+
+    /// <summary>
+    ///     Gets the buffer of the message.
+    /// </summary>
+    byte[] Buffer { get; }
+
+    /// <summary>
+    ///     Gets the offset of our current <see cref="IMessageReader" /> in the entire <see cref="Buffer" />.
+    /// </summary>
+    int Offset { get; }
+
+    /// <summary>
+    ///     Gets the current position of the reader.
+    /// </summary>
+    int Position { get; }
+
+    /// <summary>
+    ///     Gets the length of the buffer.
+    /// </summary>
+    int Length { get; }
+
+    IMessageReader ReadMessage();
+
+    bool ReadBoolean();
+
+    sbyte ReadSByte();
+
+    byte ReadByte();
+
+    ushort ReadUInt16();
+
+    short ReadInt16();
+
+    uint ReadUInt32();
+
+    int ReadInt32();
+
+    ulong ReadUInt64();
+
+    long ReadInt64();
+
+    float ReadSingle();
+
+    string ReadString(int length);
+
+    string ReadString();
+
+    ReadOnlyMemory<byte> ReadBytesAndSize();
+
+    ReadOnlyMemory<byte> ReadBytes(int length);
+
+    int ReadPackedInt32();
+
+    uint ReadPackedUInt32();
+
+    void CopyTo(IMessageWriter writer);
+
+    void Seek(int position);
+
+    void RemoveMessage(IMessageReader message);
+
+    IMessageReader Copy(int offset = 0);
+}
\ No newline at end of file
diff --git a/Impostor.Hazel.Abstractions/IMessageWriter.cs b/Impostor.Hazel.Abstractions/IMessageWriter.cs
new file mode 100644 (file)
index 0000000..6b4ce34
--- /dev/null
@@ -0,0 +1,137 @@
+using System.Net;
+
+namespace Impostor.Hazel.Abstractions;
+
+/// <summary>
+///     Base message writer.
+/// </summary>
+public interface IMessageWriter : IDisposable
+{
+    public byte[] Buffer { get; }
+
+    public int Length { get; set; }
+
+    public int Position { get; set; }
+
+    public MessageType SendOption { get; }
+
+    /// <summary>
+    ///     Copies the contents of this writer into a new array.
+    /// </summary>
+    /// <param name="includeHeader">Whether to include message header in the array.</param>
+    /// <returns>An array containing the data in the current writer.</returns>
+    byte[] ToByteArray(bool includeHeader);
+
+    /// <summary>
+    ///     Writes a boolean to the message.
+    /// </summary>
+    /// <param name="value">Value to write.</param>
+    void Write(bool value);
+
+    /// <summary>
+    ///     Writes a sbyte to the message.
+    /// </summary>
+    /// <param name="value">Value to write.</param>
+    void Write(sbyte value);
+
+    /// <summary>
+    ///     Writes a byte to the message.
+    /// </summary>
+    /// <param name="value">Value to write.</param>
+    void Write(byte value);
+
+    /// <summary>
+    ///     Writes a short to the message.
+    /// </summary>
+    /// <param name="value">Value to write.</param>
+    void Write(short value);
+
+    /// <summary>
+    ///     Writes an ushort to the message.
+    /// </summary>
+    /// <param name="value">Value to write.</param>
+    void Write(ushort value);
+
+    /// <summary>
+    ///     Writes an uint to the message.
+    /// </summary>
+    /// <param name="value">Value to write.</param>
+    void Write(uint value);
+
+    /// <summary>
+    ///     Writes an int to the message.
+    /// </summary>
+    /// <param name="value">Value to write.</param>
+    void Write(int value);
+
+    /// <summary>
+    ///     Writes an ulong to the message.
+    /// </summary>
+    /// <param name="value">Value to write.</param>
+    void Write(ulong value);
+
+    /// <summary>
+    ///     Writes an ulong to the message.
+    /// </summary>
+    /// <param name="value">Value to write.</param>
+    void Write(long value);
+
+    /// <summary>
+    ///     Writes a float to the message.
+    /// </summary>
+    /// <param name="value">Value to write.</param>
+    void Write(float value);
+
+    /// <summary>
+    ///     Writes a string to the message.
+    /// </summary>
+    /// <param name="value">Value to write.</param>
+    void Write(string value);
+
+    /// <summary>
+    ///     Writes a <see cref="IPAddress" /> to the message.
+    /// </summary>
+    /// <param name="value">Value to write.</param>
+    void Write(IPAddress value);
+
+    /// <summary>
+    ///     Writes an packed int to the message.
+    /// </summary>
+    /// <param name="value">Value to write.</param>
+    void WritePacked(int value);
+
+    /// <summary>
+    ///     Writes an packed uint to the message.
+    /// </summary>
+    /// <param name="value">Value to write.</param>
+    void WritePacked(uint value);
+
+    /// <summary>
+    ///     Writes raw bytes to the message.
+    /// </summary>
+    /// <param name="data">Bytes to write.</param>
+    void Write(ReadOnlyMemory<byte> data);
+
+    void WriteBytesAndSize(byte[] bytes);
+
+    void WriteBytesAndSize(byte[] bytes, int length);
+
+    void WriteBytesAndSize(byte[] bytes, int offset, int length);
+
+    /// <summary>
+    ///     Starts a new message.
+    /// </summary>
+    /// <param name="typeFlag">Message flag header.</param>
+    void StartMessage(byte typeFlag);
+
+    /// <summary>
+    ///     Mark the end of the message.
+    /// </summary>
+    void EndMessage();
+
+    /// <summary>
+    ///     Clear the message writer.
+    /// </summary>
+    /// <param name="type">New type of the message.</param>
+    void Clear(MessageType type);
+}
\ No newline at end of file
diff --git a/Impostor.Hazel.Abstractions/IMessageWriterProvider.cs b/Impostor.Hazel.Abstractions/IMessageWriterProvider.cs
new file mode 100644 (file)
index 0000000..cbc252d
--- /dev/null
@@ -0,0 +1,15 @@
+namespace Impostor.Hazel.Abstractions;
+
+public interface IMessageWriterProvider
+{
+    /// <summary>
+    ///     Retrieves a <see cref="IMessageWriter" /> from the internal pool.
+    ///     Make sure to call <see cref="System.IDisposable.Dispose" /> when you are done!.
+    /// </summary>
+    /// <param name="sendOption">
+    ///     Whether to send the message as <see cref="MessageType.Reliable" /> or <see cref="MessageType.Unreliable" />.
+    ///     Reliable packets will ensure delivery while unreliable packets may be lost.
+    /// </param>
+    /// <returns>A <see cref="IMessageWriter" /> from the pool.</returns>
+    IMessageWriter Get(MessageType sendOption = MessageType.Unreliable);
+}
\ No newline at end of file
diff --git a/Impostor.Hazel.Abstractions/Impostor.Hazel.Abstractions.csproj b/Impostor.Hazel.Abstractions/Impostor.Hazel.Abstractions.csproj
new file mode 100644 (file)
index 0000000..6836c68
--- /dev/null
@@ -0,0 +1,9 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <TargetFramework>net7.0</TargetFramework>
+        <ImplicitUsings>enable</ImplicitUsings>
+        <Nullable>enable</Nullable>
+    </PropertyGroup>
+
+</Project>
diff --git a/Impostor.Hazel.Abstractions/MessageType.cs b/Impostor.Hazel.Abstractions/MessageType.cs
new file mode 100644 (file)
index 0000000..08cd4e5
--- /dev/null
@@ -0,0 +1,29 @@
+namespace Impostor.Hazel.Abstractions;
+
+/// <summary>
+///     Specifies how a message should be sent between connections.
+/// </summary>
+[Flags]
+public enum MessageType : byte
+{
+    /// <summary>
+    ///     Requests unreliable delivery with no fragmentation.
+    /// </summary>
+    /// <remarks>
+    ///     Sending data using unreliable delivery means that data is not guaranteed to arrive at it's destination nor is
+    ///     it guaranteed to arrive only once. However, unreliable delivery can be faster than other methods and it
+    ///     typically requires a smaller number of protocol bytes than other methods. There is also typically less
+    ///     processing involved and less memory needed as packets are not stored once sent.
+    /// </remarks>
+    Unreliable,
+
+    /// <summary>
+    ///     Requests data be sent reliably but with no fragmentation.
+    /// </summary>
+    /// <remarks>
+    ///     Sending data reliably means that data is guaranteed to arrive and to arrive only once. Reliable delivery
+    ///     typically requires more processing, more memory (as packets need to be stored in case they need resending),
+    ///     a larger number of protocol bytes and can be slower than unreliable delivery.
+    /// </remarks>
+    Reliable,
+}
\ No newline at end of file
diff --git a/Impostor.Hazel.Tests/Impostor.Hazel.Tests.csproj b/Impostor.Hazel.Tests/Impostor.Hazel.Tests.csproj
new file mode 100644 (file)
index 0000000..a5a0a3b
--- /dev/null
@@ -0,0 +1,29 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <TargetFramework>net7.0</TargetFramework>
+        <ImplicitUsings>enable</ImplicitUsings>
+        <Nullable>enable</Nullable>
+
+        <IsPackable>false</IsPackable>
+    </PropertyGroup>
+
+    <ItemGroup>
+        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
+        <PackageReference Include="xunit" Version="2.4.2" />
+        <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
+            <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+            <PrivateAssets>all</PrivateAssets>
+        </PackageReference>
+        <PackageReference Include="coverlet.collector" Version="3.1.2">
+            <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+            <PrivateAssets>all</PrivateAssets>
+        </PackageReference>
+    </ItemGroup>
+
+    <ItemGroup>
+        <ProjectReference Include="..\Hazel\Hazel.csproj" />
+        <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
+    </ItemGroup>
+
+</Project>
diff --git a/Impostor.Hazel.Tests/MessageReaderTests.cs b/Impostor.Hazel.Tests/MessageReaderTests.cs
new file mode 100644 (file)
index 0000000..efbf502
--- /dev/null
@@ -0,0 +1,360 @@
+using Impostor.Hazel.Extensions;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.ObjectPool;
+
+namespace Impostor.Hazel.Tests;
+
+public class MessageReaderTests
+{
+    private ObjectPool<MessageReader> CreateReaderPool()
+    {
+        var services = new ServiceCollection();
+        services.AddHazel();
+        return services.BuildServiceProvider().GetRequiredService<ObjectPool<MessageReader>>();
+    }
+
+    [Fact]
+    public void ReadProperInt()
+    {
+        const int Test1 = int.MaxValue;
+        const int Test2 = int.MinValue;
+
+        var msg = new MessageWriter(128);
+        msg.StartMessage(1);
+        msg.Write(Test1);
+        msg.Write(Test2);
+        msg.EndMessage();
+
+        Assert.Equal(11, msg.Length);
+        Assert.Equal(msg.Length, msg.Position);
+
+        var readerPool = CreateReaderPool();
+        var reader = readerPool.Get();
+        reader.Update(msg.Buffer);
+        Assert.Equal(byte.MaxValue, reader.Tag);
+        var message = reader.ReadMessage();
+        Assert.Equal(1, message.Tag);
+        Assert.Equal(Test1, message.ReadInt32());
+        Assert.Equal(Test2, message.ReadInt32());
+    }
+
+    [Fact]
+    public void ReadProperBool()
+    {
+        const bool Test1 = true;
+        const bool Test2 = false;
+
+        var msg = new MessageWriter(128);
+        msg.StartMessage(1);
+        msg.Write(Test1);
+        msg.Write(Test2);
+        msg.EndMessage();
+
+        Assert.Equal(5, msg.Length);
+        Assert.Equal(msg.Length, msg.Position);
+
+        var readerPool = CreateReaderPool();
+        var reader = readerPool.Get();
+        reader.Update(msg.Buffer);
+        Assert.Equal(byte.MaxValue, reader.Tag);
+        var message = reader.ReadMessage();
+        Assert.Equal(1, message.Tag);
+        Assert.Equal(Test1, message.ReadBoolean());
+        Assert.Equal(Test2, message.ReadBoolean());
+    }
+
+    [Fact]
+    public void ReadProperString()
+    {
+        const string Test1 = "Hello";
+        var Test2 = new string(' ', 1024);
+        var msg = new MessageWriter(2048);
+        msg.StartMessage(1);
+        msg.Write(Test1);
+        msg.Write(Test2);
+        msg.Write(string.Empty);
+        msg.EndMessage();
+
+        Assert.Equal(msg.Length, msg.Position);
+
+        var readerPool = CreateReaderPool();
+        var reader = readerPool.Get();
+        reader.Update(msg.Buffer);
+        Assert.Equal(byte.MaxValue, reader.Tag);
+        var message = reader.ReadMessage();
+        Assert.Equal(1, message.Tag);
+        Assert.Equal(Test1, message.ReadString());
+        Assert.Equal(Test2, message.ReadString());
+        Assert.Equal(string.Empty, message.ReadString());
+    }
+
+    [Fact]
+    public void ReadProperFloat()
+    {
+        const float Test1 = 12.34f;
+
+        var msg = new MessageWriter(2048);
+        msg.StartMessage(1);
+        msg.Write(Test1);
+        msg.EndMessage();
+
+        Assert.Equal(7, msg.Length);
+        Assert.Equal(msg.Length, msg.Position);
+
+        var readerPool = CreateReaderPool();
+        var reader = readerPool.Get();
+        reader.Update(msg.Buffer);
+        Assert.Equal(byte.MaxValue, reader.Tag);
+        var message = reader.ReadMessage();
+        Assert.Equal(1, message.Tag);
+        Assert.Equal(Test1, message.ReadSingle());
+    }
+
+    [Fact]
+    public void CopyMessage()
+    {
+        var readerPool = CreateReaderPool();
+
+        // Create message.
+        const int msgLength = 18;
+        const byte Test1 = 12;
+        const byte Test2 = 146;
+
+        var msg = new MessageWriter(2048);
+
+        msg.StartMessage(1);
+        msg.StartMessage(2);
+        msg.Write(Test1);
+        msg.Write(Test2);
+        msg.StartMessage(2);
+        msg.Write(Test1);
+        msg.Write(Test2);
+        msg.StartMessage(2);
+        msg.Write(Test1);
+        msg.Write(Test2);
+        msg.EndMessage();
+        msg.EndMessage();
+        msg.EndMessage();
+        msg.EndMessage();
+
+        // Read message.
+        using var reader = readerPool.Get();
+
+        reader.Update(msg.Buffer);
+
+        // Read first message.
+        using var messageOne = reader.ReadMessage();
+
+        Assert.Equal(1, messageOne.Tag);
+        Assert.Equal(0, messageOne.Position);
+        Assert.Equal(3, messageOne.Offset);
+        Assert.Equal(msgLength - 3, messageOne.Length);
+
+        using var messageTwo = messageOne.ReadMessage();
+
+        Assert.Equal(2, messageTwo.Tag);
+        Assert.Equal(0, messageTwo.Position);
+        Assert.Equal(6, messageTwo.Offset);
+        Assert.Equal(msgLength - 6, messageTwo.Length);
+        Assert.Equal(Test1, messageTwo.ReadByte());
+        Assert.Equal(Test2, messageTwo.ReadByte());
+
+        using var messageThree = messageTwo.ReadMessage();
+
+        Assert.Equal(2, messageThree.Tag);
+        Assert.Equal(0, messageThree.Position);
+        Assert.Equal(11, messageThree.Offset);
+        Assert.Equal(msgLength - 11, messageThree.Length);
+        Assert.Equal(Test1, messageThree.ReadByte());
+        Assert.Equal(Test2, messageThree.ReadByte());
+    }
+
+    [Fact]
+    public void CopySubMessage()
+    {
+        const byte Test1 = 12;
+        const byte Test2 = 146;
+
+        var msg = new MessageWriter(2048);
+        msg.StartMessage(1);
+
+        msg.StartMessage(2);
+        msg.Write(Test1);
+        msg.Write(Test2);
+        msg.EndMessage();
+
+        msg.EndMessage();
+
+        var readerPool = CreateReaderPool();
+        var handleReader = readerPool.Get();
+        handleReader.Update(msg.Buffer);
+        var handleMessage = handleReader.ReadMessage();
+        Assert.Equal(1, handleMessage.Tag);
+
+        using var parentReader = handleMessage.Copy();
+
+        Assert.Equal(1, parentReader.Tag);
+
+        var reader = parentReader.ReadMessage();
+
+        Assert.Equal(2, reader.Tag);
+        Assert.Equal(Test1, reader.ReadByte());
+        Assert.Equal(Test2, reader.ReadByte());
+    }
+
+    [Fact]
+    public void CopyToMessage()
+    {
+        var expected = new byte[]
+        {
+            0x2A, 0x00, 0x01, 0x27, 0x00, 0x02, 0x26, 0x54,
+            0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61,
+            0x20, 0x6C, 0x6F, 0x6E, 0x67, 0x20, 0x70, 0x61,
+            0x63, 0x6B, 0x65, 0x74, 0x20, 0x74, 0x6F, 0x20,
+            0x74, 0x65, 0x73, 0x74, 0x20, 0x63, 0x6F, 0x70,
+            0x79, 0x69, 0x6E, 0x67, 0x2E,
+        };
+
+        var readerPool = CreateReaderPool();
+
+        // Create packet.
+        var msg = new MessageWriter(2048);
+        msg.StartMessage(1);
+        msg.StartMessage(2);
+        msg.Write("This is a long packet to test copying.");
+        msg.EndMessage();
+        msg.EndMessage();
+
+        // Create a reader.
+        var reader = readerPool.Get();
+
+        reader.Update(msg.Buffer);
+
+        // Read the initial message.
+        var message = reader.ReadMessage();
+
+        // Copy the message to a new writer.
+        var writer = new MessageWriter(2048);
+
+        message.CopyTo(writer);
+
+        // Compare.
+        Assert.Equal(expected, writer.ToByteArray(true));
+    }
+
+    [Fact]
+    public void ReadMessageLength()
+    {
+        var msg = new MessageWriter(2048);
+        msg.StartMessage(1);
+        msg.Write(65534);
+        msg.StartMessage(2);
+        msg.Write("HO");
+        msg.EndMessage();
+        msg.StartMessage(2);
+        msg.Write("NO");
+        msg.EndMessage();
+        msg.EndMessage();
+
+        Assert.Equal(msg.Length, msg.Position);
+
+        var readerPool = CreateReaderPool();
+        var reader = readerPool.Get();
+        reader.Update(msg.Buffer);
+        Assert.Equal(byte.MaxValue, reader.Tag);
+        var message = reader.ReadMessage();
+        Assert.Equal(1, message.Tag);
+        Assert.Equal(65534, message.ReadInt32()); // Content
+
+        var sub = message.ReadMessage();
+        Assert.Equal(3, sub.Length);
+        Assert.Equal(2, sub.Tag);
+        Assert.Equal("HO", sub.ReadString());
+
+        sub = message.ReadMessage();
+        Assert.Equal(3, sub.Length);
+        Assert.Equal(2, sub.Tag);
+        Assert.Equal("NO", sub.ReadString());
+    }
+
+    [Fact]
+    public void RemoveMessage()
+    {
+        // Create expected message.
+        var messageExpected = new MessageWriter(1024);
+
+        messageExpected.StartMessage(0);
+        messageExpected.StartMessage(1);
+        messageExpected.Write("HiTest1");
+        messageExpected.EndMessage();
+        messageExpected.StartMessage(2);
+        messageExpected.Write("HiTest2");
+        messageExpected.EndMessage();
+        messageExpected.EndMessage();
+
+        // Create message.
+        var messageWriter = new MessageWriter(1024);
+
+        messageWriter.StartMessage(0);
+        messageWriter.StartMessage(1);
+        messageWriter.Write("HiTest1");
+        messageWriter.StartMessage(2);
+        messageWriter.Write("RemoveMe!");
+        messageWriter.EndMessage();
+        messageWriter.EndMessage();
+        messageWriter.StartMessage(2);
+        messageWriter.Write("HiTest2");
+        messageWriter.EndMessage();
+        messageWriter.EndMessage();
+
+        // Copy buffer.
+        var bufferCopy = new byte[messageWriter.Length];
+        Buffer.BlockCopy(messageWriter.Buffer, 0, bufferCopy, 0, bufferCopy.Length);
+
+        var bufferCopyTwo = new byte[messageWriter.Length];
+        Buffer.BlockCopy(messageWriter.Buffer, 0, bufferCopyTwo, 0, bufferCopyTwo.Length);
+
+        // Do the magic.
+        var readerPool = CreateReaderPool();
+        var reader = readerPool.Get();
+        reader.Update(bufferCopy);
+        var inner = reader.ReadMessage();
+
+        while (inner.Position < inner.Length)
+        {
+            var message = inner.ReadMessage();
+            if (message.Tag == 1)
+            {
+                Assert.Equal("HiTest1", message.ReadString());
+
+                var messageSub = message.ReadMessage();
+                if (messageSub.Tag == 2)
+                {
+                    Assert.Equal("RemoveMe!", messageSub.ReadString());
+
+                    // Remove this message.
+                    inner.RemoveMessage(messageSub);
+                }
+            }
+            else if (message.Tag == 2)
+            {
+                Assert.Equal("HiTest2", message.ReadString());
+            }
+            else
+            {
+                Assert.True(false, "Invalid tag was read.");
+            }
+        }
+
+        // Check if the magic was successful.
+        Assert.Equal(messageExpected.Length, reader.Length);
+        Assert.Equal(messageExpected.ToByteArray(true), reader.Buffer.Take(reader.Length).ToArray());
+
+        // Test ownership.
+        var readerTwo = readerPool.Get();
+
+        readerTwo.Update(bufferCopyTwo);
+
+        Assert.Throws<InvalidOperationException>(() => reader.RemoveMessage(readerTwo.ReadMessage()));
+    }
+}
diff --git a/Impostor.Hazel.Tests/MessageWriterTests.cs b/Impostor.Hazel.Tests/MessageWriterTests.cs
new file mode 100644 (file)
index 0000000..d96891e
--- /dev/null
@@ -0,0 +1,32 @@
+namespace Impostor.Hazel.Tests;
+
+public class MessageWriterTests
+{
+    [Fact]
+    public void ReadOnlyMemoryWriteWorksTheSameAsArray()
+    {
+        var oldVer = new MessageWriter(1024);
+        var newVer = new MessageWriter(1024);
+
+        var data = Enumerable.Repeat
+        (
+            Enumerable.Range(0, byte.MaxValue)
+                .Select(x => (byte)x),
+            2
+        ).SelectMany(x => x).ToArray();
+
+        WriteSomeData(oldVer);
+        WriteSomeData(newVer);
+
+        oldVer.Write(data);
+        newVer.Write(data.AsMemory());
+
+        Assert.True(oldVer.Buffer.AsSpan().SequenceEqual(newVer.Buffer.AsSpan()));
+
+        static void WriteSomeData(MessageWriter oldVer)
+        {
+            oldVer.WritePacked(99);
+            oldVer.WritePacked(101);
+        }
+    }
+}
diff --git a/Impostor.Hazel.Tests/Usings.cs b/Impostor.Hazel.Tests/Usings.cs
new file mode 100644 (file)
index 0000000..c802f44
--- /dev/null
@@ -0,0 +1 @@
+global using Xunit;