From 9f2a79152522d8bc41333808bb84369ae723d5fb Mon Sep 17 00:00:00 2001 From: Leonid Umanskiy <me@leonidumanskiy.com> Date: Tue, 30 Jan 2024 09:12:08 +0100 Subject: [PATCH] Minor refactor of the network protocol. Fixed issue when the same message could not be used as an event, request, or response at the same time. --- .../Integration/IntegrationTests.cs | 100 ++++++++++++++++++ .../Unit/Network/MessageReaderTests.cs | 84 ++++++++++++--- .../Unit/Network/MessageWriterTests.cs | 84 ++++++++++++--- .../Fenrir.Multiplayer.csproj | 2 +- .../Runtime/LiteNet/LiteNetClientPeer.cs | 7 +- .../LiteNet/LiteNetProtocolListener.cs | 2 +- .../Runtime/LiteNet/LiteNetServerPeer.cs | 2 +- .../Assets/Runtime/Network/MessageFlags.cs | 14 +-- .../Assets/Runtime/Network/MessageReader.cs | 40 ++++--- .../Assets/Runtime/Network/MessageType.cs | 27 +++-- .../Assets/Runtime/Network/MessageWrapper.cs | 21 +++- .../Assets/Runtime/Network/MessageWriter.cs | 11 +- source/UnityPackage/Assets/package.json | 2 +- 13 files changed, 311 insertions(+), 85 deletions(-) diff --git a/source/Fenrir.Multiplayer.Tests/Integration/IntegrationTests.cs b/source/Fenrir.Multiplayer.Tests/Integration/IntegrationTests.cs index cb35d1f..f22d29c 100644 --- a/source/Fenrir.Multiplayer.Tests/Integration/IntegrationTests.cs +++ b/source/Fenrir.Multiplayer.Tests/Integration/IntegrationTests.cs @@ -549,6 +549,60 @@ await Assert.ThrowsExceptionAsync<RequestTimeoutException>(async () => }); } + [TestMethod, Timeout(TestTimeout)] + public async Task NetworkClient_SendRequest_CanSendMessageThatImplementsEventRequestResponse() + { + using var logger = new TestLogger(); + using var networkServer = new NetworkServer(logger); + + TaskCompletionSource<TestMessage> requestTcs = new TaskCompletionSource<TestMessage>(); + networkServer.AddRequestHandler(new TcsRequestHandler<TestMessage>(requestTcs)); + + networkServer.Start(); + + Assert.AreEqual(ServerStatus.Running, networkServer.Status, "server is not running"); + + using var networkClient = new NetworkClient(logger); + var connectionResponse = await networkClient.Connect("http://127.0.0.1:27016"); + + Assert.AreEqual(ConnectionState.Connected, networkClient.State, "client is connected"); + Assert.IsTrue(connectionResponse.Success, "connection rejected"); + + networkClient.Peer.SendRequest(new TestMessage() { Value = "test_value" }); + + TestMessage request = await requestTcs.Task; + + Assert.AreEqual(request.Value, "test_value"); + } + + + [TestMethod, Timeout(TestTimeout)] + public async Task NetworkClient_SendRequestResponse_CanSendMessageThatImplementsEventRequestResponse() + { + using var logger = new TestLogger(); + using var networkServer = new NetworkServer(logger); + + networkServer.AddRequestHandlerAsync(new TestAsyncRequestResponseHandler<TestMessage, TestMessage>(request => + { + Assert.AreEqual("test", request.Value); + return Task.FromResult(new TestMessage() { Value = request.Value }); + })); + + networkServer.Start(); + + Assert.AreEqual(ServerStatus.Running, networkServer.Status, "server is not running"); + + using var networkClient = new NetworkClient(logger); + var connectionResponse = await networkClient.Connect("http://127.0.0.1:27016"); + + Assert.AreEqual(ConnectionState.Connected, networkClient.State, "client is not connected"); + Assert.IsTrue(connectionResponse.Success, "connection rejected"); + + var response = await networkClient.Peer.SendRequest<TestMessage, TestMessage>(new TestMessage() { Value = "test" }); + + Assert.AreEqual(response.Value, "test"); + } + [TestMethod, Timeout(TestTimeout)] public async Task NetworkServer_SendEvent_SendsEvent() { @@ -579,6 +633,37 @@ public async Task NetworkServer_SendEvent_SendsEvent() Assert.AreEqual(testEvent.Value, "event_test"); } + + [TestMethod, Timeout(TestTimeout)] + public async Task NetworkServer_SendEvent_CanSendMessageThatImplementsEventRequestResponse() + { + using var logger = new TestLogger(); + using var networkServer = new NetworkServer(logger); + + networkServer.PeerConnected += (sender, e) => + { + e.Peer.SendEvent(new TestMessage() { Value = "test" }); + }; + networkServer.Start(); + + Assert.AreEqual(ServerStatus.Running, networkServer.Status, "server is not running"); + + TaskCompletionSource<TestMessage> tcs = new TaskCompletionSource<TestMessage>(); + + using var networkClient = new NetworkClient(logger); + var eventHandler = new TestEventHandler<TestMessage>(tcs); + networkClient.AddEventHandler<TestMessage>(eventHandler); + + var connectionResponse = await networkClient.Connect("http://127.0.0.1:27016"); + + Assert.AreEqual(ConnectionState.Connected, networkClient.State, "client is not connected"); + Assert.IsTrue(connectionResponse.Success, "connection rejected"); + + var testEvent = await tcs.Task; + + Assert.AreEqual(testEvent.Value, "test"); + } + [TestMethod, Timeout(TestTimeout)] public async Task NetworkServer_Peers_IncludesConnectedPeer() { @@ -882,6 +967,21 @@ public void Serialize(IByteStreamWriter writer) } } + class TestMessage : IEvent, IRequest, IRequest<TestMessage>, IResponse, IByteStreamSerializable + { + public string Value; + + public void Deserialize(IByteStreamReader reader) + { + Value = reader.ReadString(); + } + + public void Serialize(IByteStreamWriter writer) + { + writer.Write(Value); + } + } + class TestEventHandler<TEvent> : IEventHandler<TEvent> where TEvent : IEvent { diff --git a/source/Fenrir.Multiplayer.Tests/Unit/Network/MessageReaderTests.cs b/source/Fenrir.Multiplayer.Tests/Unit/Network/MessageReaderTests.cs index 795c22c..38e2f4e 100644 --- a/source/Fenrir.Multiplayer.Tests/Unit/Network/MessageReaderTests.cs +++ b/source/Fenrir.Multiplayer.Tests/Unit/Network/MessageReaderTests.cs @@ -6,7 +6,7 @@ namespace Fenrir.Multiplayer.Tests.Unit.LiteNetProtocol public class MessageReaderTests { // Message format: - // 1. [1 byte flags] + // 1. [1 byte message type + flags] // 2. [8 bytes long message type hash] // 3. [1 byte channel number] // 4. [2 bytes short requestId] - optional, if flags has HasRequestId @@ -22,7 +22,11 @@ public void MessageReader_TryReadMessage_ReadsEvent() // Write test data var byteStreamWriter = new ByteStreamWriter(serializer); - byteStreamWriter.Write((byte)MessageFlags.IsEncrypted); // byte flags + + byte typeAndFlagsCombined = (byte)MessageType.Event; + typeAndFlagsCombined = (byte)(typeAndFlagsCombined << 5); + typeAndFlagsCombined = (byte)(typeAndFlagsCombined | (byte)MessageFlags.IsEncrypted); + byteStreamWriter.Write(typeAndFlagsCombined); // byte type + flags byteStreamWriter.Write(typeHashMap.GetTypeHash<TestEvent>()); // ulong type hash byteStreamWriter.Write((byte)123); // byte Channel number serializer.Serialize(new TestEvent() { Value = "test" }, byteStreamWriter); // byte[] data @@ -37,7 +41,6 @@ public void MessageReader_TryReadMessage_ReadsEvent() Assert.AreEqual("test", ((TestEvent)messageWrapper.MessageData).Value); Assert.AreEqual(123, messageWrapper.Channel); Assert.IsTrue(messageWrapper.Flags.HasFlag(MessageFlags.IsEncrypted)); - Assert.IsFalse(messageWrapper.Flags.HasFlag(MessageFlags.HasRequestId)); } [TestMethod] @@ -49,7 +52,10 @@ public void MessageReader_TryReadMessage_ReadsEvent_WhenEmptyData() // Write test data var byteStreamWriter = new ByteStreamWriter(serializer); - byteStreamWriter.Write((byte)MessageFlags.IsEncrypted); // byte flags + byte typeAndFlagsCombined = (byte)MessageType.Event; + typeAndFlagsCombined = (byte)(typeAndFlagsCombined << 5); + typeAndFlagsCombined = (byte)(typeAndFlagsCombined | (byte)MessageFlags.IsEncrypted); + byteStreamWriter.Write(typeAndFlagsCombined); // byte type + flags byteStreamWriter.Write(typeHashMap.GetTypeHash<TestEmptyEvent>()); // ulong type hash byteStreamWriter.Write((byte)123); // byte Channel number serializer.Serialize(new TestEmptyEvent(), byteStreamWriter); // byte[] data @@ -63,7 +69,6 @@ public void MessageReader_TryReadMessage_ReadsEvent_WhenEmptyData() Assert.IsInstanceOfType(messageWrapper.MessageData, typeof(TestEmptyEvent)); Assert.AreEqual(123, messageWrapper.Channel); Assert.IsTrue(messageWrapper.Flags.HasFlag(MessageFlags.IsEncrypted)); - Assert.IsFalse(messageWrapper.Flags.HasFlag(MessageFlags.HasRequestId)); } [TestMethod] @@ -75,10 +80,12 @@ public void MessageReader_TryReadMessage_ReadsRequest() // Write test data var byteStreamWriter = new ByteStreamWriter(serializer); - byteStreamWriter.Write((byte)(MessageFlags.IsEncrypted | MessageFlags.HasRequestId)); // byte flags + byte typeAndFlagsCombined = (byte)MessageType.Request; + typeAndFlagsCombined = (byte)(typeAndFlagsCombined << 5); + typeAndFlagsCombined = (byte)(typeAndFlagsCombined | (byte)MessageFlags.IsEncrypted); + byteStreamWriter.Write(typeAndFlagsCombined); // byte type + flags byteStreamWriter.Write(typeHashMap.GetTypeHash<TestRequest>()); // [ulong] type hash byteStreamWriter.Write((byte)123); // byte Channel number - byteStreamWriter.Write((short)456); // short Request id serializer.Serialize(new TestRequest() { Value = "test" }, byteStreamWriter); // data // Read message @@ -88,9 +95,38 @@ public void MessageReader_TryReadMessage_ReadsRequest() Assert.IsTrue(result); Assert.AreEqual(MessageType.Request, messageWrapper.MessageType); Assert.AreEqual(123, messageWrapper.Channel); + Assert.AreEqual(true, messageWrapper.Flags.HasFlag(MessageFlags.IsEncrypted)); + Assert.IsInstanceOfType(messageWrapper.MessageData, typeof(TestRequest)); + Assert.AreEqual("test", ((TestRequest)messageWrapper.MessageData).Value); + } + + [TestMethod] + public void MessageReader_TryReadMessage_ReadsRequestWithResponse() + { + var typeHashMap = new TypeHashMap(); + var serializer = new NetworkSerializer(); + var messageReader = new MessageReader(serializer, typeHashMap, new EventBasedLogger(), new RecyclableObjectPool<ByteStreamReader>(() => new ByteStreamReader(serializer))); + + // Write test data + var byteStreamWriter = new ByteStreamWriter(serializer); + byte typeAndFlagsCombined = (byte)MessageType.RequestWithResponse; + typeAndFlagsCombined = (byte)(typeAndFlagsCombined << 5); + typeAndFlagsCombined = (byte)(typeAndFlagsCombined | (byte)MessageFlags.IsEncrypted); + byteStreamWriter.Write(typeAndFlagsCombined); // byte type + flags + byteStreamWriter.Write(typeHashMap.GetTypeHash<TestRequest>()); // [ulong] type hash + byteStreamWriter.Write((byte)123); // byte Channel number + byteStreamWriter.Write((short)456); // short Request id + serializer.Serialize(new TestRequest() { Value = "test" }, byteStreamWriter); // data + + // Read message + var byteStreamReader = new ByteStreamReader(byteStreamWriter, serializer); + bool result = messageReader.TryReadMessage(byteStreamReader, out MessageWrapper messageWrapper); + + Assert.IsTrue(result); + Assert.AreEqual(MessageType.RequestWithResponse, messageWrapper.MessageType); + Assert.AreEqual(123, messageWrapper.Channel); Assert.AreEqual(456, messageWrapper.RequestId); Assert.AreEqual(true, messageWrapper.Flags.HasFlag(MessageFlags.IsEncrypted)); - Assert.AreEqual(true, messageWrapper.Flags.HasFlag(MessageFlags.HasRequestId)); Assert.IsInstanceOfType(messageWrapper.MessageData, typeof(TestRequest)); Assert.AreEqual("test", ((TestRequest)messageWrapper.MessageData).Value); } @@ -104,7 +140,10 @@ public void MessageReader_TryReadMessage_ReadsResponse() // Write test data var byteStreamWriter = new ByteStreamWriter(serializer); - byteStreamWriter.Write((byte)(MessageFlags.IsEncrypted | MessageFlags.HasRequestId)); // byte flags + byte typeAndFlagsCombined = (byte)MessageType.Response; + typeAndFlagsCombined = (byte)(typeAndFlagsCombined << 5); + typeAndFlagsCombined = (byte)(typeAndFlagsCombined | (byte)MessageFlags.IsEncrypted); + byteStreamWriter.Write(typeAndFlagsCombined); // byte type + flags byteStreamWriter.Write(typeHashMap.GetTypeHash<TestResponse>()); // [ulong] type hash byteStreamWriter.Write((byte)123); // byte Channel number byteStreamWriter.Write((short)456); // short Request id @@ -119,7 +158,6 @@ public void MessageReader_TryReadMessage_ReadsResponse() Assert.AreEqual(123, messageWrapper.Channel); Assert.AreEqual(456, messageWrapper.RequestId); Assert.AreEqual(true, messageWrapper.Flags.HasFlag(MessageFlags.IsEncrypted)); - Assert.AreEqual(true, messageWrapper.Flags.HasFlag(MessageFlags.HasRequestId)); Assert.IsInstanceOfType(messageWrapper.MessageData, typeof(TestResponse)); Assert.AreEqual("test", ((TestResponse)messageWrapper.MessageData).Value); } @@ -150,7 +188,10 @@ public void MessageReader_TryReadMessage_ReturnsFalse_IfMissingMessageTypeHash() // Write test data var byteStreamWriter = new ByteStreamWriter(serializer); - byteStreamWriter.Write((byte)(MessageFlags.IsEncrypted | MessageFlags.HasRequestId)); // byte flags + byte typeAndFlagsCombined = (byte)MessageType.Event; + typeAndFlagsCombined = (byte)(typeAndFlagsCombined << 5); + typeAndFlagsCombined = (byte)(typeAndFlagsCombined | (byte)MessageFlags.IsEncrypted); + byteStreamWriter.Write(typeAndFlagsCombined); // byte type + flags // no message type hash var byteStreamReader = new ByteStreamReader(byteStreamWriter, serializer); @@ -167,7 +208,10 @@ public void MessageReader_TryReadMessage_ReturnsFalse_IfInvalidMessageTypeHash() // Write test data var byteStreamWriter = new ByteStreamWriter(serializer); - byteStreamWriter.Write((byte)(MessageFlags.IsEncrypted | MessageFlags.HasRequestId)); // byte flags + byte typeAndFlagsCombined = (byte)MessageType.Event; + typeAndFlagsCombined = (byte)(typeAndFlagsCombined << 5); + typeAndFlagsCombined = (byte)(typeAndFlagsCombined | (byte)MessageFlags.IsEncrypted); + byteStreamWriter.Write(typeAndFlagsCombined); // byte type + flags byteStreamWriter.Write((ulong)123123); // invalid message type hash var byteStreamReader = new ByteStreamReader(byteStreamWriter, serializer); @@ -184,7 +228,10 @@ public void MessageReader_TryReadMessage_ReturnsFalse_IfMissingChannelNumber() // Write test data var byteStreamWriter = new ByteStreamWriter(serializer); - byteStreamWriter.Write((byte)(MessageFlags.IsEncrypted | MessageFlags.HasRequestId)); + byte typeAndFlagsCombined = (byte)MessageType.Event; + typeAndFlagsCombined = (byte)(typeAndFlagsCombined << 5); + typeAndFlagsCombined = (byte)(typeAndFlagsCombined | (byte)MessageFlags.IsEncrypted); + byteStreamWriter.Write(typeAndFlagsCombined); // byte type + flags byteStreamWriter.Write((ulong)typeHashMap.GetTypeHash<TestResponse>()); // missing channel number byte @@ -202,7 +249,10 @@ public void MessageReader_TryReadMessage_ReturnsFalse_IfMissingRequestId() // Write test data var byteStreamWriter = new ByteStreamWriter(serializer); - byteStreamWriter.Write((byte)(MessageFlags.IsEncrypted | MessageFlags.HasRequestId)); + byte typeAndFlagsCombined = (byte)MessageType.Request; + typeAndFlagsCombined = (byte)(typeAndFlagsCombined << 5); + typeAndFlagsCombined = (byte)(typeAndFlagsCombined | (byte)MessageFlags.IsEncrypted); + byteStreamWriter.Write(typeAndFlagsCombined); // byte type + flags byteStreamWriter.Write((ulong)typeHashMap.GetTypeHash<TestResponse>()); byteStreamWriter.Write((byte)123); // missing request id short, while HasRequestId flag is set @@ -221,7 +271,10 @@ public void MessageReader_TryReadMessage_ReadsEvent_WithDebugFlag() // Write test data var byteStreamWriter = new ByteStreamWriter(serializer); - byteStreamWriter.Write((byte)(MessageFlags.IsEncrypted | MessageFlags.IsDebug)); // byte flags + byte typeAndFlagsCombined = (byte)MessageType.Event; + typeAndFlagsCombined = (byte)(typeAndFlagsCombined << 5); + typeAndFlagsCombined = (byte)(typeAndFlagsCombined | (byte)(MessageFlags.IsEncrypted | MessageFlags.IsDebug)); + byteStreamWriter.Write(typeAndFlagsCombined); // byte type + flags byteStreamWriter.Write(typeHashMap.GetTypeHash<TestEvent>()); // ulong type hash byteStreamWriter.Write((byte)123); // byte Channel number byteStreamWriter.Write("test_debug_info_string"); @@ -237,7 +290,6 @@ public void MessageReader_TryReadMessage_ReadsEvent_WithDebugFlag() Assert.AreEqual("test", ((TestEvent)messageWrapper.MessageData).Value); Assert.AreEqual(123, messageWrapper.Channel); Assert.IsTrue(messageWrapper.Flags.HasFlag(MessageFlags.IsEncrypted)); - Assert.IsFalse(messageWrapper.Flags.HasFlag(MessageFlags.HasRequestId)); Assert.IsTrue(messageWrapper.Flags.HasFlag(MessageFlags.IsDebug)); Assert.AreEqual("test_debug_info_string", messageWrapper.DebugInfo); } diff --git a/source/Fenrir.Multiplayer.Tests/Unit/Network/MessageWriterTests.cs b/source/Fenrir.Multiplayer.Tests/Unit/Network/MessageWriterTests.cs index 485e1c8..7318598 100644 --- a/source/Fenrir.Multiplayer.Tests/Unit/Network/MessageWriterTests.cs +++ b/source/Fenrir.Multiplayer.Tests/Unit/Network/MessageWriterTests.cs @@ -7,7 +7,7 @@ namespace Fenrir.Multiplayer.Tests.Unit.LiteNetProtocol public class MessageWriterTests { // Message format: - // 1. [1 byte flags] + // 1. [1 byte message type + flags] // 2. [8 bytes long message type hash] // 3. [1 byte channel number] // 4. [2 bytes short requestId] - optional, if flags has HasRequestId @@ -29,10 +29,14 @@ public void MessageWriter_WriteMessage_WritesEvent() // Validate var byteStreamReader = new ByteStreamReader(byteStreamWriter, serializer); - // Read flags - MessageFlags flags = (MessageFlags)byteStreamReader.ReadByte(); + // Read type + flags + byte typeAndFlagsCombined = byteStreamReader.ReadByte(); + byte typeByte = (byte)(typeAndFlagsCombined >> 5); + MessageType type = (MessageType)typeByte; + Assert.AreEqual(MessageType.Event, type); + byte flagsByte = (byte)(typeAndFlagsCombined & 0b111); + MessageFlags flags = (MessageFlags)flagsByte; Assert.IsTrue(flags.HasFlag(MessageFlags.IsEncrypted)); - Assert.IsFalse(flags.HasFlag(MessageFlags.HasRequestId)); // Read hash Assert.AreEqual(typeHashMap.GetTypeHash<TestEvent>(), byteStreamReader.ReadULong()); // [ulong] type hash @@ -54,21 +58,60 @@ public void MessageWriter_WriteMessage_WritesRequest() var messageWriter = new MessageWriter(serializer, typeHashMap, new EventBasedLogger()); var byteStreamWriter = new ByteStreamWriter(serializer); - var messageWrapper = MessageWrapper.WrapRequest(new TestRequest() { Value = "test" }, 456, 123, MessageFlags.IsEncrypted | MessageFlags.HasRequestId, MessageDeliveryMethod.ReliableOrdered); + var messageWrapper = MessageWrapper.WrapRequest(new TestRequest() { Value = "test" }, 123, MessageFlags.IsEncrypted, MessageDeliveryMethod.ReliableOrdered); messageWriter.WriteMessage(byteStreamWriter, messageWrapper); // Validate var byteStreamReader = new ByteStreamReader(byteStreamWriter, serializer); - - // Read flags - MessageFlags flags = (MessageFlags)byteStreamReader.ReadByte(); + + // Read type + flags + byte typeAndFlagsCombined = byteStreamReader.ReadByte(); + byte typeByte = (byte)(typeAndFlagsCombined >> 5); + MessageType type = (MessageType)typeByte; + Assert.AreEqual(MessageType.Request, type); + byte flagsByte = (byte)(typeAndFlagsCombined & 0b111); + MessageFlags flags = (MessageFlags)flagsByte; Assert.IsTrue(flags.HasFlag(MessageFlags.IsEncrypted)); - Assert.IsTrue(flags.HasFlag(MessageFlags.HasRequestId)); // Read hash Assert.AreEqual(typeHashMap.GetTypeHash<TestRequest>(), byteStreamReader.ReadULong()); // [ulong] type hash + // Read channel id + Assert.AreEqual(123, byteStreamReader.ReadByte()); + + // Read data + var testRequest = serializer.Deserialize<TestRequest>(byteStreamReader); + + Assert.AreEqual("test", testRequest.Value); + } + + [TestMethod] + public void MessageWriter_WriteMessage_WritesRequestWithResponse() + { + var typeHashMap = new TypeHashMap(); + var serializer = new NetworkSerializer(); + var messageWriter = new MessageWriter(serializer, typeHashMap, new EventBasedLogger()); + + var byteStreamWriter = new ByteStreamWriter(serializer); + var messageWrapper = MessageWrapper.WrapRequestWithResponse(new TestRequest() { Value = "test" }, 456, 123, MessageFlags.IsEncrypted, MessageDeliveryMethod.ReliableOrdered); + + messageWriter.WriteMessage(byteStreamWriter, messageWrapper); + + // Validate + var byteStreamReader = new ByteStreamReader(byteStreamWriter, serializer); + + // Read type + flags + byte typeAndFlagsCombined = byteStreamReader.ReadByte(); + byte typeByte = (byte)(typeAndFlagsCombined >> 5); + MessageType type = (MessageType)typeByte; + Assert.AreEqual(MessageType.RequestWithResponse, type); + byte flagsByte = (byte)(typeAndFlagsCombined & 0b111); + MessageFlags flags = (MessageFlags)flagsByte; + Assert.IsTrue(flags.HasFlag(MessageFlags.IsEncrypted)); + + // Read hash + Assert.AreEqual(typeHashMap.GetTypeHash<TestRequest>(), byteStreamReader.ReadULong()); // [ulong] type hash // Read channel id Assert.AreEqual(123, byteStreamReader.ReadByte()); @@ -82,7 +125,6 @@ public void MessageWriter_WriteMessage_WritesRequest() Assert.AreEqual("test", testRequest.Value); } - [TestMethod] public void MessageWriter_WriteMessage_WritesResponse() { @@ -91,17 +133,21 @@ public void MessageWriter_WriteMessage_WritesResponse() var messageWriter = new MessageWriter(serializer, typeHashMap, new EventBasedLogger()); var byteStreamWriter = new ByteStreamWriter(serializer); - var messageWrapper = MessageWrapper.WrapResponse(new TestResponse() { Value = "test" }, 456, 123, MessageFlags.IsEncrypted | MessageFlags.HasRequestId, MessageDeliveryMethod.ReliableOrdered); + var messageWrapper = MessageWrapper.WrapResponse(new TestResponse() { Value = "test" }, 456, 123, MessageFlags.IsEncrypted, MessageDeliveryMethod.ReliableOrdered); messageWriter.WriteMessage(byteStreamWriter, messageWrapper); // Validate var byteStreamReader = new ByteStreamReader(byteStreamWriter, serializer); - // Read flags - MessageFlags flags = (MessageFlags)byteStreamReader.ReadByte(); + // Read type + flags + byte typeAndFlagsCombined = byteStreamReader.ReadByte(); + byte typeByte = (byte)(typeAndFlagsCombined >> 5); + MessageType type = (MessageType)typeByte; + Assert.AreEqual(MessageType.Response, type); + byte flagsByte = (byte)(typeAndFlagsCombined & 0b111); + MessageFlags flags = (MessageFlags)flagsByte; Assert.IsTrue(flags.HasFlag(MessageFlags.IsEncrypted)); - Assert.IsTrue(flags.HasFlag(MessageFlags.HasRequestId)); // Read hash Assert.AreEqual(typeHashMap.GetTypeHash<TestResponse>(), byteStreamReader.ReadULong()); // [ulong] type hash @@ -134,10 +180,14 @@ public void MessageWriter_WriteMessage_WritesDebugInfo_WhenIsDebugFlagSet() // Validate var byteStreamReader = new ByteStreamReader(byteStreamWriter, serializer); - // Read flags - MessageFlags flags = (MessageFlags)byteStreamReader.ReadByte(); + // Read type + flags + byte typeAndFlagsCombined = byteStreamReader.ReadByte(); + byte typeByte = (byte)(typeAndFlagsCombined >> 5); + MessageType type = (MessageType)typeByte; + Assert.AreEqual(MessageType.Event, type); + byte flagsByte = (byte)(typeAndFlagsCombined & 0b111); + MessageFlags flags = (MessageFlags)flagsByte; Assert.IsTrue(flags.HasFlag(MessageFlags.IsEncrypted)); - Assert.IsFalse(flags.HasFlag(MessageFlags.HasRequestId)); Assert.IsTrue(flags.HasFlag(MessageFlags.IsDebug)); // Read hash diff --git a/source/Fenrir.Multiplayer/Fenrir.Multiplayer.csproj b/source/Fenrir.Multiplayer/Fenrir.Multiplayer.csproj index a1f9b78..46f38ca 100644 --- a/source/Fenrir.Multiplayer/Fenrir.Multiplayer.csproj +++ b/source/Fenrir.Multiplayer/Fenrir.Multiplayer.csproj @@ -5,7 +5,7 @@ <GenerateDocumentationFile>true</GenerateDocumentationFile> <GeneratePackageOnBuild>true</GeneratePackageOnBuild> <Description>Fenrir Multiplayer Library</Description> - <Version>1.0.19</Version> + <Version>1.0.20</Version> <PackageReadmeFile>README.md</PackageReadmeFile> </PropertyGroup> diff --git a/source/UnityPackage/Assets/Runtime/LiteNet/LiteNetClientPeer.cs b/source/UnityPackage/Assets/Runtime/LiteNet/LiteNetClientPeer.cs index 579a1d0..5b71dee 100644 --- a/source/UnityPackage/Assets/Runtime/LiteNet/LiteNetClientPeer.cs +++ b/source/UnityPackage/Assets/Runtime/LiteNet/LiteNetClientPeer.cs @@ -66,11 +66,10 @@ public void SendRequest<TRequest>(TRequest request, byte channel = 0, MessageDel public void SendRequest<TRequest>(TRequest request, bool encrypted, byte channel = 0, MessageDeliveryMethod deliveryMethod = MessageDeliveryMethod.ReliableOrdered) where TRequest : IRequest { - short requestId = 0; // Requests with no response, do not require a unique id MessageFlags flags = encrypted ? MessageFlags.IsEncrypted : MessageFlags.None; flags |= GetDebugFlag(); - MessageWrapper messageWrapper = MessageWrapper.WrapRequest(request, requestId, channel, flags, deliveryMethod); + MessageWrapper messageWrapper = MessageWrapper.WrapRequest(request, channel, flags, deliveryMethod); Send(messageWrapper); } @@ -86,7 +85,7 @@ public async Task<TResponse> SendRequest<TRequest, TResponse>(TRequest request, short requestId = GetNextRequestId(); MessageDeliveryMethod deliveryMethod = ordered ? MessageDeliveryMethod.ReliableOrdered : MessageDeliveryMethod.ReliableUnordered; // Requests that require a response are always reliable - MessageFlags flags = MessageFlags.HasRequestId; + MessageFlags flags = MessageFlags.None; if (encrypted) { flags |= MessageFlags.IsEncrypted; @@ -97,7 +96,7 @@ public async Task<TResponse> SendRequest<TRequest, TResponse>(TRequest request, } flags |= GetDebugFlag(); - MessageWrapper messageWrapper = MessageWrapper.WrapRequest(request, requestId, channel, flags, deliveryMethod); + MessageWrapper messageWrapper = MessageWrapper.WrapRequestWithResponse(request, requestId, channel, flags, deliveryMethod); // Add request awaiter to a response map Task<MessageWrapper> task = _pendingRequestMap.OnSendRequest(messageWrapper); diff --git a/source/UnityPackage/Assets/Runtime/LiteNet/LiteNetProtocolListener.cs b/source/UnityPackage/Assets/Runtime/LiteNet/LiteNetProtocolListener.cs index d0c12f9..071d68a 100644 --- a/source/UnityPackage/Assets/Runtime/LiteNet/LiteNetProtocolListener.cs +++ b/source/UnityPackage/Assets/Runtime/LiteNet/LiteNetProtocolListener.cs @@ -414,7 +414,7 @@ private async Task HandleNetworkReceive(NetPeer netPeer, NetPacketReader netPack } // Dispatch message - if (messageWrapper.MessageType == MessageType.Request) + if (messageWrapper.MessageType == MessageType.Request || messageWrapper.MessageType == MessageType.RequestWithResponse) { // Request IRequest request = messageWrapper.MessageData as IRequest; diff --git a/source/UnityPackage/Assets/Runtime/LiteNet/LiteNetServerPeer.cs b/source/UnityPackage/Assets/Runtime/LiteNet/LiteNetServerPeer.cs index 52975af..01853d0 100644 --- a/source/UnityPackage/Assets/Runtime/LiteNet/LiteNetServerPeer.cs +++ b/source/UnityPackage/Assets/Runtime/LiteNet/LiteNetServerPeer.cs @@ -79,7 +79,7 @@ public void SendResponse<TResponse>(TResponse response, short requestId, bool en { MessageDeliveryMethod deliveryMethod = ordered ? MessageDeliveryMethod.ReliableOrdered : MessageDeliveryMethod.ReliableUnordered; // Responses are always reliable - MessageFlags flags = MessageFlags.HasRequestId; // Responses always have request id + MessageFlags flags = MessageFlags.None; if(encrypted) { flags |= MessageFlags.IsEncrypted; diff --git a/source/UnityPackage/Assets/Runtime/Network/MessageFlags.cs b/source/UnityPackage/Assets/Runtime/Network/MessageFlags.cs index 3fa2389..e37bfed 100644 --- a/source/UnityPackage/Assets/Runtime/Network/MessageFlags.cs +++ b/source/UnityPackage/Assets/Runtime/Network/MessageFlags.cs @@ -6,33 +6,27 @@ namespace Fenrir.Multiplayer /// Message Flags /// </summary> [Flags] - enum MessageFlags : byte + enum MessageFlags { /// <summary> /// No specific flags /// </summary> None = 0, - /// <summary> - /// Indicates if message has unique request id. - /// This is true for requests that require a response and responses. - /// </summary> - HasRequestId = 1, - /// <summary> /// Indicates if message is encrypted /// </summary> - IsEncrypted = 2, + IsEncrypted = 1, /// <summary> /// Indicates if responses should arrive in order in the selected channel /// </summary> - IsOrdered = 4, + IsOrdered = 2, /// <summary> /// If set to ture, message contains Debug information. /// This flag affects netcode performance and should be disabled in production builds. /// </summary> - IsDebug = 8, + IsDebug = 4, } } diff --git a/source/UnityPackage/Assets/Runtime/Network/MessageReader.cs b/source/UnityPackage/Assets/Runtime/Network/MessageReader.cs index 4e8b101..46ae88a 100644 --- a/source/UnityPackage/Assets/Runtime/Network/MessageReader.cs +++ b/source/UnityPackage/Assets/Runtime/Network/MessageReader.cs @@ -55,7 +55,7 @@ public bool TryReadMessage(ByteStreamReader byteStreamReader, out MessageWrapper // TODO: Encryption // Message format: - // 1. [1 byte flags] + // 1. [1 byte message type + flags] // 2. [8 bytes long message type hash] // 3. [1 byte channel number] // 4. [2 bytes short requestId] - optional, if flags has HasRequestId @@ -63,14 +63,18 @@ public bool TryReadMessage(ByteStreamReader byteStreamReader, out MessageWrapper messageWrapper = default; - // 1. byte Flags - if (!byteStreamReader.TryReadByte(out byte flagBytes)) + // 1. byte Message type + flags + if (!byteStreamReader.TryReadByte(out byte typeAndFlagsCombined)) { - _logger.Warning("Malformed message: no flags"); + _logger.Warning("Malformed message: no message type and flags"); return false; } + byte messageTypeByte = (byte)(typeAndFlagsCombined >> 5); + MessageType messageType = (MessageType)messageTypeByte; + + byte messageFlagsByte = (byte)(typeAndFlagsCombined & 0b111); // Clear front 5 bits to make sure conversion to MessageFlags works correctly + MessageFlags messageFlags = (MessageFlags)messageFlagsByte; - MessageFlags messageFlags = (MessageFlags)flagBytes; // Flags enum // 2. ulong Message type hash if (!byteStreamReader.TryReadULong(out ulong messageTypeHash)) @@ -88,7 +92,7 @@ public bool TryReadMessage(ByteStreamReader byteStreamReader, out MessageWrapper // 4. short request id short requestId = 0; - if (messageFlags.HasFlag(MessageFlags.HasRequestId)) + if (messageType == MessageType.RequestWithResponse || messageType == MessageType.Response) { if (!byteStreamReader.TryReadShort(out requestId)) { @@ -104,7 +108,7 @@ public bool TryReadMessage(ByteStreamReader byteStreamReader, out MessageWrapper debugInfo = byteStreamReader.ReadString(); } - // Find message type + // Find message data type if (!_typeHashMap.TryGetTypeByHash(messageTypeHash, out Type dataType)) { if(debugInfo != null) @@ -135,23 +139,25 @@ public bool TryReadMessage(ByteStreamReader byteStreamReader, out MessageWrapper _byteStreamReaderPool.Return(byteStreamReader); } - // Check data type - MessageType messageType; - if(messageData is IEvent) + // Validate incoming message type + if(messageType == MessageType.Request && !(messageData is IRequest)) { - messageType = MessageType.Event; + _logger.Warning($"Malformed message: message sent as Request but data type {dataType.Name} is not {nameof(IRequest)}"); + return false; } - else if(messageData is IRequest) + else if(messageType == MessageType.RequestWithResponse && !(messageData is IRequest)) { - messageType = MessageType.Request; + _logger.Warning($"Malformed message: message sent as Request but message data type {dataType.Name} is not {nameof(IRequest)}"); + return false; } - else if(messageData is IResponse) + else if(messageType == MessageType.Response && !(messageData is IResponse)) { - messageType = MessageType.Response; + _logger.Warning($"Malformed message: message sent as Response but message data type {dataType.Name} is not {nameof(IResponse)}"); + return false; } - else + else if(messageType == MessageType.Event && !(messageData is IEvent)) { - _logger.Warning("Malformed message: unknown message type {0}, must be Event, Request or Response", dataType.Name); + _logger.Warning($"Malformed message: message sent as Event but message data type {dataType.Name} is not {nameof(IEvent)}"); return false; } diff --git a/source/UnityPackage/Assets/Runtime/Network/MessageType.cs b/source/UnityPackage/Assets/Runtime/Network/MessageType.cs index e17aef9..d460048 100644 --- a/source/UnityPackage/Assets/Runtime/Network/MessageType.cs +++ b/source/UnityPackage/Assets/Runtime/Network/MessageType.cs @@ -3,25 +3,34 @@ /// <summary> /// Type of the message /// </summary> - enum MessageType : byte + enum MessageType { /// <summary> - /// Event - /// Events are sent from server to client, - /// to notify client(s) of a state change + /// Raw bytes + /// </summary> + RawBytes = 0, + + /// <summary> + /// Request with no response /// </summary> - Event, + Request = 1, /// <summary> - /// Request - /// Request are sent from client to server, and might require a response + /// Request that requires a response /// </summary> - Request, + RequestWithResponse = 2, /// <summary> /// Response /// Responses are sent back from server to client, as a result of a given request /// </summary> - Response + Response = 3, + + /// <summary> + /// Event + /// Events are sent from server to client, + /// to notify client(s) of a state change + /// </summary> + Event = 4, } } diff --git a/source/UnityPackage/Assets/Runtime/Network/MessageWrapper.cs b/source/UnityPackage/Assets/Runtime/Network/MessageWrapper.cs index 54cd84c..441c600 100644 --- a/source/UnityPackage/Assets/Runtime/Network/MessageWrapper.cs +++ b/source/UnityPackage/Assets/Runtime/Network/MessageWrapper.cs @@ -6,13 +6,13 @@ internal struct MessageWrapper { /// <summary> - /// Type of the message: Event, Request or Response + /// Type of the message: Raw, Event, Request or Response /// </summary> public MessageType MessageType; /// <summary> /// Message data object. - /// If <see cref="MessageType"/> is <see cref="MessageType.Request"/>, should be <see cref="IRequest"/>. + /// If <see cref="MessageType"/> is <see cref="MessageType.Request"/> or <see cref="MessageType.RequestWithResponse"/>, should be <see cref="IRequest"/>. /// If <see cref="MessageType"/> is <see cref="MessageType.Response"/>, should be <see cref="IResponse"/>. /// If <see cref="MessageType"/> is <see cref="MessageType.Event"/>, should be <see cref="IEvent"/>. /// </summary> @@ -102,14 +102,27 @@ public static MessageWrapper WrapEvent(IEvent data, byte channel, MessageFlags f /// Credates message wrapper for a request /// </summary> /// <param name="data">Request data. <seealso cref="MessageData"/></param> + /// <param name="channel">Channel number. <seealso cref="Channel"/></param> + /// <param name="flags">Message flags. <see cref="Flags"/></param> + /// <param name="deliveryMethod">Delivery method. <seealso cref="DeliveryMethod"/></param> + /// <returns>New MessageWrapper that wraps given request</returns> + public static MessageWrapper WrapRequest(IRequest data, byte channel, MessageFlags flags, MessageDeliveryMethod deliveryMethod) + { + return new MessageWrapper(MessageType.Request, data, 0, channel, flags, deliveryMethod, null); + } + + /// <summary> + /// Credates message wrapper for a request with response + /// </summary> + /// <param name="data">Request data. <seealso cref="MessageData"/></param> /// <param name="requestId">Request id. <seealso cref="RequestId"/></param> /// <param name="channel">Channel number. <seealso cref="Channel"/></param> /// <param name="flags">Message flags. <see cref="Flags"/></param> /// <param name="deliveryMethod">Delivery method. <seealso cref="DeliveryMethod"/></param> /// <returns>New MessageWrapper that wraps given request</returns> - public static MessageWrapper WrapRequest(IRequest data, short requestId, byte channel, MessageFlags flags, MessageDeliveryMethod deliveryMethod) + public static MessageWrapper WrapRequestWithResponse(IRequest data, short requestId, byte channel, MessageFlags flags, MessageDeliveryMethod deliveryMethod) { - return new MessageWrapper(MessageType.Request, data, requestId, channel, flags, deliveryMethod, null); + return new MessageWrapper(MessageType.RequestWithResponse, data, requestId, channel, flags, deliveryMethod, null); } /// <summary> diff --git a/source/UnityPackage/Assets/Runtime/Network/MessageWriter.cs b/source/UnityPackage/Assets/Runtime/Network/MessageWriter.cs index a6e6bcb..8507777 100644 --- a/source/UnityPackage/Assets/Runtime/Network/MessageWriter.cs +++ b/source/UnityPackage/Assets/Runtime/Network/MessageWriter.cs @@ -44,14 +44,17 @@ public void WriteMessage(ByteStreamWriter byteStreamWriter, MessageWrapper messa // TODO: Encryption // Message format: - // 1. [1 byte flags] + // 1. [1 byte message type + flags] // 2. [8 bytes long message type hash] // 3. [1 byte channel number] // 4. [2 bytes short requestId] - optional, if flags has HasRequestId // 5. [N bytes serialized message] - // 1. byte Message flags - byteStreamWriter.Write((byte)messageWrapper.Flags); + // 1. byte Message type + flags + byte typeAndFlagsCombined = (byte)messageWrapper.MessageType; + typeAndFlagsCombined = (byte)(typeAndFlagsCombined << 5); + typeAndFlagsCombined = (byte)(typeAndFlagsCombined | (byte)messageWrapper.Flags); + byteStreamWriter.Write(typeAndFlagsCombined); // 2. ulong Message type hash ulong messageTypeHash = _typeHashMap.GetTypeHash(messageWrapper.MessageData.GetType()); @@ -61,7 +64,7 @@ public void WriteMessage(ByteStreamWriter byteStreamWriter, MessageWrapper messa byteStreamWriter.Write(messageWrapper.Channel); // 4. short Request id - if (messageWrapper.Flags.HasFlag(MessageFlags.HasRequestId)) + if (messageWrapper.MessageType == MessageType.RequestWithResponse || messageWrapper.MessageType == MessageType.Response) { byteStreamWriter.Write(messageWrapper.RequestId); } diff --git a/source/UnityPackage/Assets/package.json b/source/UnityPackage/Assets/package.json index cacb37d..640e1c4 100644 --- a/source/UnityPackage/Assets/package.json +++ b/source/UnityPackage/Assets/package.json @@ -3,7 +3,7 @@ "displayName": "Fenrir Multiplayer", "description": "Library for building multiplayer games with using Fenrir Multiplayer Platform", "license": "MIT", - "version": "1.0.19", + "version": "1.0.20", "author": { "name": "Fenrir", "email": "info@fenrirserver.com",