Skip to content

Commit 67b27b7

Browse files
fix: Add size validation to named and unnamed message sending (#3043)
* Add better error messages with too big named message sizes * also validate unnamed message sizes * add to changelog * fix pr number * Update CHANGELOG.md * styles adding whitespace --------- Co-authored-by: Noel Stephens <noel.stephens@unity3d.com>
1 parent f41fb30 commit 67b27b7

File tree

6 files changed

+122
-3
lines changed

6 files changed

+122
-3
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
1010

1111
### Added
1212

13+
- Added message size validation to named and unnamed message sending functions for better error messages. (#3043)
1314
- Added "Check for NetworkObject Component" property to the Multiplayer->Netcode for GameObjects project settings. When disabled, this will bypass the in-editor `NetworkObject` check on `NetworkBehaviour` components. (#3034)
1415

1516
### Fixed

com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ public void SendUnnamedMessage(IReadOnlyList<ulong> clientIds, FastBufferWriter
7373
throw new ArgumentNullException(nameof(clientIds), "You must pass in a valid clientId List");
7474
}
7575

76+
ValidateMessageSize(messageBuffer, networkDelivery, isNamed: false);
77+
7678
if (m_NetworkManager.IsHost)
7779
{
7880
for (var i = 0; i < clientIds.Count; ++i)
@@ -108,6 +110,8 @@ public void SendUnnamedMessage(IReadOnlyList<ulong> clientIds, FastBufferWriter
108110
/// <param name="networkDelivery">The delivery type (QoS) to send data with</param>
109111
public void SendUnnamedMessage(ulong clientId, FastBufferWriter messageBuffer, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced)
110112
{
113+
ValidateMessageSize(messageBuffer, networkDelivery, isNamed: false);
114+
111115
if (m_NetworkManager.IsHost)
112116
{
113117
if (clientId == m_NetworkManager.LocalClientId)
@@ -263,6 +267,8 @@ public void SendNamedMessageToAll(string messageName, FastBufferWriter messageSt
263267
/// <param name="networkDelivery">The delivery type (QoS) to send data with</param>
264268
public void SendNamedMessage(string messageName, ulong clientId, FastBufferWriter messageStream, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced)
265269
{
270+
ValidateMessageSize(messageStream, networkDelivery, isNamed: true);
271+
266272
ulong hash = 0;
267273
switch (m_NetworkManager.NetworkConfig.RpcHashSize)
268274
{
@@ -321,6 +327,8 @@ public void SendNamedMessage(string messageName, IReadOnlyList<ulong> clientIds,
321327
throw new ArgumentNullException(nameof(clientIds), "You must pass in a valid clientId List");
322328
}
323329

330+
ValidateMessageSize(messageStream, networkDelivery, isNamed: true);
331+
324332
ulong hash = 0;
325333
switch (m_NetworkManager.NetworkConfig.RpcHashSize)
326334
{
@@ -359,5 +367,32 @@ public void SendNamedMessage(string messageName, IReadOnlyList<ulong> clientIds,
359367
m_NetworkManager.NetworkMetrics.TrackNamedMessageSent(clientIds, messageName, size);
360368
}
361369
}
370+
371+
/// <summary>
372+
/// Validate the size of the message. If it's a non-fragmented delivery type the message must fit within the
373+
/// max allowed size with headers also subtracted. Named messages also include the hash
374+
/// of the name string. Only validates in editor and development builds.
375+
/// </summary>
376+
/// <param name="messageStream">The named message payload</param>
377+
/// <param name="networkDelivery">Delivery method</param>
378+
/// <param name="isNamed">Is the message named (or unnamed)</param>
379+
/// <exception cref="OverflowException">Exception thrown in case validation fails</exception>
380+
private unsafe void ValidateMessageSize(FastBufferWriter messageStream, NetworkDelivery networkDelivery, bool isNamed)
381+
{
382+
#if DEVELOPMENT_BUILD || UNITY_EDITOR
383+
var maxNonFragmentedSize = m_NetworkManager.MessageManager.NonFragmentedMessageMaxSize - FastBufferWriter.GetWriteSize<NetworkMessageHeader>() - sizeof(NetworkBatchHeader);
384+
if (isNamed)
385+
{
386+
maxNonFragmentedSize -= sizeof(ulong); // MessageName hash
387+
}
388+
if (networkDelivery != NetworkDelivery.ReliableFragmentedSequenced
389+
&& messageStream.Length > maxNonFragmentedSize)
390+
{
391+
throw new OverflowException($"Given message size ({messageStream.Length} bytes) is greater than " +
392+
$"the maximum allowed for the selected delivery method ({maxNonFragmentedSize} bytes). Try using " +
393+
$"ReliableFragmentedSequenced delivery method instead.");
394+
}
395+
#endif
396+
}
362397
}
363398
}

com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -730,7 +730,11 @@ internal unsafe int SendPreSerializedMessage<TMessageType>(in FastBufferWriter t
730730
}
731731

732732
ref var writeQueueItem = ref sendQueueItem.ElementAt(sendQueueItem.Length - 1);
733-
writeQueueItem.Writer.TryBeginWrite(tmpSerializer.Length + headerSerializer.Length);
733+
if (!writeQueueItem.Writer.TryBeginWrite(tmpSerializer.Length + headerSerializer.Length))
734+
{
735+
Debug.LogError($"Not enough space to write message, size={tmpSerializer.Length + headerSerializer.Length} space used={writeQueueItem.Writer.Position} total size={writeQueueItem.Writer.Capacity}");
736+
continue;
737+
}
734738

735739
writeQueueItem.Writer.WriteBytes(headerSerializer.GetUnsafePtr(), headerSerializer.Length);
736740
writeQueueItem.Writer.WriteBytes(tmpSerializer.GetUnsafePtr(), tmpSerializer.Length);

com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -700,7 +700,7 @@ public unsafe void WriteBytes(byte* value, int size, int offset = 0)
700700
}
701701
if (Handle->Position + size > Handle->AllowedWriteMark)
702702
{
703-
throw new OverflowException($"Attempted to write without first calling {nameof(TryBeginWrite)}()");
703+
throw new OverflowException($"Attempted to write without first calling {nameof(TryBeginWrite)}(), Position+Size={Handle->Position + size} > AllowedWriteMark={Handle->AllowedWriteMark}");
704704
}
705705
#endif
706706
UnsafeUtility.MemCpy((Handle->BufferPointer + Handle->Position), value + offset, size);
@@ -729,7 +729,7 @@ public unsafe void WriteBytesSafe(byte* value, int size, int offset = 0)
729729

730730
if (!TryBeginWriteInternal(size))
731731
{
732-
throw new OverflowException("Writing past the end of the buffer");
732+
throw new OverflowException($"Writing past the end of the buffer, size is {size} bytes but remaining capacity is {Handle->Capacity - Handle->Position} bytes");
733733
}
734734
UnsafeUtility.MemCpy((Handle->BufferPointer + Handle->Position), value + offset, size);
735735
Handle->Position += size;

com.unity.netcode.gameobjects/Tests/Runtime/Messaging/NamedMessageTests.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,5 +239,45 @@ public void WhenSendingNamedMessageToNullClientList_ArgumentNullExceptionIsThrow
239239
});
240240
}
241241
}
242+
243+
[Test]
244+
public unsafe void ErrorMessageIsPrintedWhenAttemptingToSendNamedMessageWithTooBigBuffer()
245+
{
246+
// First try a valid send with the maximum allowed size (this is atm 1264)
247+
var msgSize = m_ServerNetworkManager.MessageManager.NonFragmentedMessageMaxSize - FastBufferWriter.GetWriteSize<NetworkMessageHeader>() - sizeof(ulong)/*MessageName hash*/ - sizeof(NetworkBatchHeader);
248+
var bufferSize = m_ServerNetworkManager.MessageManager.NonFragmentedMessageMaxSize;
249+
var messageName = Guid.NewGuid().ToString();
250+
var messageContent = new byte[msgSize];
251+
var writer = new FastBufferWriter(bufferSize, Allocator.Temp, bufferSize * 2);
252+
using (writer)
253+
{
254+
writer.TryBeginWrite(msgSize);
255+
writer.WriteBytes(messageContent, msgSize, 0);
256+
m_ServerNetworkManager.CustomMessagingManager.SendNamedMessage(messageName, new List<ulong> { FirstClient.LocalClientId }, writer);
257+
m_ServerNetworkManager.CustomMessagingManager.SendNamedMessage(messageName, FirstClient.LocalClientId, writer);
258+
}
259+
260+
msgSize++;
261+
messageContent = new byte[msgSize];
262+
writer = new FastBufferWriter(bufferSize, Allocator.Temp, bufferSize * 2);
263+
using (writer)
264+
{
265+
writer.TryBeginWrite(msgSize);
266+
writer.WriteBytes(messageContent, msgSize, 0);
267+
var message = Assert.Throws<OverflowException>(
268+
() =>
269+
{
270+
m_ServerNetworkManager.CustomMessagingManager.SendNamedMessage(messageName, new List<ulong> { FirstClient.LocalClientId }, writer);
271+
}).Message;
272+
Assert.IsTrue(message.Contains($"Given message size ({msgSize} bytes) is greater than the maximum"), $"Unexpected exception: {message}");
273+
274+
message = Assert.Throws<OverflowException>(
275+
() =>
276+
{
277+
m_ServerNetworkManager.CustomMessagingManager.SendNamedMessage(messageName, FirstClient.LocalClientId, writer);
278+
}).Message;
279+
Assert.IsTrue(message.Contains($"Given message size ({msgSize} bytes) is greater than the maximum"), $"Unexpected exception: {message}");
280+
}
281+
}
242282
}
243283
}

com.unity.netcode.gameobjects/Tests/Runtime/Messaging/UnnamedMessageTests.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,5 +194,44 @@ public void WhenSendingNamedMessageToNullClientList_ArgumentNullExceptionIsThrow
194194
});
195195
}
196196
}
197+
198+
[Test]
199+
public unsafe void ErrorMessageIsPrintedWhenAttemptingToSendUnnamedMessageWithTooBigBuffer()
200+
{
201+
// First try a valid send with the maximum allowed size (this is atm 1272)
202+
var msgSize = m_ServerNetworkManager.MessageManager.NonFragmentedMessageMaxSize - FastBufferWriter.GetWriteSize<NetworkMessageHeader>() - sizeof(NetworkBatchHeader);
203+
var bufferSize = m_ServerNetworkManager.MessageManager.NonFragmentedMessageMaxSize;
204+
var messageContent = new byte[msgSize];
205+
var writer = new FastBufferWriter(bufferSize, Allocator.Temp, bufferSize * 2);
206+
using (writer)
207+
{
208+
writer.TryBeginWrite(msgSize);
209+
writer.WriteBytes(messageContent, msgSize, 0);
210+
m_ServerNetworkManager.CustomMessagingManager.SendUnnamedMessage(new List<ulong> { FirstClient.LocalClientId }, writer);
211+
m_ServerNetworkManager.CustomMessagingManager.SendUnnamedMessage(FirstClient.LocalClientId, writer);
212+
}
213+
214+
msgSize++;
215+
messageContent = new byte[msgSize];
216+
writer = new FastBufferWriter(bufferSize, Allocator.Temp, bufferSize * 2);
217+
using (writer)
218+
{
219+
writer.TryBeginWrite(msgSize);
220+
writer.WriteBytes(messageContent, msgSize, 0);
221+
var message = Assert.Throws<OverflowException>(
222+
() =>
223+
{
224+
m_ServerNetworkManager.CustomMessagingManager.SendUnnamedMessage(new List<ulong> { FirstClient.LocalClientId }, writer);
225+
}).Message;
226+
Assert.IsTrue(message.Contains($"Given message size ({msgSize} bytes) is greater than the maximum"), $"Unexpected exception: {message}");
227+
228+
message = Assert.Throws<OverflowException>(
229+
() =>
230+
{
231+
m_ServerNetworkManager.CustomMessagingManager.SendUnnamedMessage(FirstClient.LocalClientId, writer);
232+
}).Message;
233+
Assert.IsTrue(message.Contains($"Given message size ({msgSize} bytes) is greater than the maximum"), $"Unexpected exception: {message}");
234+
}
235+
}
197236
}
198237
}

0 commit comments

Comments
 (0)