458 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			458 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using FishNet.Broadcast;
 | |
| using FishNet.Broadcast.Helping;
 | |
| using FishNet.Connection;
 | |
| using FishNet.Managing.Logging;
 | |
| using FishNet.Managing.Utility;
 | |
| using FishNet.Object;
 | |
| using FishNet.Serializing;
 | |
| using FishNet.Serializing.Helping;
 | |
| using FishNet.Transporting;
 | |
| using FishNet.Utility.Extension;
 | |
| using System;
 | |
| using System.Collections.Generic;
 | |
| using System.Linq;
 | |
| using System.Runtime.CompilerServices;
 | |
| using UnityEngine;
 | |
| 
 | |
| namespace FishNet.Managing.Server
 | |
| {
 | |
|     public sealed partial class ServerManager : MonoBehaviour
 | |
|     {
 | |
|         #region Private.
 | |
|         /// <summary>
 | |
|         /// Delegate to read received broadcasts.
 | |
|         /// </summary>
 | |
|         /// <param name="connection"></param>
 | |
|         /// <param name="reader"></param>
 | |
|         private delegate void ClientBroadcastDelegate(NetworkConnection connection, PooledReader reader);
 | |
|         /// <summary>
 | |
|         /// Delegates for each key.
 | |
|         /// </summary>
 | |
|         private readonly Dictionary<ushort, HashSet<ClientBroadcastDelegate>> _broadcastHandlers = new Dictionary<ushort, HashSet<ClientBroadcastDelegate>>();
 | |
|         /// <summary>
 | |
|         /// Delegate targets for each key.
 | |
|         /// </summary>
 | |
|         private Dictionary<ushort, HashSet<(int, ClientBroadcastDelegate)>> _handlerTargets = new Dictionary<ushort, HashSet<(int, ClientBroadcastDelegate)>>();
 | |
|         /// <summary>
 | |
|         /// Connections which can be broadcasted to after having excluded removed.
 | |
|         /// </summary>
 | |
|         private HashSet<NetworkConnection> _connectionsWithoutExclusions = new HashSet<NetworkConnection>();
 | |
|         #endregion
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Registers a method to call when a Broadcast arrives.
 | |
|         /// </summary>
 | |
|         /// <typeparam name="T">Type of broadcast being registered.</typeparam>
 | |
|         /// <param name="handler">Method to call.</param>
 | |
|         /// <param name="requireAuthentication">True if the client must be authenticated for the method to call.</param>
 | |
|         public void RegisterBroadcast<T>(Action<NetworkConnection, T> handler, bool requireAuthentication = true) where T : struct, IBroadcast
 | |
|         {
 | |
|             ushort key = BroadcastHelper.GetKey<T>();
 | |
| 
 | |
|             /* Create delegate and add for
 | |
|              * handler method. */
 | |
|             HashSet<ClientBroadcastDelegate> handlers;
 | |
|             if (!_broadcastHandlers.TryGetValueIL2CPP(key, out handlers))
 | |
|             {
 | |
|                 handlers = new HashSet<ClientBroadcastDelegate>();
 | |
|                 _broadcastHandlers.Add(key, handlers);
 | |
|             }
 | |
|             ClientBroadcastDelegate del = CreateBroadcastDelegate(handler, requireAuthentication);
 | |
|             handlers.Add(del);
 | |
| 
 | |
|             /* Add hashcode of target for handler.
 | |
|              * This is so we can unregister the target later. */
 | |
|             int handlerHashCode = handler.GetHashCode();
 | |
|             HashSet<(int, ClientBroadcastDelegate)> targetHashCodes;
 | |
|             if (!_handlerTargets.TryGetValueIL2CPP(key, out targetHashCodes))
 | |
|             {
 | |
|                 targetHashCodes = new HashSet<(int, ClientBroadcastDelegate)>();
 | |
|                 _handlerTargets.Add(key, targetHashCodes);
 | |
|             }
 | |
| 
 | |
|             targetHashCodes.Add((handlerHashCode, del));
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Unregisters a method call from a Broadcast type.
 | |
|         /// </summary>
 | |
|         /// <typeparam name="T">Type of broadcast being unregistered.</typeparam>
 | |
|         /// <param name="handler">Method to unregister.</param>
 | |
|         public void UnregisterBroadcast<T>(Action<NetworkConnection, T> handler) where T : struct, IBroadcast
 | |
|         {
 | |
|             ushort key = BroadcastHelper.GetKey<T>();
 | |
| 
 | |
|             /* If key is found for T then look for
 | |
|              * the appropriate handler to remove. */
 | |
|             if (_broadcastHandlers.TryGetValueIL2CPP(key, out HashSet<ClientBroadcastDelegate> handlers))
 | |
|             {
 | |
|                 HashSet<(int, ClientBroadcastDelegate)> targetHashCodes;
 | |
|                 if (_handlerTargets.TryGetValueIL2CPP(key, out targetHashCodes))
 | |
|                 {
 | |
|                     int handlerHashCode = handler.GetHashCode();
 | |
|                     ClientBroadcastDelegate result = null;
 | |
|                     foreach ((int targetHashCode, ClientBroadcastDelegate del) in targetHashCodes)
 | |
|                     {
 | |
|                         if (targetHashCode == handlerHashCode)
 | |
|                         {
 | |
|                             result = del;
 | |
|                             targetHashCodes.Remove((targetHashCode, del));
 | |
|                             break;
 | |
|                         }
 | |
|                     }
 | |
|                     //If no more in targetHashCodes then remove from handlerTarget.
 | |
|                     if (targetHashCodes.Count == 0)
 | |
|                         _handlerTargets.Remove(key);
 | |
| 
 | |
|                     if (result != null)
 | |
|                         handlers.Remove(result);
 | |
|                 }
 | |
| 
 | |
|                 //If no more in handlers then remove broadcastHandlers.
 | |
|                 if (handlers.Count == 0)
 | |
|                     _broadcastHandlers.Remove(key);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Creates a ClientBroadcastDelegate.
 | |
|         /// </summary>
 | |
|         /// <typeparam name="T"></typeparam>
 | |
|         /// <param name="handler"></param>
 | |
|         /// <param name="requireAuthentication"></param>
 | |
|         /// <returns></returns>
 | |
|         private ClientBroadcastDelegate CreateBroadcastDelegate<T>(Action<NetworkConnection, T> handler, bool requireAuthentication)
 | |
|         {
 | |
|             void LogicContainer(NetworkConnection connection, PooledReader reader)
 | |
|             {
 | |
|                 //If requires authentication and client isn't authenticated.
 | |
|                 if (requireAuthentication && !connection.Authenticated)
 | |
|                 {
 | |
|                     connection.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"ConnectionId {connection.ClientId} sent broadcast {typeof(T).Name} which requires authentication, but client was not authenticated. Client has been disconnected.");
 | |
|                     return;
 | |
|                 }
 | |
| 
 | |
|                 T broadcast = reader.Read<T>();
 | |
|                 handler?.Invoke(connection, broadcast);
 | |
|             }
 | |
|             return LogicContainer;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Parses a received broadcast.
 | |
|         /// </summary>
 | |
|         [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | |
|         private void ParseBroadcast(PooledReader reader, NetworkConnection conn, Channel channel)
 | |
|         {
 | |
|             ushort key = reader.ReadUInt16();
 | |
|             int dataLength = Packets.GetPacketLength((ushort)PacketId.Broadcast, reader, channel);
 | |
| 
 | |
|             //Try to invoke the handler for that message
 | |
|             if (_broadcastHandlers.TryGetValueIL2CPP(key, out HashSet<ClientBroadcastDelegate> handlers))
 | |
|             {
 | |
|                 int readerStartPosition = reader.Position;
 | |
|                 /* //muchlater resetting the position could be better by instead reading once and passing in
 | |
|                  * the object to invoke with. */
 | |
|                 bool rebuildHandlers = false;
 | |
|                 //True if data is read at least once. Otherwise it's length will have to be purged.
 | |
|                 bool dataRead = false;
 | |
|                 foreach (ClientBroadcastDelegate handler in handlers)
 | |
|                 {
 | |
|                     if (handler.Target == null)
 | |
|                     {
 | |
|                         NetworkManager.LogWarning($"A Broadcast handler target is null. This can occur when a script is destroyed but does not unregister from a Broadcast.");
 | |
|                         rebuildHandlers = true;
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         reader.Position = readerStartPosition;
 | |
|                         handler.Invoke(conn, reader);
 | |
|                         dataRead = true;
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 //If rebuilding handlers...
 | |
|                 if (rebuildHandlers)
 | |
|                 {
 | |
|                     List<ClientBroadcastDelegate> dels = handlers.ToList();
 | |
|                     handlers.Clear();
 | |
|                     for (int i = 0; i < dels.Count; i++)
 | |
|                     {
 | |
|                         if (dels[i].Target != null)
 | |
|                             handlers.Add(dels[i]);
 | |
|                     }
 | |
|                 }
 | |
|                 //Make sure data was read as well.
 | |
|                 if (!dataRead)
 | |
|                     reader.Skip(dataLength);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 reader.Skip(dataLength);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Sends a broadcast to a connection.
 | |
|         /// </summary>
 | |
|         /// <typeparam name="T">Type of broadcast to send.</typeparam>
 | |
|         /// <param name="connection">Connection to send to.</param>
 | |
|         /// <param name="message">Broadcast data being sent; for example: an instance of your broadcast type.</param>
 | |
|         /// <param name="requireAuthenticated">True if the client must be authenticated for this broadcast to send.</param>
 | |
|         /// <param name="channel">Channel to send on.</param>
 | |
|         public void Broadcast<T>(NetworkConnection connection, T message, bool requireAuthenticated = true, Channel channel = Channel.Reliable) where T : struct, IBroadcast
 | |
|         {
 | |
|             if (!Started)
 | |
|             {
 | |
|                 NetworkManager.LogWarning($"Cannot send broadcast to client because server is not active.");
 | |
|                 return;
 | |
|             }
 | |
|             if (requireAuthenticated && !connection.Authenticated)
 | |
|             {
 | |
|                 NetworkManager.LogWarning($"Cannot send broadcast to client because they are not authenticated.");
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             using (PooledWriter writer = WriterPool.GetWriter())
 | |
|             {
 | |
|                 Broadcasts.WriteBroadcast<T>(writer, message, channel);
 | |
|                 ArraySegment<byte> segment = writer.GetArraySegment();
 | |
|                 NetworkManager.TransportManager.SendToClient((byte)channel, segment, connection);
 | |
|             }
 | |
|         }
 | |
| 
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Sends a broadcast to connections.
 | |
|         /// </summary>
 | |
|         /// <typeparam name="T">Type of broadcast to send.</typeparam>
 | |
|         /// <param name="connections">Connections to send to.</param>
 | |
|         /// <param name="message">Broadcast data being sent; for example: an instance of your broadcast type.</param>
 | |
|         /// <param name="requireAuthenticated">True if the clients must be authenticated for this broadcast to send.</param>
 | |
|         /// <param name="channel">Channel to send on.</param>
 | |
|         public void Broadcast<T>(HashSet<NetworkConnection> connections, T message, bool requireAuthenticated = true, Channel channel = Channel.Reliable) where T : struct, IBroadcast
 | |
|         {
 | |
|             if (!Started)
 | |
|             {
 | |
|                 NetworkManager.LogWarning($"Cannot send broadcast to client because server is not active.");
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             bool failedAuthentication = false;
 | |
|             using (PooledWriter writer = WriterPool.GetWriter())
 | |
|             {
 | |
|                 Broadcasts.WriteBroadcast<T>(writer, message, channel);
 | |
|                 ArraySegment<byte> segment = writer.GetArraySegment();
 | |
| 
 | |
|                 foreach (NetworkConnection conn in connections)
 | |
|                 {
 | |
|                     if (requireAuthenticated && !conn.Authenticated)
 | |
|                         failedAuthentication = true;
 | |
|                     else
 | |
|                         NetworkManager.TransportManager.SendToClient((byte)channel, segment, conn);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if (failedAuthentication)
 | |
|             {
 | |
|                 NetworkManager.LogWarning($"One or more broadcast did not send to a client because they were not authenticated.");
 | |
|                 return;
 | |
|             }
 | |
|         }
 | |
| 
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Sends a broadcast to connections except excluded.
 | |
|         /// </summary>
 | |
|         /// <typeparam name="T">Type of broadcast to send.</typeparam>
 | |
|         /// <param name="connections">Connections to send to.</param>
 | |
|         /// <param name="excludedConnection">Connection to exclude.</param>
 | |
|         /// <param name="message">Broadcast data being sent; for example: an instance of your broadcast type.</param>
 | |
|         /// <param name="requireAuthenticated">True if the clients must be authenticated for this broadcast to send.</param>
 | |
|         /// <param name="channel">Channel to send on.</param>
 | |
|         public void BroadcastExcept<T>(HashSet<NetworkConnection> connections, NetworkConnection excludedConnection, T message, bool requireAuthenticated = true, Channel channel = Channel.Reliable) where T : struct, IBroadcast
 | |
|         {
 | |
|             if (!Started)
 | |
|             {
 | |
|                 NetworkManager.LogWarning($"Cannot send broadcast to client because server is not active.");
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             //Fast exit if no exclusions.
 | |
|             if (excludedConnection == null || !excludedConnection.IsValid)
 | |
|             {
 | |
|                 Broadcast(connections, message, requireAuthenticated, channel);
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             connections.Remove(excludedConnection);
 | |
|             Broadcast(connections, message, requireAuthenticated, channel);
 | |
|         }
 | |
| 
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Sends a broadcast to connections except excluded.
 | |
|         /// </summary>
 | |
|         /// <typeparam name="T">Type of broadcast to send.</typeparam>
 | |
|         /// <param name="connections">Connections to send to.</param>
 | |
|         /// <param name="excludedConnections">Connections to exclude.</param>
 | |
|         /// <param name="message">Broadcast data being sent; for example: an instance of your broadcast type.</param>
 | |
|         /// <param name="requireAuthenticated">True if the clients must be authenticated for this broadcast to send.</param>
 | |
|         /// <param name="channel">Channel to send on.</param>
 | |
|         public void BroadcastExcept<T>(HashSet<NetworkConnection> connections, HashSet<NetworkConnection> excludedConnections, T message, bool requireAuthenticated = true, Channel channel = Channel.Reliable) where T : struct, IBroadcast
 | |
|         {
 | |
|             if (!Started)
 | |
|             {
 | |
|                 NetworkManager.LogWarning($"Cannot send broadcast to client because server is not active.");
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             //Fast exit if no exclusions.
 | |
|             if (excludedConnections == null || excludedConnections.Count == 0)
 | |
|             {
 | |
|                 Broadcast(connections, message, requireAuthenticated, channel);
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             /* I'm not sure if the hashset API such as intersect generates
 | |
|              * GC or not but I'm betting doing remove locally is faster, or
 | |
|              * just as fast. */
 | |
|             foreach (NetworkConnection ec in excludedConnections)
 | |
|                 connections.Remove(ec);
 | |
| 
 | |
|             Broadcast(connections, message, requireAuthenticated, channel);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Sends a broadcast to all connections except excluded.
 | |
|         /// </summary>
 | |
|         /// <typeparam name="T">Type of broadcast to send.</typeparam>
 | |
|         /// <param name="excludedConnection">Connection to exclude.</param>
 | |
|         /// <param name="message">Broadcast data being sent; for example: an instance of your broadcast type.</param>
 | |
|         /// <param name="requireAuthenticated">True if the clients must be authenticated for this broadcast to send.</param>
 | |
|         /// <param name="channel">Channel to send on.</param>
 | |
|         public void BroadcastExcept<T>(NetworkConnection excludedConnection, T message, bool requireAuthenticated = true, Channel channel = Channel.Reliable) where T : struct, IBroadcast
 | |
|         {
 | |
|             if (!Started)
 | |
|             {
 | |
|                 NetworkManager.LogWarning($"Cannot send broadcast to client because server is not active.");
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             //Fast exit if there are no excluded.
 | |
|             if (excludedConnection == null || !excludedConnection.IsValid)
 | |
|             {
 | |
|                 Broadcast(message, requireAuthenticated, channel);
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             _connectionsWithoutExclusions.Clear();
 | |
|             /* It will be faster to fill the entire list then
 | |
|              * remove vs checking if each connection is contained within excluded. */
 | |
|             foreach (NetworkConnection c in Clients.Values)
 | |
|                 _connectionsWithoutExclusions.Add(c);
 | |
|             //Remove
 | |
|             _connectionsWithoutExclusions.Remove(excludedConnection);
 | |
| 
 | |
|             Broadcast(_connectionsWithoutExclusions, message, requireAuthenticated, channel);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Sends a broadcast to all connections except excluded.
 | |
|         /// </summary>
 | |
|         /// <typeparam name="T">Type of broadcast to send.</typeparam>
 | |
|         /// <param name="excludedConnections">Connections to send to.</param>
 | |
|         /// <param name="message">Broadcast data being sent; for example: an instance of your broadcast type.</param>
 | |
|         /// <param name="requireAuthenticated">True if the clients must be authenticated for this broadcast to send.</param>
 | |
|         /// <param name="channel">Channel to send on.</param>
 | |
|         public void BroadcastExcept<T>(HashSet<NetworkConnection> excludedConnections, T message, bool requireAuthenticated = true, Channel channel = Channel.Reliable) where T : struct, IBroadcast
 | |
|         {
 | |
|             if (!Started)
 | |
|             {
 | |
|                 NetworkManager.LogWarning($"Cannot send broadcast to client because server is not active.");
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             //Fast exit if there are no excluded.
 | |
|             if (excludedConnections == null || excludedConnections.Count == 0)
 | |
|             {
 | |
|                 Broadcast(message, requireAuthenticated, channel);
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             _connectionsWithoutExclusions.Clear();
 | |
|             /* It will be faster to fill the entire list then
 | |
|              * remove vs checking if each connection is contained within excluded. */
 | |
|             foreach (NetworkConnection c in Clients.Values)
 | |
|                 _connectionsWithoutExclusions.Add(c);
 | |
|             //Remove
 | |
|             foreach (NetworkConnection c in excludedConnections)
 | |
|                 _connectionsWithoutExclusions.Remove(c);
 | |
| 
 | |
|             Broadcast(_connectionsWithoutExclusions, message, requireAuthenticated, channel);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Sends a broadcast to observers.
 | |
|         /// </summary>
 | |
|         /// <typeparam name="T">Type of broadcast to send.</typeparam>
 | |
|         /// <param name="networkObject">NetworkObject to use Observers from.</param>
 | |
|         /// <param name="message">Broadcast data being sent; for example: an instance of your broadcast type.</param>
 | |
|         /// <param name="requireAuthenticated">True if the clients must be authenticated for this broadcast to send.</param>
 | |
|         /// <param name="channel">Channel to send on.</param>
 | |
|         [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | |
|         public void Broadcast<T>(NetworkObject networkObject, T message, bool requireAuthenticated = true, Channel channel = Channel.Reliable) where T : struct, IBroadcast
 | |
|         {
 | |
|             if (networkObject == null)
 | |
|             {
 | |
|                 NetworkManager.LogWarning($"Cannot send broadcast because networkObject is null.");
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             Broadcast(networkObject.Observers, message, requireAuthenticated, channel);
 | |
|         }
 | |
| 
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Sends a broadcast to all clients.
 | |
|         /// </summary>
 | |
|         /// <typeparam name="T">Type of broadcast to send.</typeparam>
 | |
|         /// <param name="message">Broadcast data being sent; for example: an instance of your broadcast type.</param>
 | |
|         /// <param name="requireAuthenticated">True if the clients must be authenticated for this broadcast to send.</param>
 | |
|         /// <param name="channel">Channel to send on.</param>
 | |
|         public void Broadcast<T>(T message, bool requireAuthenticated = true, Channel channel = Channel.Reliable) where T : struct, IBroadcast
 | |
|         {
 | |
|             if (!Started)
 | |
|             {
 | |
|                 NetworkManager.LogWarning($"Cannot send broadcast to client because server is not active.");
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             bool failedAuthentication = false;
 | |
|             using (PooledWriter writer = WriterPool.GetWriter())
 | |
|             {
 | |
|                 Broadcasts.WriteBroadcast<T>(writer, message, channel);
 | |
|                 ArraySegment<byte> segment = writer.GetArraySegment();
 | |
| 
 | |
|                 foreach (NetworkConnection conn in Clients.Values)
 | |
|                 {
 | |
|                     //
 | |
|                     if (requireAuthenticated && !conn.Authenticated)
 | |
|                         failedAuthentication = true;
 | |
|                     else
 | |
|                         NetworkManager.TransportManager.SendToClient((byte)channel, segment, conn);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if (failedAuthentication)
 | |
|             {
 | |
|                 NetworkManager.LogWarning($"One or more broadcast did not send to a client because they were not authenticated.");
 | |
|                 return;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|     }
 | |
| 
 | |
| 
 | |
| }
 |