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 GameKit.Dependencies.Utilities;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace FishNet.Managing.Server
{
public sealed partial class ServerManager : MonoBehaviour
{
#region Private.
///
/// Handler for registered broadcasts.
///
private readonly Dictionary _broadcastHandlers = new();
///
/// Connections which can be broadcasted to after having excluded removed.
///
private HashSet _connectionsWithoutExclusionsCache = new();
#endregion
///
/// Registers a method to call when a Broadcast arrives.
///
/// Type of broadcast being registered.
/// Method to call.
/// True if the client must be authenticated for the method to call.
public void RegisterBroadcast(Action handler, bool requireAuthentication = true) where T : struct, IBroadcast
{
if (handler == null)
{
NetworkManager.LogError($"Broadcast cannot be registered because handler is null. This may occur when trying to register to objects which require initialization, such as events.");
return;
}
ushort key = BroadcastExtensions.GetKey();
//Create new IBroadcastHandler if needed.
BroadcastHandlerBase bhs;
if (!_broadcastHandlers.TryGetValueIL2CPP(key, out bhs))
{
bhs = new ClientBroadcastHandler(requireAuthentication);
_broadcastHandlers.Add(key, bhs);
}
//Register handler to IBroadcastHandler.
bhs.RegisterHandler(handler);
}
///
/// Unregisters a method call from a Broadcast type.
///
/// Type of broadcast being unregistered.
/// Method to unregister.
public void UnregisterBroadcast(Action handler) where T : struct, IBroadcast
{
ushort key = BroadcastExtensions.GetKey();
if (_broadcastHandlers.TryGetValueIL2CPP(key, out BroadcastHandlerBase bhs))
bhs.UnregisterHandler(handler);
}
///
/// Parses a received broadcast.
///
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 BroadcastHandlerBase bhs))
{
if (bhs.RequireAuthentication && !conn.IsAuthenticated)
conn.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"ConnectionId {conn.ClientId} sent a broadcast which requires authentication, but client was not authenticated. Client has been disconnected.");
else
bhs.InvokeHandlers(conn, reader, channel);
}
else
{
reader.Skip(dataLength);
}
}
///
/// Sends a broadcast to a connection.
///
/// Type of broadcast to send.
/// Connection to send to.
/// Broadcast data being sent; for example: an instance of your broadcast type.
/// True if the client must be authenticated for this broadcast to send.
/// Channel to send on.
public void Broadcast(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.IsAuthenticated)
{
NetworkManager.LogWarning($"Cannot send broadcast to client because they are not authenticated.");
return;
}
PooledWriter writer = WriterPool.Retrieve();
BroadcastsSerializers.WriteBroadcast(NetworkManager, writer, message, ref channel);
ArraySegment segment = writer.GetArraySegment();
NetworkManager.TransportManager.SendToClient((byte)channel, segment, connection);
writer.Store();
}
///
/// Sends a broadcast to connections.
///
/// Type of broadcast to send.
/// Connections to send to.
/// Broadcast data being sent; for example: an instance of your broadcast type.
/// True if the clients must be authenticated for this broadcast to send.
/// Channel to send on.
public void Broadcast(HashSet 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;
PooledWriter writer = WriterPool.Retrieve();
BroadcastsSerializers.WriteBroadcast(NetworkManager, writer, message, ref channel);
ArraySegment segment = writer.GetArraySegment();
foreach (NetworkConnection conn in connections)
{
if (requireAuthenticated && !conn.IsAuthenticated)
failedAuthentication = true;
else
NetworkManager.TransportManager.SendToClient((byte)channel, segment, conn);
}
writer.Store();
if (failedAuthentication)
{
NetworkManager.LogWarning($"One or more broadcast did not send to a client because they were not authenticated.");
return;
}
}
///
/// Sends a broadcast to connections except excluded.
///
/// Type of broadcast to send.
/// Connections to send to.
/// Connection to exclude.
/// Broadcast data being sent; for example: an instance of your broadcast type.
/// True if the clients must be authenticated for this broadcast to send.
/// Channel to send on.
public void BroadcastExcept(HashSet 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);
}
///
/// Sends a broadcast to connections except excluded.
///
/// Type of broadcast to send.
/// Connections to send to.
/// Connections to exclude.
/// Broadcast data being sent; for example: an instance of your broadcast type.
/// True if the clients must be authenticated for this broadcast to send.
/// Channel to send on.
public void BroadcastExcept(HashSet connections, HashSet 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);
}
///
/// Sends a broadcast to all connections except excluded.
///
/// Type of broadcast to send.
/// Connection to exclude.
/// Broadcast data being sent; for example: an instance of your broadcast type.
/// True if the clients must be authenticated for this broadcast to send.
/// Channel to send on.
public void BroadcastExcept(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;
}
_connectionsWithoutExclusionsCache.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)
_connectionsWithoutExclusionsCache.Add(c);
//Remove
_connectionsWithoutExclusionsCache.Remove(excludedConnection);
Broadcast(_connectionsWithoutExclusionsCache, message, requireAuthenticated, channel);
}
///
/// Sends a broadcast to all connections except excluded.
///
/// Type of broadcast to send.
/// Connections to send to.
/// Broadcast data being sent; for example: an instance of your broadcast type.
/// True if the clients must be authenticated for this broadcast to send.
/// Channel to send on.
public void BroadcastExcept(HashSet 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;
}
_connectionsWithoutExclusionsCache.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)
_connectionsWithoutExclusionsCache.Add(c);
//Remove
foreach (NetworkConnection c in excludedConnections)
_connectionsWithoutExclusionsCache.Remove(c);
Broadcast(_connectionsWithoutExclusionsCache, message, requireAuthenticated, channel);
}
///
/// Sends a broadcast to observers.
///
/// Type of broadcast to send.
/// NetworkObject to use Observers from.
/// Broadcast data being sent; for example: an instance of your broadcast type.
/// True if the clients must be authenticated for this broadcast to send.
/// Channel to send on.
public void Broadcast(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);
}
///
/// Sends a broadcast to all clients.
///
/// Type of broadcast to send.
/// Broadcast data being sent; for example: an instance of your broadcast type.
/// True if the clients must be authenticated for this broadcast to send.
/// Channel to send on.
public void Broadcast(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;
PooledWriter writer = WriterPool.Retrieve();
BroadcastsSerializers.WriteBroadcast(NetworkManager, writer, message, ref channel);
ArraySegment segment = writer.GetArraySegment();
foreach (NetworkConnection conn in Clients.Values)
{
//
if (requireAuthenticated && !conn.IsAuthenticated)
failedAuthentication = true;
else
NetworkManager.TransportManager.SendToClient((byte)channel, segment, conn);
}
writer.Store();
if (failedAuthentication)
{
NetworkManager.LogWarning($"One or more broadcast did not send to a client because they were not authenticated.");
return;
}
}
}
}