760 lines
		
	
	
		
			33 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			760 lines
		
	
	
		
			33 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using FishNet.Authenticating;
 | |
| using FishNet.Component.Observing;
 | |
| using FishNet.Connection;
 | |
| using FishNet.Managing.Debugging;
 | |
| using FishNet.Managing.Logging;
 | |
| using FishNet.Managing.Predicting;
 | |
| using FishNet.Managing.Transporting;
 | |
| using FishNet.Object;
 | |
| using FishNet.Serializing;
 | |
| using FishNet.Transporting;
 | |
| using FishNet.Utility.Extension;
 | |
| using FishNet.Utility.Performance;
 | |
| using System;
 | |
| using System.Collections.Generic;
 | |
| using System.Linq;
 | |
| using System.Text;
 | |
| using UnityEngine;
 | |
| 
 | |
| namespace FishNet.Managing.Server
 | |
| {
 | |
|     /// <summary>
 | |
|     /// A container for server data and actions.
 | |
|     /// </summary>
 | |
|     [DisallowMultipleComponent]
 | |
|     [AddComponentMenu("FishNet/Manager/ServerManager")]
 | |
|     public sealed partial class ServerManager : MonoBehaviour
 | |
|     {
 | |
|         #region Public.
 | |
|         /// <summary>
 | |
|         /// Called after the local server connection state changes.
 | |
|         /// </summary>
 | |
|         public event Action<ServerConnectionStateArgs> OnServerConnectionState;
 | |
|         /// <summary>
 | |
|         /// Called when authenticator has concluded a result for a connection. Boolean is true if authentication passed, false if failed.
 | |
|         /// </summary>
 | |
|         public event Action<NetworkConnection, bool> OnAuthenticationResult;
 | |
|         /// <summary>
 | |
|         /// Called when a remote client state changes with the server.
 | |
|         /// </summary>
 | |
|         public event Action<NetworkConnection, RemoteConnectionStateArgs> OnRemoteConnectionState;
 | |
|         /// <summary>
 | |
|         /// True if the server connection has started.
 | |
|         /// </summary>
 | |
|         public bool Started { get; private set; }
 | |
|         /// <summary>
 | |
|         /// Handling and information for objects on the server.
 | |
|         /// </summary>
 | |
|         public ServerObjects Objects { get; private set; }
 | |
|         /// <summary>
 | |
|         /// Authenticated and non-authenticated connected clients.
 | |
|         /// </summary>
 | |
|         [HideInInspector]
 | |
|         public Dictionary<int, NetworkConnection> Clients = new Dictionary<int, NetworkConnection>();
 | |
|         /// <summary>
 | |
|         /// NetworkManager for server.
 | |
|         /// </summary>
 | |
|         [HideInInspector]
 | |
|         public NetworkManager NetworkManager { get; private set; }
 | |
|         #endregion
 | |
| 
 | |
|         #region Serialized.
 | |
|         /// <summary>
 | |
|         /// Authenticator for this ServerManager. May be null if not using authentication.
 | |
|         /// </summary>
 | |
|         [Obsolete("Use GetAuthenticator and SetAuthenticator.")] //Remove on 2023/06/01
 | |
|         public Authenticator Authenticator
 | |
|         {
 | |
|             get => GetAuthenticator();
 | |
|             set => SetAuthenticator(value);
 | |
|         }
 | |
|         /// <summary>
 | |
|         /// Gets the Authenticator for this manager.
 | |
|         /// </summary>
 | |
|         /// <returns></returns>
 | |
|         public Authenticator GetAuthenticator() => _authenticator;
 | |
|         /// <summary>
 | |
|         /// Gets the Authenticator for this manager, and initializes it.
 | |
|         /// </summary>
 | |
|         /// <returns></returns>
 | |
|         public void SetAuthenticator(Authenticator value)
 | |
|         {
 | |
|             _authenticator = value;
 | |
|             InitializeAuthenticator();
 | |
|         }
 | |
|         [Tooltip("Authenticator for this ServerManager. May be null if not using authentication.")]
 | |
|         [SerializeField]
 | |
|         private Authenticator _authenticator;
 | |
|         /// <summary>
 | |
|         /// Default send rate for SyncTypes. A value of 0f will send changed values every tick.
 | |
|         /// SyncTypeRate cannot yet be changed at runtime because this would require recalculating rates on SyncBase, which is not yet implemented.
 | |
|         /// </summary>
 | |
|         /// <returns></returns>
 | |
|         internal float GetSynctypeRate() => _syncTypeRate;
 | |
|         [Tooltip("Default send rate for SyncTypes. A value of 0f will send changed values every tick.")]
 | |
|         [Range(0f, 60f)]
 | |
|         [SerializeField]
 | |
|         private float _syncTypeRate = 0.1f;
 | |
|         /// <summary>
 | |
|         /// How to pack object spawns.
 | |
|         /// </summary>
 | |
|         [Tooltip("How to pack object spawns.")]
 | |
|         [SerializeField]
 | |
|         internal TransformPackingData SpawnPacking = new TransformPackingData()
 | |
|         {
 | |
|             Position = AutoPackType.Unpacked,
 | |
|             Rotation = AutoPackType.PackedLess,
 | |
|             Scale = AutoPackType.PackedLess
 | |
|         };
 | |
|         /// <summary>
 | |
|         /// True to automatically set the frame rate when the client connects.
 | |
|         /// </summary>
 | |
|         [Tooltip("True to automatically set the frame rate when the client connects.")]
 | |
|         [SerializeField]
 | |
|         private bool _changeFrameRate = true;
 | |
|         /// <summary>
 | |
|         /// Maximum frame rate the server may run at. When as host this value runs at whichever is higher between client and server.
 | |
|         /// </summary>
 | |
|         internal ushort FrameRate => (_changeFrameRate) ? _frameRate : (ushort)0;
 | |
|         [Tooltip("Maximum frame rate the server may run at. When as host this value runs at whichever is higher between client and server.")]
 | |
|         [Range(1, NetworkManager.MAXIMUM_FRAMERATE)]
 | |
|         [SerializeField]
 | |
|         private ushort _frameRate = NetworkManager.MAXIMUM_FRAMERATE;
 | |
|         /// <summary>
 | |
|         /// True to share the Ids of clients and the objects they own with other clients. No sensitive information is shared.
 | |
|         /// </summary>
 | |
|         internal bool ShareIds => _shareIds;
 | |
|         [Tooltip("True to share the Ids of clients and the objects they own with other clients. No sensitive information is shared.")]
 | |
|         [SerializeField]
 | |
|         private bool _shareIds = true;
 | |
|         /// <summary>
 | |
|         /// Gets StartOnHeadless value.
 | |
|         /// </summary>
 | |
|         public bool GetStartOnHeadless() => _startOnHeadless;
 | |
|         /// <summary>
 | |
|         /// Sets StartOnHeadless value.
 | |
|         /// </summary>
 | |
|         /// <param name="value">New value to use.</param>
 | |
|         public void SetStartOnHeadless(bool value) => _startOnHeadless = value;
 | |
|         [Tooltip("True to automatically start the server connection when running as headless.")]
 | |
|         [SerializeField]
 | |
|         private bool _startOnHeadless = true;
 | |
|         /// <summary>
 | |
|         /// True to kick clients which send data larger than the MTU.
 | |
|         /// </summary>
 | |
|         internal bool LimitClientMTU => _limitClientMTU;
 | |
|         [Tooltip("True to kick clients which send data larger than the MTU.")]
 | |
|         [SerializeField]
 | |
|         private bool _limitClientMTU = true;
 | |
|         #endregion
 | |
| 
 | |
|         #region Private.
 | |
|         /// <summary>
 | |
|         /// Used to read splits.
 | |
|         /// </summary>
 | |
|         private SplitReader _splitReader = new SplitReader();
 | |
| #if UNITY_EDITOR || DEVELOPMENT_BUILD
 | |
|         /// <summary>
 | |
|         /// Logs data about parser to help debug.
 | |
|         /// </summary>
 | |
|         private ParseLogger _parseLogger = new ParseLogger();
 | |
| #endif
 | |
|         #endregion
 | |
| 
 | |
|         private void OnDestroy()
 | |
|         {
 | |
|             Objects?.SubscribeToSceneLoaded(false);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Initializes this script for use.
 | |
|         /// </summary>
 | |
|         /// <param name="manager"></param>
 | |
|         internal void InitializeOnce_Internal(NetworkManager manager)
 | |
|         {
 | |
|             NetworkManager = manager;
 | |
|             Objects = new ServerObjects(manager);
 | |
|             Objects.SubscribeToSceneLoaded(true);
 | |
|             InitializeRpcLinks();
 | |
|             //Unsubscribe first incase already subscribed.
 | |
|             SubscribeToTransport(false);
 | |
|             SubscribeToTransport(true);
 | |
|             NetworkManager.ClientManager.OnClientConnectionState += ClientManager_OnClientConnectionState;
 | |
|             NetworkManager.SceneManager.OnClientLoadedStartScenes += SceneManager_OnClientLoadedStartScenes;
 | |
| 
 | |
|             if (_authenticator == null)
 | |
|                 _authenticator = GetComponent<Authenticator>();
 | |
|             if (_authenticator != null)
 | |
|                 InitializeAuthenticator();
 | |
| 
 | |
|             _cachedLevelOfDetailInterval = NetworkManager.ClientManager.LevelOfDetailInterval;
 | |
|             _cachedUseLod = NetworkManager.ObserverManager.GetUseNetworkLod();
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Initializes the authenticator to this manager.
 | |
|         /// </summary>
 | |
|         private void InitializeAuthenticator()
 | |
|         {
 | |
|             Authenticator auth = GetAuthenticator();
 | |
|             if (auth == null || auth.Initialized)
 | |
|                 return;
 | |
|             if (NetworkManager == null)
 | |
|                 return;
 | |
| 
 | |
|             auth.InitializeOnce(NetworkManager);
 | |
|             auth.OnAuthenticationResult += _authenticator_OnAuthenticationResult;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Starts the server if configured to for headless.
 | |
|         /// </summary>
 | |
|         internal void StartForHeadless()
 | |
|         {
 | |
|             if (GetStartOnHeadless())
 | |
|             {
 | |
|                 //Wrapping logic in check instead of everything so _startOnHeadless doesnt warn as unused in editor.
 | |
| #if UNITY_SERVER
 | |
|                 StartConnection();
 | |
| #endif
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Stops the local server connection.
 | |
|         /// </summary>
 | |
|         /// <param name="sendDisconnectMessage">True to send a disconnect message to all clients first.</param>
 | |
|         public bool StopConnection(bool sendDisconnectMessage)
 | |
|         {
 | |
|             if (sendDisconnectMessage)
 | |
|                 SendDisconnectMessages(Clients.Values.ToList(), true);
 | |
| 
 | |
|             //Return stop connection result.
 | |
|             return NetworkManager.TransportManager.Transport.StopConnection(true);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Sends a disconnect messge to connectionIds.
 | |
|         /// This does not iterate outgoing automatically.
 | |
|         /// </summary>
 | |
|         /// <param name="connectionIds"></param>
 | |
|         internal void SendDisconnectMessages(int[] connectionIds)
 | |
|         {
 | |
|             List<NetworkConnection> conns = new List<NetworkConnection>();
 | |
|             foreach (int item in connectionIds)
 | |
|             {
 | |
|                 if (Clients.TryGetValueIL2CPP(item, out NetworkConnection c))
 | |
|                     conns.Add(c);
 | |
|             }
 | |
| 
 | |
|             if (conns.Count > 0)
 | |
|                 SendDisconnectMessages(conns, false);
 | |
|         }
 | |
|         /// <summary>
 | |
|         /// Sends a disconnect message to all clients and immediately iterates outgoing.
 | |
|         /// </summary>
 | |
|         private void SendDisconnectMessages(List<NetworkConnection> conns, bool iterate)
 | |
|         {
 | |
|             PooledWriter writer = WriterPool.GetWriter();
 | |
|             writer.WritePacketId(PacketId.Disconnect);
 | |
|             ArraySegment<byte> segment = writer.GetArraySegment();
 | |
|             //Send segment to each client, authenticated or not.
 | |
|             foreach (NetworkConnection c in conns)
 | |
|                 c.SendToClient((byte)Channel.Reliable, segment);
 | |
|             //Recycle writer.
 | |
|             writer.Dispose();
 | |
| 
 | |
|             if (iterate)
 | |
|                 NetworkManager.TransportManager.IterateOutgoing(true);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Starts the local server connection.
 | |
|         /// </summary>
 | |
|         public bool StartConnection()
 | |
|         {
 | |
|             return NetworkManager.TransportManager.Transport.StartConnection(true);
 | |
|         }
 | |
|         /// <summary>
 | |
|         /// Starts the local server using port.
 | |
|         /// </summary>
 | |
|         /// <param name="port">Port to start on.</param>
 | |
|         /// <returns></returns>
 | |
|         public bool StartConnection(ushort port)
 | |
|         {
 | |
|             Transport t = NetworkManager.TransportManager.Transport;
 | |
|             t.SetPort(port);
 | |
|             return t.StartConnection(true);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Called after the local client connection state changes.
 | |
|         /// </summary>
 | |
|         private void ClientManager_OnClientConnectionState(ClientConnectionStateArgs obj)
 | |
|         {
 | |
|             /* If client is doing anything but started destroy pending.
 | |
|              * Pending is only used for host mode. */
 | |
|             if (obj.ConnectionState != LocalConnectionState.Started)
 | |
|                 Objects.DestroyPending();
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Called when a client loads initial scenes after connecting.
 | |
|         /// </summary>
 | |
|         private void SceneManager_OnClientLoadedStartScenes(NetworkConnection conn, bool asServer)
 | |
|         {
 | |
|             if (asServer)
 | |
|             {
 | |
|                 Objects.RebuildObservers(conn);
 | |
|                 /* If connection is host then renderers must be hidden
 | |
|                  * for all objects not visible to the host. The observer system
 | |
|                  * does handle this but only after an initial state is set.
 | |
|                  * If the clientHost joins without observation of an object
 | |
|                  * then the initial state will never be set. */
 | |
|                 if (conn.IsLocalClient)
 | |
|                 {
 | |
|                     foreach (NetworkObject nob in Objects.Spawned.Values)
 | |
|                     {
 | |
|                         if (!nob.Observers.Contains(conn))
 | |
|                             nob.SetRenderersVisible(false);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Changes subscription status to transport.
 | |
|         /// </summary>
 | |
|         /// <param name="subscribe"></param>
 | |
|         private void SubscribeToTransport(bool subscribe)
 | |
|         {
 | |
|             if (NetworkManager == null || NetworkManager.TransportManager == null || NetworkManager.TransportManager.Transport == null)
 | |
|                 return;
 | |
| 
 | |
|             if (subscribe)
 | |
|             {
 | |
|                 NetworkManager.TransportManager.Transport.OnServerReceivedData += Transport_OnServerReceivedData;
 | |
|                 NetworkManager.TransportManager.Transport.OnServerConnectionState += Transport_OnServerConnectionState;
 | |
|                 NetworkManager.TransportManager.Transport.OnRemoteConnectionState += Transport_OnRemoteConnectionState;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 NetworkManager.TransportManager.Transport.OnServerReceivedData -= Transport_OnServerReceivedData;
 | |
|                 NetworkManager.TransportManager.Transport.OnServerConnectionState -= Transport_OnServerConnectionState;
 | |
|                 NetworkManager.TransportManager.Transport.OnRemoteConnectionState -= Transport_OnRemoteConnectionState;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Called when authenticator has concluded a result for a connection. Boolean is true if authentication passed, false if failed.
 | |
|         /// Server listens for this event automatically.
 | |
|         /// </summary>
 | |
|         private void _authenticator_OnAuthenticationResult(NetworkConnection conn, bool authenticated)
 | |
|         {
 | |
|             if (!authenticated)
 | |
|                 conn.Disconnect(false);
 | |
|             else
 | |
|                 ClientAuthenticated(conn);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Called when a connection state changes for the local server.
 | |
|         /// </summary>
 | |
|         private void Transport_OnServerConnectionState(ServerConnectionStateArgs args)
 | |
|         {
 | |
|             /* Let the client manager know the server state is changing first.
 | |
|              * This gives the client an opportunity to clean-up or prepare
 | |
|              * before the server completes it's actions. */
 | |
|             Started = AnyServerStarted();
 | |
|             NetworkManager.ClientManager.Objects.OnServerConnectionState(args);
 | |
|             //If no servers are started then reset match conditions.
 | |
|             if (!Started)
 | |
|             {
 | |
|                 MatchCondition.ClearMatchesWithoutRebuilding();
 | |
|                 //Despawn without synchronizing network objects.
 | |
|                 Objects.DespawnWithoutSynchronization(true);
 | |
|             }
 | |
|             Objects.OnServerConnectionState(args);
 | |
| 
 | |
|             LocalConnectionState state = args.ConnectionState;
 | |
| 
 | |
|             if (NetworkManager.CanLog(LoggingType.Common))
 | |
|             {
 | |
|                 Transport t = NetworkManager.TransportManager.GetTransport(args.TransportIndex);
 | |
|                 string tName = (t == null) ? "Unknown" : t.GetType().Name;
 | |
|                 Debug.Log($"Local server is {state.ToString().ToLower()} for {tName}.");
 | |
|             }
 | |
| 
 | |
|             NetworkManager.UpdateFramerate();
 | |
|             OnServerConnectionState?.Invoke(args);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Called when a connection state changes for a remote client.
 | |
|         /// </summary>
 | |
|         private void Transport_OnRemoteConnectionState(RemoteConnectionStateArgs args)
 | |
|         {
 | |
|             //Sanity check to make sure transports are following proper types/ranges.
 | |
|             int id = args.ConnectionId;
 | |
|             int maxIdValue = short.MaxValue;
 | |
|             if (id < 0 || id > maxIdValue)
 | |
|             {
 | |
|                 Kick(args.ConnectionId, KickReason.UnexpectedProblem, LoggingType.Error, $"The transport you are using supplied an invalid connection Id of {id}. Connection Id values must range between 0 and {maxIdValue}. The client has been disconnected.");
 | |
|                 return;
 | |
|             }
 | |
|             //Valid Id.
 | |
|             else
 | |
|             {
 | |
|                 //If started then add to authenticated clients.
 | |
|                 if (args.ConnectionState == RemoteConnectionState.Started)
 | |
|                 {
 | |
|                     NetworkManager.Log($"Remote connection started for Id {id}.");
 | |
|                     NetworkConnection conn = new NetworkConnection(NetworkManager, id, true);
 | |
|                     Clients.Add(args.ConnectionId, conn);
 | |
|                     OnRemoteConnectionState?.Invoke(conn, args);
 | |
|                     //Connection is no longer valid. This can occur if the user changes the state using the OnRemoteConnectionState event.
 | |
|                     if (!conn.IsValid)
 | |
|                         return;
 | |
|                     /* If there is an authenticator
 | |
|                      * and the transport is not a local transport. */
 | |
|                     Authenticator auth = GetAuthenticator();
 | |
|                     if (auth != null && !NetworkManager.TransportManager.IsLocalTransport(id))
 | |
|                         auth.OnRemoteConnection(conn);
 | |
|                     else
 | |
|                         ClientAuthenticated(conn);
 | |
|                 }
 | |
|                 //If stopping.
 | |
|                 else if (args.ConnectionState == RemoteConnectionState.Stopped)
 | |
|                 {
 | |
|                     /* If client's connection is found then clean
 | |
|                      * them up from server. */
 | |
|                     if (Clients.TryGetValueIL2CPP(id, out NetworkConnection conn))
 | |
|                     {
 | |
|                         conn.SetDisconnecting(true);
 | |
|                         OnRemoteConnectionState?.Invoke(conn, args);
 | |
|                         Clients.Remove(id);
 | |
|                         MatchCondition.RemoveFromMatchWithoutRebuild(conn, NetworkManager);
 | |
|                         Objects.ClientDisconnected(conn);
 | |
|                         BroadcastClientConnectionChange(false, conn);
 | |
|                         //Return predictedObjectIds.
 | |
|                         Queue<int> pqId = conn.PredictedObjectIds;
 | |
|                         while (pqId.Count > 0)
 | |
|                             Objects.CacheObjectId(pqId.Dequeue());
 | |
| 
 | |
|                         conn.Reset();
 | |
|                         NetworkManager.Log($"Remote connection stopped for Id {id}.");
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Sends client their connectionId.
 | |
|         /// </summary>
 | |
|         /// <param name="connectionid"></param>
 | |
|         private void SendAuthenticated(NetworkConnection conn)
 | |
|         {
 | |
|             using (PooledWriter writer = WriterPool.GetWriter())
 | |
|             {
 | |
|                 writer.WritePacketId(PacketId.Authenticated);
 | |
|                 writer.WriteNetworkConnection(conn);
 | |
|                 /* If predicted spawning is enabled then also send
 | |
|                  * reserved objectIds. */
 | |
|                 ;
 | |
|                 PredictionManager pm = NetworkManager.PredictionManager;
 | |
|                 if (pm.GetAllowPredictedSpawning())
 | |
|                 {
 | |
|                     int count = Mathf.Min(Objects.GetObjectIdCache().Count, pm.GetReservedObjectIds());
 | |
|                     writer.WriteByte((byte)count);
 | |
| 
 | |
|                     for (int i = 0; i < count; i++)
 | |
|                     {
 | |
|                         ushort val = (ushort)Objects.GetNextNetworkObjectId(false);
 | |
|                         writer.WriteNetworkObjectId(val);
 | |
|                         conn.PredictedObjectIds.Enqueue(val);
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 NetworkManager.TransportManager.SendToClient((byte)Channel.Reliable, writer.GetArraySegment(), conn);
 | |
|             }
 | |
|         }
 | |
|         /// <summary>
 | |
|         /// Called when the server socket receives data.
 | |
|         /// </summary>
 | |
|         private void Transport_OnServerReceivedData(ServerReceivedDataArgs args)
 | |
|         {
 | |
|             args.Data = NetworkManager.TransportManager.ProcessIntermediateIncoming(args.Data, false);
 | |
|             ParseReceived(args);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Called when the server receives data.
 | |
|         /// </summary>
 | |
|         /// <param name="args"></param>
 | |
|         private void ParseReceived(ServerReceivedDataArgs args)
 | |
|         {
 | |
| #if UNITY_EDITOR || DEVELOPMENT_BUILD
 | |
|             _parseLogger.Reset();
 | |
| #endif
 | |
| 
 | |
|             //Not from a valid connection.
 | |
|             if (args.ConnectionId < 0)
 | |
|                 return;
 | |
|             ArraySegment<byte> segment = args.Data;
 | |
|             NetworkManager.StatisticsManager.NetworkTraffic.LocalServerReceivedData((ulong)segment.Count);
 | |
|             if (segment.Count <= TransportManager.TICK_BYTES)
 | |
|                 return;
 | |
| 
 | |
|             //FishNet internally splits packets so nothing should ever arrive over MTU.
 | |
|             int channelMtu = NetworkManager.TransportManager.GetMTU(args.TransportIndex, (byte)args.Channel);
 | |
|             //If over MTU kick client immediately.
 | |
|             if (segment.Count > channelMtu && !NetworkManager.TransportManager.IsLocalTransport(args.ConnectionId))
 | |
|             {
 | |
|                 ExceededMTUKick();
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             PacketId packetId = PacketId.Unset;
 | |
| #if !UNITY_EDITOR && !DEVELOPMENT_BUILD
 | |
|             try
 | |
|             {
 | |
| #endif
 | |
|             using (PooledReader reader = ReaderPool.GetReader(segment, NetworkManager))
 | |
|             {
 | |
|                 uint tick = reader.ReadUInt32(AutoPackType.Unpacked);
 | |
|                 NetworkManager.TimeManager.LastPacketTick = tick;
 | |
|                 /* This is a special condition where a message may arrive split.
 | |
|                 * When this occurs buffer each packet until all packets are
 | |
|                 * received. */
 | |
|                 if (reader.PeekPacketId() == PacketId.Split)
 | |
|                 {
 | |
|                     //Skip packetId.
 | |
|                     reader.ReadPacketId();
 | |
| 
 | |
|                     int expectedMessages;
 | |
|                     _splitReader.GetHeader(reader, out expectedMessages);
 | |
|                     //If here split message can be written.
 | |
|                     _splitReader.Write(NetworkManager.TimeManager.LastPacketTick, reader, expectedMessages);
 | |
| 
 | |
|                     /* If fullMessage returns 0 count then the split
 | |
|                      * has not written fully yet. Otherwise, if there is
 | |
|                      * data within then reinitialize reader with the
 | |
|                      * full message. */
 | |
|                     ArraySegment<byte> fullMessage = _splitReader.GetFullMessage();
 | |
|                     if (fullMessage.Count == 0)
 | |
|                         return;
 | |
| 
 | |
|                     /* If here then all data has been received.
 | |
|                      * It's possible the client could have exceeded 
 | |
|                      * maximum MTU but not the maximum number of splits.
 | |
|                      * This is because the length of each split
 | |
|                      * is not written, so we don't know how much data of the
 | |
|                      * final message actually belonged to the split vs
 | |
|                      * unrelated data added afterwards. We're going to cut
 | |
|                      * the client some slack in this situation for the sake
 | |
|                      * of keeping things simple. */
 | |
|                     //Initialize reader with full message.
 | |
|                     reader.Initialize(fullMessage, NetworkManager);
 | |
|                 }
 | |
| 
 | |
|                 //Parse reader.
 | |
|                 while (reader.Remaining > 0)
 | |
|                 {
 | |
|                     packetId = reader.ReadPacketId();
 | |
| #if UNITY_EDITOR || DEVELOPMENT_BUILD
 | |
|                     _parseLogger.AddPacket(packetId);
 | |
| #endif
 | |
|                     NetworkConnection conn;
 | |
| 
 | |
|                     /* Connection isn't available. This should never happen.
 | |
|                      * Force an immediate disconnect. */
 | |
|                     if (!Clients.TryGetValueIL2CPP(args.ConnectionId, out conn))
 | |
|                     {
 | |
|                         Kick(args.ConnectionId, KickReason.UnexpectedProblem, LoggingType.Error, $"ConnectionId {conn.ClientId} not found within Clients. Connection will be kicked immediately.");
 | |
|                         return;
 | |
|                     }
 | |
|                     conn.SetLastPacketTick(tick);
 | |
|                     /* If connection isn't authenticated and isn't a broadcast
 | |
|                      * then disconnect client. If a broadcast then process
 | |
|                      * normally; client may still become disconnected if the broadcast
 | |
|                      * does not allow to be called while not authenticated. */
 | |
|                     if (!conn.Authenticated && packetId != PacketId.Broadcast)
 | |
|                     {
 | |
|                         conn.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"ConnectionId {conn.ClientId} sent a Broadcast without being authenticated. Connection will be kicked immediately.");
 | |
|                         return;
 | |
|                     }
 | |
| 
 | |
|                     //Only check if not developer build because users pay pause editor.
 | |
| #if !DEVELOPMENT_BUILD && !UNITY_EDITOR
 | |
|                     /* If hasn't sent LOD recently enough. LODs are sent every half a second, so
 | |
|                      * by multiplaying interval by 60 this gives the client a 30 second window. */
 | |
|                     if (_cachedUseLod && conn.IsLateForLevelOfDetail(_cachedLevelOfDetailInterval * 60))
 | |
|                     {
 | |
|                         conn.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"ConnectionId {conn.ClientId} has gone too long without sending a level of detail update. Connection will be kicked immediately.");
 | |
|                         return;
 | |
|                     }
 | |
| #endif
 | |
|                     if (packetId == PacketId.Replicate)
 | |
|                     {
 | |
|                         Objects.ParseReplicateRpc(reader, conn, args.Channel);
 | |
|                     }
 | |
|                     else if (packetId == PacketId.ServerRpc)
 | |
|                     {
 | |
|                         Objects.ParseServerRpc(reader, conn, args.Channel);
 | |
|                     }
 | |
|                     else if (packetId == PacketId.ObjectSpawn)
 | |
|                     {
 | |
|                         if (!NetworkManager.PredictionManager.GetAllowPredictedSpawning())
 | |
|                         {
 | |
|                             conn.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"ConnectionId {conn.ClientId} sent a predicted spawn while predicted spawning is not enabled. Connection will be kicked immediately.");
 | |
|                             return;
 | |
|                         }
 | |
|                         Objects.ReadPredictedSpawn(reader, conn);
 | |
|                     }
 | |
|                     else if (packetId == PacketId.ObjectDespawn)
 | |
|                     {
 | |
|                         if (!NetworkManager.PredictionManager.GetAllowPredictedSpawning())
 | |
|                         {
 | |
|                             conn.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"ConnectionId {conn.ClientId} sent a predicted spawn while predicted spawning is not enabled. Connection will be kicked immediately.");
 | |
|                             return;
 | |
|                         }
 | |
|                         Objects.ReadPredictedDespawn(reader, conn);
 | |
|                     }
 | |
|                     else if (packetId == PacketId.NetworkLODUpdate)
 | |
|                     {
 | |
|                         ParseNetworkLODUpdate(reader, conn);
 | |
|                     }
 | |
|                     else if (packetId == PacketId.Broadcast)
 | |
|                     {
 | |
|                         ParseBroadcast(reader, conn, args.Channel);
 | |
|                     }
 | |
|                     else if (packetId == PacketId.PingPong)
 | |
|                     {
 | |
|                         ParsePingPong(reader, conn);
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
| #if UNITY_EDITOR || DEVELOPMENT_BUILD
 | |
|                         NetworkManager.LogError($"Server received an unhandled PacketId of {(ushort)packetId} from connectionId {args.ConnectionId}. Remaining data has been purged.");
 | |
|                         _parseLogger.Print(NetworkManager);
 | |
| #else
 | |
|                         NetworkManager.LogError($"Server received an unhandled PacketId of {(ushort)packetId} from connectionId {args.ConnectionId}. Connection will be kicked immediately.");
 | |
|                         NetworkManager.TransportManager.Transport.StopConnection(args.ConnectionId, true);
 | |
| #endif
 | |
|                         return;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| #if !UNITY_EDITOR && !DEVELOPMENT_BUILD
 | |
|             }
 | |
|             catch (Exception e)
 | |
|             {
 | |
|                 Kick(args.ConnectionId, KickReason.MalformedData, LoggingType.Error, $"Server encountered an error while parsing data for packetId {packetId} from connectionId {args.ConnectionId}. Connection will be kicked immediately. Message: {e.Message}.");
 | |
|             }
 | |
| #endif
 | |
| 
 | |
|             //Kicks connection for exceeding MTU.
 | |
|             void ExceededMTUKick()
 | |
|             {
 | |
|                 Kick(args.ConnectionId, KickReason.ExploitExcessiveData, LoggingType.Common, $"ConnectionId {args.ConnectionId} sent a message larger than allowed amount. Connection will be kicked immediately.");
 | |
|             }
 | |
| 
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Parses a received PingPong.
 | |
|         /// </summary>
 | |
|         /// <param name="reader"></param>
 | |
|         /// <param name="conn"></param>
 | |
|         private void ParsePingPong(PooledReader reader, NetworkConnection conn)
 | |
|         {
 | |
|             /* //security limit how often clients can send pings.
 | |
|              * have clients use a stopwatch rather than frame time
 | |
|              * for checks to ensure it's not possible to send
 | |
|              * excessively should their game stutter then catch back up. */
 | |
|             uint clientTick = reader.ReadUInt32(AutoPackType.Unpacked);
 | |
|             if (conn.CanPingPong())
 | |
|                 NetworkManager.TimeManager.SendPong(conn, clientTick);
 | |
|         }
 | |
| 
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Called when a remote client authenticates with the server.
 | |
|         /// </summary>
 | |
|         /// <param name="connectionId"></param>
 | |
|         private void ClientAuthenticated(NetworkConnection connection)
 | |
|         {
 | |
|             /* Immediately send connectionId to client. Some transports
 | |
|             * don't give clients their remoteId, therefor it has to be sent
 | |
|             * by the ServerManager. This packet is very simple and can be built
 | |
|             * on the spot. */
 | |
|             connection.ConnectionAuthenticated();
 | |
|             /* Send client Ids before telling the client
 | |
|              * they are authenticated. This is important because when the client becomes
 | |
|              * authenticated they set their LocalConnection using Clients field in ClientManager,
 | |
|              * which is set after getting Ids. */
 | |
|             BroadcastClientConnectionChange(true, connection);
 | |
|             SendAuthenticated(connection);
 | |
| 
 | |
|             OnAuthenticationResult?.Invoke(connection, true);
 | |
|             NetworkManager.SceneManager.OnClientAuthenticated(connection);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Sends a client connection state change to owner and other clients if applicable.
 | |
|         /// </summary>
 | |
|         private void BroadcastClientConnectionChange(bool connected, NetworkConnection conn)
 | |
|         {
 | |
|             //If sharing Ids then send all connected client Ids first if is a connected state.
 | |
|             if (ShareIds)
 | |
|             {
 | |
|                 /* Send a broadcast to all authenticated clients with the clientId
 | |
|                  * that just connected. The conn client will also get this. */
 | |
|                 ClientConnectionChangeBroadcast changeMsg = new ClientConnectionChangeBroadcast()
 | |
|                 {
 | |
|                     Connected = connected,
 | |
|                     Id = conn.ClientId
 | |
|                 };
 | |
|                 Broadcast(changeMsg);
 | |
| 
 | |
|                 /* If state is connected then the conn client
 | |
|                  * must also receive all currently connected client ids. */
 | |
|                 if (connected)
 | |
|                 {
 | |
|                     //Send already connected clients to the connection that just joined.
 | |
|                     ListCache<int> lc = ListCaches.GetIntCache();
 | |
|                     foreach (int key in Clients.Keys)
 | |
|                         lc.AddValue(key);
 | |
| 
 | |
|                     ConnectedClientsBroadcast allMsg = new ConnectedClientsBroadcast()
 | |
|                     {
 | |
|                         ListCache = lc
 | |
|                     };
 | |
|                     conn.Broadcast(allMsg);
 | |
|                     ListCaches.StoreCache(lc);
 | |
|                 }
 | |
|             }
 | |
|             //If not sharing Ids then only send ConnectionChange to conn.
 | |
|             else
 | |
|             {
 | |
|                 if (connected)
 | |
|                 {
 | |
|                     /* Send broadcast only to the client which just disconnected.
 | |
|                      * Only send if connecting. If the client is disconnected there's no reason
 | |
|                      * to send them a disconnect msg. */
 | |
|                     ClientConnectionChangeBroadcast changeMsg = new ClientConnectionChangeBroadcast()
 | |
|                     {
 | |
|                         Connected = connected,
 | |
|                         Id = conn.ClientId
 | |
|                     };
 | |
|                     Broadcast(conn, changeMsg, true, Channel.Reliable);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|         }
 | |
| 
 | |
|     }
 | |
| 
 | |
| 
 | |
| }
 |