Skip to content

Commit af995b8

Browse files
committed
fix: NetworkBehaviour and NetworkVariableDelta length safety checks
1 parent f6a6c1f commit af995b8

File tree

10 files changed

+144
-269
lines changed

10 files changed

+144
-269
lines changed

com.unity.netcode.gameobjects/Runtime/Configuration/SessionConfig.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ internal class SessionConfig
99
public const uint BypassFeatureCompatible = 1;
1010
public const uint ServerDistributionCompatible = 2;
1111
public const uint SessionStateToken = 3;
12+
public const uint NetworkBehaviourSerializationSafety = 4;
1213

1314
// The most current session version (!!!!set this when you increment!!!!!)
14-
public static uint PackageSessionVersion => SessionStateToken;
15+
public static uint PackageSessionVersion => NetworkBehaviourSerializationSafety;
1516

1617
internal uint SessionVersion;
1718

com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs

Lines changed: 31 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1232,55 +1232,41 @@ internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClie
12321232
var networkManager = NetworkManager;
12331233
var ensureLengthSafety = networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety;
12341234

1235-
1236-
// Exit early if there are no NetworkVariables
1237-
if (NetworkVariableFields.Count == 0)
1238-
{
1239-
return;
1240-
}
1241-
1242-
for (int j = 0; j < NetworkVariableFields.Count; j++)
1235+
foreach (var field in NetworkVariableFields)
12431236
{
12441237
// Client-Server: Try to write values only for clients that have read permissions.
12451238
// Distributed Authority: All clients have read permissions, always try to write the value.
1246-
if (NetworkVariableFields[j].CanClientRead(targetClientId))
1239+
if (field.CanClientRead(targetClientId))
12471240
{
1248-
// Write additional NetworkVariable information when length safety is enabled or when in distributed authority mode
1241+
// Write additional NetworkVariable information when length safety is enabled
12491242
if (ensureLengthSafety)
12501243
{
12511244
var writePos = writer.Position;
12521245
// Note: This value can't be packed because we don't know how large it will be in advance
1253-
// we reserve space for it, then write the data, then come back and fill in the space
1254-
// to pack here, we'd have to write data to a temporary buffer and copy it in - which
1255-
// isn't worth possibly saving one byte if and only if the data is less than 63 bytes long...
1256-
// The way we do packing, any value > 63 in a ushort will use the full 2 bytes to represent.
1257-
writer.WriteValueSafe((ushort)0);
1246+
// we reserve space for it, then write the data, then come back and write the final size value
1247+
writer.WriteValueSafe(0);
12581248
var startPos = writer.Position;
1249+
12591250
// Write the NetworkVariable field value
1260-
// WriteFieldSynchronization will write the current value only if there are no pending changes.
1261-
// Otherwise, it will write the previous value if there are pending changes since the pending
1262-
// changes will be sent shortly after the client's synchronization.
1263-
NetworkVariableFields[j].WriteFieldSynchronization(writer);
1251+
field.WriteFieldSynchronization(writer);
1252+
1253+
// Write the NetworkVariable field value size
12641254
var size = writer.Position - startPos;
12651255
writer.Seek(writePos);
1266-
// Write the NetworkVariable field value size
1267-
writer.WriteValueSafe((ushort)size);
1256+
writer.WriteValueSafe(size);
12681257
writer.Seek(startPos + size);
12691258
}
1270-
else // Client-Server Only: Should only ever be invoked when using a client-server NetworkTopology
1259+
else
12711260
{
12721261
// Write the NetworkVariable field value
1273-
// WriteFieldSynchronization will write the current value only if there are no pending changes.
1274-
// Otherwise, it will write the previous value if there are pending changes since the pending
1275-
// changes will be sent shortly after the client's synchronization.
1276-
NetworkVariableFields[j].WriteFieldSynchronization(writer);
1262+
field.WriteFieldSynchronization(writer);
12771263
}
12781264
}
12791265
else if (ensureLengthSafety)
12801266
{
12811267
// Client-Server Only: If the client cannot read this field, then skip it but write a 0 for this NetworkVariable's position
12821268
{
1283-
writer.WriteValueSafe((ushort)0);
1269+
writer.WriteValueSafe(0);
12841270
}
12851271
}
12861272
}
@@ -1300,26 +1286,20 @@ internal void SetNetworkVariableData(FastBufferReader reader, ulong clientId)
13001286
var networkManager = NetworkManager;
13011287
var ensureLengthSafety = networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety;
13021288

1303-
// Exit early if nothing else to read
1304-
if (NetworkVariableFields.Count == 0)
1289+
foreach (var field in NetworkVariableFields)
13051290
{
1306-
return;
1307-
}
1308-
1309-
for (int j = 0; j < NetworkVariableFields.Count; j++)
1310-
{
1311-
var varSize = (ushort)0;
1291+
int expectedBytesToRead = 0;
13121292
var readStartPos = 0;
13131293
// Client-Server: Clients that only have read permissions will try to read the value
13141294
// Distributed Authority: All clients have read permissions, always try to read the value
1315-
if (NetworkVariableFields[j].CanClientRead(clientId))
1295+
if (field.CanClientRead(clientId))
13161296
{
13171297
if (ensureLengthSafety)
13181298
{
1319-
reader.ReadValueSafe(out varSize);
1320-
if (varSize == 0)
1299+
reader.ReadValueSafe(out expectedBytesToRead);
1300+
if (expectedBytesToRead == 0)
13211301
{
1322-
Debug.LogError($"[{name}][NetworkObjectId: {NetworkObjectId}][NetworkBehaviourId: {NetworkBehaviourId}][{NetworkVariableFields[j].Name}] Expected non-zero size readable NetworkVariable! (Skipping)");
1302+
Debug.LogError($"[{name}][NetworkObjectId: {NetworkObjectId}][NetworkBehaviourId: {NetworkBehaviourId}][{field.Name}] Expected non-zero size readable NetworkVariable! (Skipping)");
13231303
continue;
13241304
}
13251305
readStartPos = reader.Position;
@@ -1330,38 +1310,29 @@ internal void SetNetworkVariableData(FastBufferReader reader, ulong clientId)
13301310
// If skipping and length safety, then fill in a 0 size for this one spot
13311311
if (ensureLengthSafety)
13321312
{
1333-
reader.ReadValueSafe(out ushort size);
1334-
if (size != 0)
1313+
reader.ReadValueSafe(out expectedBytesToRead);
1314+
if (expectedBytesToRead != 0)
13351315
{
1336-
Debug.LogError($"[{name}][NetworkObjectId: {NetworkObjectId}][NetworkBehaviourId: {NetworkBehaviourId}][{NetworkVariableFields[j].Name}] Expected zero size for non-readable NetworkVariable when EnsureNetworkVariableLengthSafety is enabled! (Skipping)");
1316+
Debug.LogError($"[{name}][NetworkObjectId: {NetworkObjectId}][NetworkBehaviourId: {NetworkBehaviourId}][{field.Name}] Expected zero size for non-readable NetworkVariable when EnsureNetworkVariableLengthSafety is enabled! (Skipping)");
13371317
}
13381318
}
13391319
continue;
13401320
}
13411321

13421322
// Read the NetworkVariable value
1343-
NetworkVariableFields[j].ReadField(reader);
1323+
field.ReadField(reader);
13441324

13451325
// When EnsureNetworkVariableLengthSafety always do a bounds check
13461326
if (ensureLengthSafety)
13471327
{
1348-
if (reader.Position > (readStartPos + varSize))
1349-
{
1350-
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
1351-
{
1352-
NetworkLog.LogWarning($"[{name}][NetworkObjectId: {NetworkObjectId}][NetworkBehaviourId: {NetworkBehaviourId}][{NetworkVariableFields[j].Name}] NetworkVariable data read too big. {reader.Position - (readStartPos + varSize)} bytes.");
1353-
}
1354-
1355-
reader.Seek(readStartPos + varSize);
1356-
}
1357-
else if (reader.Position < (readStartPos + varSize))
1328+
var totalBytesRead = reader.Position - readStartPos;
1329+
if (totalBytesRead != expectedBytesToRead)
13581330
{
1359-
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
1331+
if (NetworkManager.LogLevel <= LogLevel.Normal)
13601332
{
1361-
NetworkLog.LogWarning($"[{name}][NetworkObjectId: {NetworkObjectId}][NetworkBehaviourId: {NetworkBehaviourId}][{NetworkVariableFields[j].Name}] NetworkVariable data read too small. {(readStartPos + varSize) - reader.Position} bytes.");
1333+
NetworkLog.LogWarning($"[{name}][NetworkObjectId: {NetworkObjectId}][NetworkBehaviourId: {NetworkBehaviourId}][{field.Name}] NetworkVariable read {totalBytesRead} bytes but was expected to read {expectedBytesToRead} bytes during synchronization deserialization!");
13621334
}
1363-
1364-
reader.Seek(readStartPos + varSize);
1335+
reader.Seek(readStartPos + expectedBytesToRead);
13651336
}
13661337
}
13671338
}
@@ -1443,7 +1414,7 @@ internal bool Synchronize<T>(ref BufferSerializer<T> serializer, ulong targetCli
14431414
// Save our position where we will write the final size being written so we can skip over it in the
14441415
// event an exception occurs when deserializing.
14451416
var sizePosition = writer.Position;
1446-
writer.WriteValueSafe((ushort)0);
1417+
writer.WriteValueSafe(0);
14471418

14481419
// Save our position before synchronizing to determine how much was written
14491420
var positionBeforeSynchronize = writer.Position;
@@ -1481,7 +1452,7 @@ internal bool Synchronize<T>(ref BufferSerializer<T> serializer, ulong targetCli
14811452
// Write the number of bytes serialized to handle exceptions on the deserialization side
14821453
var bytesWritten = finalPosition - positionBeforeSynchronize;
14831454
writer.Seek(sizePosition);
1484-
writer.WriteValueSafe((ushort)bytesWritten);
1455+
writer.WriteValueSafe(bytesWritten);
14851456
writer.Seek(finalPosition);
14861457
}
14871458
return true;
@@ -1490,7 +1461,7 @@ internal bool Synchronize<T>(ref BufferSerializer<T> serializer, ulong targetCli
14901461
{
14911462
var reader = serializer.GetFastBufferReader();
14921463
// We will always read the expected byte count
1493-
reader.ReadValueSafe(out ushort expectedBytesToRead);
1464+
reader.ReadValueSafe(out int expectedBytesToRead);
14941465

14951466
// Save our position before we begin synchronization deserialization
14961467
var positionBeforeSynchronize = reader.Position;

com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,14 @@ internal void NetworkBehaviourUpdate(bool forceSend = false)
104104
}
105105
}
106106
}
107+
107108
// Now, reset all the no-longer-dirty variables
108-
foreach (var dirtyobj in m_DirtyNetworkObjects)
109+
foreach (var dirtyObj in m_DirtyNetworkObjects)
109110
{
110-
dirtyobj.PostNetworkVariableWrite(forceSend);
111+
foreach (var behaviour in dirtyObj.ChildNetworkBehaviours)
112+
{
113+
behaviour.PostNetworkVariableWrite(forceSend);
114+
}
111115
}
112116
m_DirtyNetworkObjects.Clear();
113117
}

com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs

Lines changed: 21 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1961,7 +1961,11 @@ public void SpawnAsPlayerObject(ulong clientId, bool destroyWithScene = false)
19611961
/// <param name="destroy">(true) the <see cref="GameObject"/> will be destroyed (false) the <see cref="GameObject"/> will persist after being despawned</param>
19621962
public void Despawn(bool destroy = true)
19631963
{
1964-
MarkVariablesDirty(false);
1964+
foreach (var behavior in ChildNetworkBehaviours)
1965+
{
1966+
behavior.MarkVariablesDirty(false);
1967+
}
1968+
19651969
NetworkManager.SpawnManager.DespawnObject(this, destroy);
19661970
}
19671971

@@ -2644,33 +2648,6 @@ internal List<NetworkBehaviour> ChildNetworkBehaviours
26442648
}
26452649
}
26462650

2647-
internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClientId)
2648-
{
2649-
if (NetworkManager.DistributedAuthorityMode)
2650-
{
2651-
writer.WriteValueSafe((ushort)ChildNetworkBehaviours.Count);
2652-
if (ChildNetworkBehaviours.Count == 0)
2653-
{
2654-
return;
2655-
}
2656-
}
2657-
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
2658-
{
2659-
var behavior = ChildNetworkBehaviours[i];
2660-
behavior.InitializeVariables();
2661-
behavior.WriteNetworkVariableData(writer, targetClientId);
2662-
}
2663-
}
2664-
2665-
internal void MarkVariablesDirty(bool dirty)
2666-
{
2667-
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
2668-
{
2669-
var behavior = ChildNetworkBehaviours[i];
2670-
behavior.MarkVariablesDirty(dirty);
2671-
}
2672-
}
2673-
26742651
/// <summary>
26752652
/// Used when changing ownership, this will mark any owner read permission base NetworkVariables as dirty
26762653
/// and will check if any owner write permission NetworkVariables are dirty (primarily for collections) so
@@ -2728,31 +2705,6 @@ internal static void VerifyParentingStatus()
27282705
}
27292706
}
27302707

2731-
/// <summary>
2732-
/// Only invoked during first synchronization of a NetworkObject (late join or newly spawned)
2733-
/// </summary>
2734-
internal bool SetNetworkVariableData(FastBufferReader reader, ulong clientId)
2735-
{
2736-
if (NetworkManager.DistributedAuthorityMode)
2737-
{
2738-
var readerPosition = reader.Position;
2739-
reader.ReadValueSafe(out ushort behaviourCount);
2740-
if (behaviourCount != ChildNetworkBehaviours.Count)
2741-
{
2742-
Debug.LogError($"[{name}] Network Behavior Count Mismatch! [In: {behaviourCount} vs Local: {ChildNetworkBehaviours.Count}][StartReaderPos: {readerPosition}] CurrentReaderPos: {reader.Position}]");
2743-
return false;
2744-
}
2745-
}
2746-
2747-
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
2748-
{
2749-
var behaviour = ChildNetworkBehaviours[i];
2750-
behaviour.InitializeVariables();
2751-
behaviour.SetNetworkVariableData(reader, clientId);
2752-
}
2753-
return true;
2754-
}
2755-
27562708
/// <summary>
27572709
/// Gets the order index of a NetworkBehaviour instance within the ChildNetworkBehaviours collection
27582710
/// </summary>
@@ -3040,14 +2992,6 @@ public void Deserialize(FastBufferReader reader)
30402992
}
30412993
}
30422994

3043-
internal void PostNetworkVariableWrite(bool forced = false)
3044-
{
3045-
for (int k = 0; k < ChildNetworkBehaviours.Count; k++)
3046-
{
3047-
ChildNetworkBehaviours[k].PostNetworkVariableWrite(forced);
3048-
}
3049-
}
3050-
30512995
/// <summary>
30522996
/// Handles synchronizing NetworkVariables and custom synchronization data for NetworkBehaviours.
30532997
/// </summary>
@@ -3059,13 +3003,20 @@ internal void SynchronizeNetworkBehaviours<T>(ref BufferSerializer<T> serializer
30593003
{
30603004
if (serializer.IsWriter)
30613005
{
3006+
// write placeholder int.
3007+
// Can't be bitpacked because we don't know the value until we calculate it later
30623008
var writer = serializer.GetFastBufferWriter();
30633009
var positionBeforeSynchronizing = writer.Position;
3064-
writer.WriteValueSafe((ushort)0);
3010+
writer.WriteValueSafe(0);
30653011
var sizeToSkipCalculationPosition = writer.Position;
30663012

30673013
// Synchronize NetworkVariables
3068-
WriteNetworkVariableData(writer, targetClientId);
3014+
foreach (var behavior in ChildNetworkBehaviours)
3015+
{
3016+
behavior.InitializeVariables();
3017+
behavior.WriteNetworkVariableData(writer, targetClientId);
3018+
}
3019+
30693020
// Reserve the NetworkBehaviour synchronization count position
30703021
var networkBehaviourCountPosition = writer.Position;
30713022
writer.WriteValueSafe((byte)0);
@@ -3087,7 +3038,7 @@ internal void SynchronizeNetworkBehaviours<T>(ref BufferSerializer<T> serializer
30873038
// synchronization.
30883039
writer.Seek(positionBeforeSynchronizing);
30893040
// We want the size of everything after our size to skip calculation position
3090-
var size = (ushort)(currentPosition - sizeToSkipCalculationPosition);
3041+
var size = currentPosition - sizeToSkipCalculationPosition;
30913042
writer.WriteValueSafe(size);
30923043
// Write the number of NetworkBehaviours synchronized
30933044
writer.Seek(networkBehaviourCountPosition);
@@ -3102,26 +3053,25 @@ internal void SynchronizeNetworkBehaviours<T>(ref BufferSerializer<T> serializer
31023053
var reader = serializer.GetFastBufferReader();
31033054
try
31043055
{
3105-
reader.ReadValueSafe(out ushort sizeOfSynchronizationData);
3056+
reader.ReadValueSafe(out int sizeOfSynchronizationData);
31063057
seekToEndOfSynchData = reader.Position + sizeOfSynchronizationData;
3058+
31073059
// Apply the network variable synchronization data
3108-
if (!SetNetworkVariableData(reader, targetClientId))
3060+
foreach (var behaviour in ChildNetworkBehaviours)
31093061
{
3110-
reader.Seek(seekToEndOfSynchData);
3111-
return;
3062+
behaviour.InitializeVariables();
3063+
behaviour.SetNetworkVariableData(reader, targetClientId);
31123064
}
31133065

31143066
// Read the number of NetworkBehaviours to synchronize
31153067
reader.ReadValueSafe(out byte numberSynchronized);
31163068

3117-
var networkBehaviourId = (ushort)0;
3118-
31193069
// If a NetworkBehaviour writes synchronization data, it will first
31203070
// write its NetworkBehaviourId so when deserializing the client-side
31213071
// can find the right NetworkBehaviour to deserialize the synchronization data.
31223072
for (int i = 0; i < numberSynchronized; i++)
31233073
{
3124-
reader.ReadValueSafe(out networkBehaviourId);
3074+
reader.ReadValueSafe(out ushort networkBehaviourId);
31253075
var networkBehaviour = GetNetworkBehaviourAtOrderIndex(networkBehaviourId);
31263076
networkBehaviour.Synchronize(ref serializer, targetClientId);
31273077
}

0 commit comments

Comments
 (0)