using System; using System.Collections.Generic; using Unity.Collections; namespace Unity.Netcode { /// /// This particular struct is a little weird because it doesn't actually contain the data /// it's serializing. Instead, it contains references to the data it needs to do the /// serialization. This is due to the generally amorphous nature of network variable /// deltas, since they're all driven by custom virtual method overloads. /// internal struct NetworkVariableDeltaMessage : INetworkMessage { public ulong NetworkObjectId; public ushort NetworkBehaviourIndex; public HashSet DeliveryMappedNetworkVariableIndex; public ulong ClientId; public NetworkBehaviour NetworkBehaviour; public void Serialize(FastBufferWriter writer) { if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(NetworkObjectId) + FastBufferWriter.GetWriteSize(NetworkBehaviourIndex))) { throw new OverflowException( $"Not enough space in the buffer to write {nameof(NetworkVariableDeltaMessage)}"); } writer.WriteValue(NetworkObjectId); writer.WriteValue(NetworkBehaviourIndex); for (int k = 0; k < NetworkBehaviour.NetworkVariableFields.Count; k++) { if (!DeliveryMappedNetworkVariableIndex.Contains(k)) { // This var does not belong to the currently iterating delivery group. if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) { writer.WriteValueSafe((short)0); } else { writer.WriteValueSafe(false); } continue; } // if I'm dirty AND a client, write (server always has all permissions) // if I'm dirty AND the server AND the client can read me, send. bool shouldWrite = NetworkBehaviour.NetworkVariableFields[k].ShouldWrite(ClientId, NetworkBehaviour.NetworkManager.IsServer); if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) { if (!shouldWrite) { writer.WriteValueSafe((ushort)0); } } else { writer.WriteValueSafe(shouldWrite); } if (shouldWrite) { if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) { var tmpWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, short.MaxValue); NetworkBehaviour.NetworkVariableFields[k].WriteDelta(tmpWriter); writer.WriteValueSafe((ushort)tmpWriter.Length); tmpWriter.CopyTo(writer); } else { NetworkBehaviour.NetworkVariableFields[k].WriteDelta(writer); } if (!NetworkBehaviour.NetworkVariableIndexesToResetSet.Contains(k)) { NetworkBehaviour.NetworkVariableIndexesToResetSet.Add(k); NetworkBehaviour.NetworkVariableIndexesToReset.Add(k); } NetworkBehaviour.NetworkManager.NetworkMetrics.TrackNetworkVariableDeltaSent( ClientId, NetworkBehaviour.NetworkObject, NetworkBehaviour.NetworkVariableFields[k].Name, NetworkBehaviour.__getTypeName(), writer.Length); } } } public static void Receive(FastBufferReader reader, in NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; var message = new NetworkVariableDeltaMessage(); if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(message.NetworkObjectId) + FastBufferWriter.GetWriteSize(message.NetworkBehaviourIndex))) { throw new OverflowException( $"Not enough data in the buffer to read {nameof(NetworkVariableDeltaMessage)}"); } reader.ReadValue(out message.NetworkObjectId); reader.ReadValue(out message.NetworkBehaviourIndex); message.Handle(context.SenderId, reader, context, networkManager); } public void Handle(ulong senderId, FastBufferReader reader, in NetworkContext context, NetworkManager networkManager) { if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out NetworkObject networkObject)) { NetworkBehaviour behaviour = networkObject.GetNetworkBehaviourAtOrderIndex(NetworkBehaviourIndex); if (behaviour == null) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { NetworkLog.LogWarning($"Network variable delta message received for a non-existent behaviour. {nameof(NetworkObjectId)}: {NetworkObjectId}, {nameof(NetworkBehaviourIndex)}: {NetworkBehaviourIndex}"); } } else { for (int i = 0; i < behaviour.NetworkVariableFields.Count; i++) { ushort varSize = 0; if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) { reader.ReadValueSafe(out varSize); if (varSize == 0) { continue; } } else { reader.ReadValueSafe(out bool deltaExists); if (!deltaExists) { continue; } } if (networkManager.IsServer) { // we are choosing not to fire an exception here, because otherwise a malicious client could use this to crash the server if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { NetworkLog.LogWarning($"Client wrote to {typeof(NetworkVariable<>).Name} without permission. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}"); NetworkLog.LogError($"[{behaviour.NetworkVariableFields[i].GetType().Name}]"); } reader.Seek(reader.Position + varSize); continue; } //This client wrote somewhere they are not allowed. This is critical //We can't just skip this field. Because we don't actually know how to dummy read //That is, we don't know how many bytes to skip. Because the interface doesn't have a //Read that gives us the value. Only a Read that applies the value straight away //A dummy read COULD be added to the interface for this situation, but it's just being too nice. //This is after all a developer fault. A critical error should be fine. // - TwoTen if (NetworkLog.CurrentLogLevel <= LogLevel.Error) { NetworkLog.LogError($"Client wrote to {typeof(NetworkVariable<>).Name} without permission. No more variables can be read. This is critical. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}"); NetworkLog.LogError($"[{behaviour.NetworkVariableFields[i].GetType().Name}]"); } return; } int readStartPos = reader.Position; behaviour.NetworkVariableFields[i].ReadDelta(reader, networkManager.IsServer); networkManager.NetworkMetrics.TrackNetworkVariableDeltaReceived( senderId, networkObject, behaviour.NetworkVariableFields[i].Name, behaviour.__getTypeName(), reader.Length); if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) { if (reader.Position > (readStartPos + varSize)) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { NetworkLog.LogWarning( $"Var delta read too far. {reader.Position - (readStartPos + varSize)} bytes. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}"); } reader.Seek(readStartPos + varSize); } else if (reader.Position < (readStartPos + varSize)) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { NetworkLog.LogWarning( $"Var delta read too little. {(readStartPos + varSize) - reader.Position} bytes. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}"); } reader.Seek(readStartPos + varSize); } } } } } else { networkManager.SpawnManager.TriggerOnSpawn(NetworkObjectId, reader, context); } } } }