From 490c15261d0b4a12f0c26e563ed3044c0ebaf520 Mon Sep 17 00:00:00 2001 From: EmirHanMamak Date: Fri, 16 Jan 2026 23:17:39 +0300 Subject: [PATCH] chore(network): add LiteNetLib to project --- Assets/Lib.meta | 8 + Assets/Lib/LiteNetLib.meta | 8 + Assets/Lib/LiteNetLib/BaseChannel.cs | 48 + Assets/Lib/LiteNetLib/BaseChannel.cs.meta | 2 + Assets/Lib/LiteNetLib/ConnectionRequest.cs | 134 ++ .../Lib/LiteNetLib/ConnectionRequest.cs.meta | 2 + Assets/Lib/LiteNetLib/INetEventListener.cs | 272 +++ .../Lib/LiteNetLib/INetEventListener.cs.meta | 2 + Assets/Lib/LiteNetLib/InternalPackets.cs | 151 ++ Assets/Lib/LiteNetLib/InternalPackets.cs.meta | 2 + Assets/Lib/LiteNetLib/Layers.meta | 8 + Assets/Lib/LiteNetLib/Layers/Crc32cLayer.cs | 41 + .../Lib/LiteNetLib/Layers/Crc32cLayer.cs.meta | 2 + .../Lib/LiteNetLib/Layers/PacketLayerBase.cs | 17 + .../LiteNetLib/Layers/PacketLayerBase.cs.meta | 2 + .../Lib/LiteNetLib/Layers/XorEncryptLayer.cs | 59 + .../LiteNetLib/Layers/XorEncryptLayer.cs.meta | 2 + Assets/Lib/LiteNetLib/LiteNetLib.asmdef | 13 + Assets/Lib/LiteNetLib/LiteNetLib.asmdef.meta | 7 + Assets/Lib/LiteNetLib/LiteNetLib.csproj.meta | 7 + Assets/Lib/LiteNetLib/NatPunchModule.cs | 264 +++ Assets/Lib/LiteNetLib/NatPunchModule.cs.meta | 2 + Assets/Lib/LiteNetLib/NativeSocket.cs | 179 ++ Assets/Lib/LiteNetLib/NativeSocket.cs.meta | 2 + Assets/Lib/LiteNetLib/NetConstants.cs | 78 + Assets/Lib/LiteNetLib/NetConstants.cs.meta | 2 + Assets/Lib/LiteNetLib/NetDebug.cs | 92 + Assets/Lib/LiteNetLib/NetDebug.cs.meta | 2 + Assets/Lib/LiteNetLib/NetManager.HashSet.cs | 322 +++ .../Lib/LiteNetLib/NetManager.HashSet.cs.meta | 2 + .../Lib/LiteNetLib/NetManager.PacketPool.cs | 82 + .../LiteNetLib/NetManager.PacketPool.cs.meta | 2 + Assets/Lib/LiteNetLib/NetManager.Socket.cs | 743 +++++++ .../Lib/LiteNetLib/NetManager.Socket.cs.meta | 2 + Assets/Lib/LiteNetLib/NetManager.cs | 1943 +++++++++++++++++ Assets/Lib/LiteNetLib/NetManager.cs.meta | 2 + Assets/Lib/LiteNetLib/NetPacket.cs | 164 ++ Assets/Lib/LiteNetLib/NetPacket.cs.meta | 2 + Assets/Lib/LiteNetLib/NetPeer.cs | 1489 +++++++++++++ Assets/Lib/LiteNetLib/NetPeer.cs.meta | 2 + Assets/Lib/LiteNetLib/NetStatistics.cs | 81 + Assets/Lib/LiteNetLib/NetStatistics.cs.meta | 2 + Assets/Lib/LiteNetLib/NetUtils.cs | 238 ++ Assets/Lib/LiteNetLib/NetUtils.cs.meta | 2 + Assets/Lib/LiteNetLib/PausedSocketFix.cs | 67 + Assets/Lib/LiteNetLib/PausedSocketFix.cs.meta | 2 + Assets/Lib/LiteNetLib/PooledPacket.cs | 32 + Assets/Lib/LiteNetLib/PooledPacket.cs.meta | 2 + Assets/Lib/LiteNetLib/ReliableChannel.cs | 337 +++ Assets/Lib/LiteNetLib/ReliableChannel.cs.meta | 2 + Assets/Lib/LiteNetLib/SequencedChannel.cs | 114 + .../Lib/LiteNetLib/SequencedChannel.cs.meta | 2 + Assets/Lib/LiteNetLib/Trimming.cs | 12 + Assets/Lib/LiteNetLib/Trimming.cs.meta | 2 + Assets/Lib/LiteNetLib/Utils.meta | 8 + Assets/Lib/LiteNetLib/Utils/CRC32C.cs | 150 ++ Assets/Lib/LiteNetLib/Utils/CRC32C.cs.meta | 2 + .../Lib/LiteNetLib/Utils/FastBitConverter.cs | 175 ++ .../LiteNetLib/Utils/FastBitConverter.cs.meta | 2 + .../Lib/LiteNetLib/Utils/INetSerializable.cs | 8 + .../LiteNetLib/Utils/INetSerializable.cs.meta | 2 + Assets/Lib/LiteNetLib/Utils/NetDataReader.cs | 797 +++++++ .../LiteNetLib/Utils/NetDataReader.cs.meta | 2 + Assets/Lib/LiteNetLib/Utils/NetDataWriter.cs | 449 ++++ .../LiteNetLib/Utils/NetDataWriter.cs.meta | 2 + .../LiteNetLib/Utils/NetPacketProcessor.cs | 288 +++ .../Utils/NetPacketProcessor.cs.meta | 2 + Assets/Lib/LiteNetLib/Utils/NetSerializer.cs | 770 +++++++ .../LiteNetLib/Utils/NetSerializer.cs.meta | 2 + Assets/Lib/LiteNetLib/Utils/NtpPacket.cs | 423 ++++ Assets/Lib/LiteNetLib/Utils/NtpPacket.cs.meta | 2 + Assets/Lib/LiteNetLib/Utils/NtpRequest.cs | 42 + .../Lib/LiteNetLib/Utils/NtpRequest.cs.meta | 2 + Assets/Lib/LiteNetLib/Utils/Preserve.cs | 12 + Assets/Lib/LiteNetLib/Utils/Preserve.cs.meta | 2 + Assets/Lib/LiteNetLib/package.json | 11 + Assets/Lib/LiteNetLib/package.json.meta | 7 + ProjectSettings/ProjectSettings.asset | 4 +- 78 files changed, 10221 insertions(+), 1 deletion(-) create mode 100644 Assets/Lib.meta create mode 100644 Assets/Lib/LiteNetLib.meta create mode 100644 Assets/Lib/LiteNetLib/BaseChannel.cs create mode 100644 Assets/Lib/LiteNetLib/BaseChannel.cs.meta create mode 100644 Assets/Lib/LiteNetLib/ConnectionRequest.cs create mode 100644 Assets/Lib/LiteNetLib/ConnectionRequest.cs.meta create mode 100644 Assets/Lib/LiteNetLib/INetEventListener.cs create mode 100644 Assets/Lib/LiteNetLib/INetEventListener.cs.meta create mode 100644 Assets/Lib/LiteNetLib/InternalPackets.cs create mode 100644 Assets/Lib/LiteNetLib/InternalPackets.cs.meta create mode 100644 Assets/Lib/LiteNetLib/Layers.meta create mode 100644 Assets/Lib/LiteNetLib/Layers/Crc32cLayer.cs create mode 100644 Assets/Lib/LiteNetLib/Layers/Crc32cLayer.cs.meta create mode 100644 Assets/Lib/LiteNetLib/Layers/PacketLayerBase.cs create mode 100644 Assets/Lib/LiteNetLib/Layers/PacketLayerBase.cs.meta create mode 100644 Assets/Lib/LiteNetLib/Layers/XorEncryptLayer.cs create mode 100644 Assets/Lib/LiteNetLib/Layers/XorEncryptLayer.cs.meta create mode 100644 Assets/Lib/LiteNetLib/LiteNetLib.asmdef create mode 100644 Assets/Lib/LiteNetLib/LiteNetLib.asmdef.meta create mode 100644 Assets/Lib/LiteNetLib/LiteNetLib.csproj.meta create mode 100644 Assets/Lib/LiteNetLib/NatPunchModule.cs create mode 100644 Assets/Lib/LiteNetLib/NatPunchModule.cs.meta create mode 100644 Assets/Lib/LiteNetLib/NativeSocket.cs create mode 100644 Assets/Lib/LiteNetLib/NativeSocket.cs.meta create mode 100644 Assets/Lib/LiteNetLib/NetConstants.cs create mode 100644 Assets/Lib/LiteNetLib/NetConstants.cs.meta create mode 100644 Assets/Lib/LiteNetLib/NetDebug.cs create mode 100644 Assets/Lib/LiteNetLib/NetDebug.cs.meta create mode 100644 Assets/Lib/LiteNetLib/NetManager.HashSet.cs create mode 100644 Assets/Lib/LiteNetLib/NetManager.HashSet.cs.meta create mode 100644 Assets/Lib/LiteNetLib/NetManager.PacketPool.cs create mode 100644 Assets/Lib/LiteNetLib/NetManager.PacketPool.cs.meta create mode 100644 Assets/Lib/LiteNetLib/NetManager.Socket.cs create mode 100644 Assets/Lib/LiteNetLib/NetManager.Socket.cs.meta create mode 100644 Assets/Lib/LiteNetLib/NetManager.cs create mode 100644 Assets/Lib/LiteNetLib/NetManager.cs.meta create mode 100644 Assets/Lib/LiteNetLib/NetPacket.cs create mode 100644 Assets/Lib/LiteNetLib/NetPacket.cs.meta create mode 100644 Assets/Lib/LiteNetLib/NetPeer.cs create mode 100644 Assets/Lib/LiteNetLib/NetPeer.cs.meta create mode 100644 Assets/Lib/LiteNetLib/NetStatistics.cs create mode 100644 Assets/Lib/LiteNetLib/NetStatistics.cs.meta create mode 100644 Assets/Lib/LiteNetLib/NetUtils.cs create mode 100644 Assets/Lib/LiteNetLib/NetUtils.cs.meta create mode 100644 Assets/Lib/LiteNetLib/PausedSocketFix.cs create mode 100644 Assets/Lib/LiteNetLib/PausedSocketFix.cs.meta create mode 100644 Assets/Lib/LiteNetLib/PooledPacket.cs create mode 100644 Assets/Lib/LiteNetLib/PooledPacket.cs.meta create mode 100644 Assets/Lib/LiteNetLib/ReliableChannel.cs create mode 100644 Assets/Lib/LiteNetLib/ReliableChannel.cs.meta create mode 100644 Assets/Lib/LiteNetLib/SequencedChannel.cs create mode 100644 Assets/Lib/LiteNetLib/SequencedChannel.cs.meta create mode 100644 Assets/Lib/LiteNetLib/Trimming.cs create mode 100644 Assets/Lib/LiteNetLib/Trimming.cs.meta create mode 100644 Assets/Lib/LiteNetLib/Utils.meta create mode 100644 Assets/Lib/LiteNetLib/Utils/CRC32C.cs create mode 100644 Assets/Lib/LiteNetLib/Utils/CRC32C.cs.meta create mode 100644 Assets/Lib/LiteNetLib/Utils/FastBitConverter.cs create mode 100644 Assets/Lib/LiteNetLib/Utils/FastBitConverter.cs.meta create mode 100644 Assets/Lib/LiteNetLib/Utils/INetSerializable.cs create mode 100644 Assets/Lib/LiteNetLib/Utils/INetSerializable.cs.meta create mode 100644 Assets/Lib/LiteNetLib/Utils/NetDataReader.cs create mode 100644 Assets/Lib/LiteNetLib/Utils/NetDataReader.cs.meta create mode 100644 Assets/Lib/LiteNetLib/Utils/NetDataWriter.cs create mode 100644 Assets/Lib/LiteNetLib/Utils/NetDataWriter.cs.meta create mode 100644 Assets/Lib/LiteNetLib/Utils/NetPacketProcessor.cs create mode 100644 Assets/Lib/LiteNetLib/Utils/NetPacketProcessor.cs.meta create mode 100644 Assets/Lib/LiteNetLib/Utils/NetSerializer.cs create mode 100644 Assets/Lib/LiteNetLib/Utils/NetSerializer.cs.meta create mode 100644 Assets/Lib/LiteNetLib/Utils/NtpPacket.cs create mode 100644 Assets/Lib/LiteNetLib/Utils/NtpPacket.cs.meta create mode 100644 Assets/Lib/LiteNetLib/Utils/NtpRequest.cs create mode 100644 Assets/Lib/LiteNetLib/Utils/NtpRequest.cs.meta create mode 100644 Assets/Lib/LiteNetLib/Utils/Preserve.cs create mode 100644 Assets/Lib/LiteNetLib/Utils/Preserve.cs.meta create mode 100644 Assets/Lib/LiteNetLib/package.json create mode 100644 Assets/Lib/LiteNetLib/package.json.meta diff --git a/Assets/Lib.meta b/Assets/Lib.meta new file mode 100644 index 0000000..3000893 --- /dev/null +++ b/Assets/Lib.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0737eee0ca13cd6439629e611b8da2a4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Lib/LiteNetLib.meta b/Assets/Lib/LiteNetLib.meta new file mode 100644 index 0000000..f1815f0 --- /dev/null +++ b/Assets/Lib/LiteNetLib.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4cbae4f02c11e5c4da1d63dd0f7c245f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Lib/LiteNetLib/BaseChannel.cs b/Assets/Lib/LiteNetLib/BaseChannel.cs new file mode 100644 index 0000000..b70c436 --- /dev/null +++ b/Assets/Lib/LiteNetLib/BaseChannel.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.Threading; + +namespace LiteNetLib +{ + internal abstract class BaseChannel + { + protected readonly NetPeer Peer; + protected readonly Queue OutgoingQueue = new Queue(NetConstants.DefaultWindowSize); + private int _isAddedToPeerChannelSendQueue; + + public int PacketsInQueue => OutgoingQueue.Count; + + protected BaseChannel(NetPeer peer) + { + Peer = peer; + } + + public void AddToQueue(NetPacket packet) + { + lock (OutgoingQueue) + { + OutgoingQueue.Enqueue(packet); + } + AddToPeerChannelSendQueue(); + } + + protected void AddToPeerChannelSendQueue() + { + if (Interlocked.CompareExchange(ref _isAddedToPeerChannelSendQueue, 1, 0) == 0) + { + Peer.AddToReliableChannelSendQueue(this); + } + } + + public bool SendAndCheckQueue() + { + bool hasPacketsToSend = SendNextPackets(); + if (!hasPacketsToSend) + Interlocked.Exchange(ref _isAddedToPeerChannelSendQueue, 0); + + return hasPacketsToSend; + } + + protected abstract bool SendNextPackets(); + public abstract bool ProcessPacket(NetPacket packet); + } +} diff --git a/Assets/Lib/LiteNetLib/BaseChannel.cs.meta b/Assets/Lib/LiteNetLib/BaseChannel.cs.meta new file mode 100644 index 0000000..95bd283 --- /dev/null +++ b/Assets/Lib/LiteNetLib/BaseChannel.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 382f306c9e98d4946b55289ca1f9426f \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/ConnectionRequest.cs b/Assets/Lib/LiteNetLib/ConnectionRequest.cs new file mode 100644 index 0000000..4a2cdd9 --- /dev/null +++ b/Assets/Lib/LiteNetLib/ConnectionRequest.cs @@ -0,0 +1,134 @@ +using System.Net; +using System.Threading; +using LiteNetLib.Utils; + +namespace LiteNetLib +{ + internal enum ConnectionRequestResult + { + None, + Accept, + Reject, + RejectForce + } + + public class ConnectionRequest + { + private readonly NetManager _listener; + private int _used; + + public NetDataReader Data => InternalPacket.Data; + + internal ConnectionRequestResult Result { get; private set; } + internal NetConnectRequestPacket InternalPacket; + + public readonly IPEndPoint RemoteEndPoint; + + internal void UpdateRequest(NetConnectRequestPacket connectRequest) + { + //old request + if (connectRequest.ConnectionTime < InternalPacket.ConnectionTime) + return; + + if (connectRequest.ConnectionTime == InternalPacket.ConnectionTime && + connectRequest.ConnectionNumber == InternalPacket.ConnectionNumber) + return; + + InternalPacket = connectRequest; + } + + private bool TryActivate() + { + return Interlocked.CompareExchange(ref _used, 1, 0) == 0; + } + + internal ConnectionRequest(IPEndPoint remoteEndPoint, NetConnectRequestPacket requestPacket, NetManager listener) + { + InternalPacket = requestPacket; + RemoteEndPoint = remoteEndPoint; + _listener = listener; + } + + public NetPeer AcceptIfKey(string key) + { + if (!TryActivate()) + return null; + try + { + if (Data.GetString() == key) + Result = ConnectionRequestResult.Accept; + } + catch + { + NetDebug.WriteError("[AC] Invalid incoming data"); + } + if (Result == ConnectionRequestResult.Accept) + return _listener.OnConnectionSolved(this, null, 0, 0); + + Result = ConnectionRequestResult.Reject; + _listener.OnConnectionSolved(this, null, 0, 0); + return null; + } + + /// + /// Accept connection and get new NetPeer as result + /// + /// Connected NetPeer + public NetPeer Accept() + { + if (!TryActivate()) + return null; + Result = ConnectionRequestResult.Accept; + return _listener.OnConnectionSolved(this, null, 0, 0); + } + + public void Reject(byte[] rejectData, int start, int length, bool force) + { + if (!TryActivate()) + return; + Result = force ? ConnectionRequestResult.RejectForce : ConnectionRequestResult.Reject; + _listener.OnConnectionSolved(this, rejectData, start, length); + } + + public void Reject(byte[] rejectData, int start, int length) + { + Reject(rejectData, start, length, false); + } + + + public void RejectForce(byte[] rejectData, int start, int length) + { + Reject(rejectData, start, length, true); + } + + public void RejectForce() + { + Reject(null, 0, 0, true); + } + + public void RejectForce(byte[] rejectData) + { + Reject(rejectData, 0, rejectData.Length, true); + } + + public void RejectForce(NetDataWriter rejectData) + { + Reject(rejectData.Data, 0, rejectData.Length, true); + } + + public void Reject() + { + Reject(null, 0, 0, false); + } + + public void Reject(byte[] rejectData) + { + Reject(rejectData, 0, rejectData.Length, false); + } + + public void Reject(NetDataWriter rejectData) + { + Reject(rejectData.Data, 0, rejectData.Length, false); + } + } +} diff --git a/Assets/Lib/LiteNetLib/ConnectionRequest.cs.meta b/Assets/Lib/LiteNetLib/ConnectionRequest.cs.meta new file mode 100644 index 0000000..6e9f5cf --- /dev/null +++ b/Assets/Lib/LiteNetLib/ConnectionRequest.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 15b75ccd895110d4b8b7b888ef07e57f \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/INetEventListener.cs b/Assets/Lib/LiteNetLib/INetEventListener.cs new file mode 100644 index 0000000..13d8852 --- /dev/null +++ b/Assets/Lib/LiteNetLib/INetEventListener.cs @@ -0,0 +1,272 @@ +using System.Net; +using System.Net.Sockets; +using LiteNetLib.Utils; + +namespace LiteNetLib +{ + /// + /// Type of message that you receive in OnNetworkReceiveUnconnected event + /// + public enum UnconnectedMessageType + { + BasicMessage, + Broadcast + } + + /// + /// Disconnect reason that you receive in OnPeerDisconnected event + /// + public enum DisconnectReason + { + ConnectionFailed, + Timeout, + HostUnreachable, + NetworkUnreachable, + RemoteConnectionClose, + DisconnectPeerCalled, + ConnectionRejected, + InvalidProtocol, + UnknownHost, + Reconnect, + PeerToPeerConnection, + PeerNotFound + } + + /// + /// Additional information about disconnection + /// + public struct DisconnectInfo + { + /// + /// Additional info why peer disconnected + /// + public DisconnectReason Reason; + + /// + /// Error code (if reason is SocketSendError or SocketReceiveError) + /// + public SocketError SocketErrorCode; + + /// + /// Additional data that can be accessed (only if reason is RemoteConnectionClose) + /// + public NetPacketReader AdditionalData; + } + + public interface INetEventListener + { + /// + /// New remote peer connected to host, or client connected to remote host + /// + /// Connected peer object + void OnPeerConnected(NetPeer peer); + + /// + /// Peer disconnected + /// + /// disconnected peer + /// additional info about reason, errorCode or data received with disconnect message + void OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo); + + /// + /// Network error (on send or receive) + /// + /// From endPoint (can be null) + /// Socket error + void OnNetworkError(IPEndPoint endPoint, SocketError socketError); + + /// + /// Received some data + /// + /// From peer + /// DataReader containing all received data + /// Number of channel at which packet arrived + /// Type of received packet + void OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channelNumber, DeliveryMethod deliveryMethod); + + /// + /// Received unconnected message + /// + /// From address (IP and Port) + /// Message data + /// Message type (simple, discovery request or response) + void OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType); + + /// + /// Latency information updated + /// + /// Peer with updated latency + /// latency value in milliseconds + void OnNetworkLatencyUpdate(NetPeer peer, int latency); + + /// + /// On peer connection requested + /// + /// Request information (EndPoint, internal id, additional data) + void OnConnectionRequest(ConnectionRequest request); + } + + public interface IDeliveryEventListener + { + /// + /// On reliable message delivered + /// + /// + /// + void OnMessageDelivered(NetPeer peer, object userData); + } + + public interface INtpEventListener + { + /// + /// Ntp response + /// + /// + void OnNtpResponse(NtpPacket packet); + } + + public interface IPeerAddressChangedListener + { + /// + /// Called when peer address changed (when AllowPeerAddressChange is enabled) + /// + /// Peer that changed address (with new address) + /// previous IP + void OnPeerAddressChanged(NetPeer peer, IPEndPoint previousAddress); + } + + public class EventBasedNetListener : INetEventListener, IDeliveryEventListener, INtpEventListener, IPeerAddressChangedListener + { + public delegate void OnPeerConnected(NetPeer peer); + public delegate void OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo); + public delegate void OnNetworkError(IPEndPoint endPoint, SocketError socketError); + public delegate void OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channel, DeliveryMethod deliveryMethod); + public delegate void OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType); + public delegate void OnNetworkLatencyUpdate(NetPeer peer, int latency); + public delegate void OnConnectionRequest(ConnectionRequest request); + public delegate void OnDeliveryEvent(NetPeer peer, object userData); + public delegate void OnNtpResponseEvent(NtpPacket packet); + public delegate void OnPeerAddressChangedEvent(NetPeer peer, IPEndPoint previousAddress); + + public event OnPeerConnected PeerConnectedEvent; + public event OnPeerDisconnected PeerDisconnectedEvent; + public event OnNetworkError NetworkErrorEvent; + public event OnNetworkReceive NetworkReceiveEvent; + public event OnNetworkReceiveUnconnected NetworkReceiveUnconnectedEvent; + public event OnNetworkLatencyUpdate NetworkLatencyUpdateEvent; + public event OnConnectionRequest ConnectionRequestEvent; + public event OnDeliveryEvent DeliveryEvent; + public event OnNtpResponseEvent NtpResponseEvent; + public event OnPeerAddressChangedEvent PeerAddressChangedEvent; + + public void ClearPeerConnectedEvent() + { + PeerConnectedEvent = null; + } + + public void ClearPeerDisconnectedEvent() + { + PeerDisconnectedEvent = null; + } + + public void ClearNetworkErrorEvent() + { + NetworkErrorEvent = null; + } + + public void ClearNetworkReceiveEvent() + { + NetworkReceiveEvent = null; + } + + public void ClearNetworkReceiveUnconnectedEvent() + { + NetworkReceiveUnconnectedEvent = null; + } + + public void ClearNetworkLatencyUpdateEvent() + { + NetworkLatencyUpdateEvent = null; + } + + public void ClearConnectionRequestEvent() + { + ConnectionRequestEvent = null; + } + + public void ClearDeliveryEvent() + { + DeliveryEvent = null; + } + + public void ClearNtpResponseEvent() + { + NtpResponseEvent = null; + } + + public void ClearPeerAddressChangedEvent() + { + PeerAddressChangedEvent = null; + } + + void INetEventListener.OnPeerConnected(NetPeer peer) + { + if (PeerConnectedEvent != null) + PeerConnectedEvent(peer); + } + + void INetEventListener.OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo) + { + if (PeerDisconnectedEvent != null) + PeerDisconnectedEvent(peer, disconnectInfo); + } + + void INetEventListener.OnNetworkError(IPEndPoint endPoint, SocketError socketErrorCode) + { + if (NetworkErrorEvent != null) + NetworkErrorEvent(endPoint, socketErrorCode); + } + + void INetEventListener.OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channelNumber, DeliveryMethod deliveryMethod) + { + if (NetworkReceiveEvent != null) + NetworkReceiveEvent(peer, reader, channelNumber, deliveryMethod); + } + + void INetEventListener.OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType) + { + if (NetworkReceiveUnconnectedEvent != null) + NetworkReceiveUnconnectedEvent(remoteEndPoint, reader, messageType); + } + + void INetEventListener.OnNetworkLatencyUpdate(NetPeer peer, int latency) + { + if (NetworkLatencyUpdateEvent != null) + NetworkLatencyUpdateEvent(peer, latency); + } + + void INetEventListener.OnConnectionRequest(ConnectionRequest request) + { + if (ConnectionRequestEvent != null) + ConnectionRequestEvent(request); + } + + void IDeliveryEventListener.OnMessageDelivered(NetPeer peer, object userData) + { + if (DeliveryEvent != null) + DeliveryEvent(peer, userData); + } + + void INtpEventListener.OnNtpResponse(NtpPacket packet) + { + if (NtpResponseEvent != null) + NtpResponseEvent(packet); + } + + void IPeerAddressChangedListener.OnPeerAddressChanged(NetPeer peer, IPEndPoint previousAddress) + { + if (PeerAddressChangedEvent != null) + PeerAddressChangedEvent(peer, previousAddress); + } + } +} diff --git a/Assets/Lib/LiteNetLib/INetEventListener.cs.meta b/Assets/Lib/LiteNetLib/INetEventListener.cs.meta new file mode 100644 index 0000000..3be0cee --- /dev/null +++ b/Assets/Lib/LiteNetLib/INetEventListener.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a20db216008abd145abdc1ebbf305f0c \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/InternalPackets.cs b/Assets/Lib/LiteNetLib/InternalPackets.cs new file mode 100644 index 0000000..504bf32 --- /dev/null +++ b/Assets/Lib/LiteNetLib/InternalPackets.cs @@ -0,0 +1,151 @@ +using System; +using System.Net; +using LiteNetLib.Utils; + +namespace LiteNetLib +{ + internal sealed class NetConnectRequestPacket + { + public const int HeaderSize = 18; + public readonly long ConnectionTime; + public byte ConnectionNumber; + public readonly byte[] TargetAddress; + public readonly NetDataReader Data; + public readonly int PeerId; + + private NetConnectRequestPacket(long connectionTime, byte connectionNumber, int localId, byte[] targetAddress, NetDataReader data) + { + ConnectionTime = connectionTime; + ConnectionNumber = connectionNumber; + TargetAddress = targetAddress; + Data = data; + PeerId = localId; + } + + public static int GetProtocolId(NetPacket packet) + { + return BitConverter.ToInt32(packet.RawData, 1); + } + + public static NetConnectRequestPacket FromData(NetPacket packet) + { + if (packet.ConnectionNumber >= NetConstants.MaxConnectionNumber) + return null; + + //Getting connection time for peer + long connectionTime = BitConverter.ToInt64(packet.RawData, 5); + + //Get peer id + int peerId = BitConverter.ToInt32(packet.RawData, 13); + + //Get target address + int addrSize = packet.RawData[HeaderSize-1]; + if (addrSize != 16 && addrSize != 28) + return null; + byte[] addressBytes = new byte[addrSize]; + Buffer.BlockCopy(packet.RawData, HeaderSize, addressBytes, 0, addrSize); + + // Read data and create request + var reader = new NetDataReader(null, 0, 0); + if (packet.Size > HeaderSize+addrSize) + reader.SetSource(packet.RawData, HeaderSize + addrSize, packet.Size); + + return new NetConnectRequestPacket(connectionTime, packet.ConnectionNumber, peerId, addressBytes, reader); + } + + public static NetPacket Make(NetDataWriter connectData, SocketAddress addressBytes, long connectTime, int localId) + { + //Make initial packet + var packet = new NetPacket(PacketProperty.ConnectRequest, connectData.Length+addressBytes.Size); + + //Add data + FastBitConverter.GetBytes(packet.RawData, 1, NetConstants.ProtocolId); + FastBitConverter.GetBytes(packet.RawData, 5, connectTime); + FastBitConverter.GetBytes(packet.RawData, 13, localId); + packet.RawData[HeaderSize-1] = (byte)addressBytes.Size; + for (int i = 0; i < addressBytes.Size; i++) + packet.RawData[HeaderSize + i] = addressBytes[i]; + Buffer.BlockCopy(connectData.Data, 0, packet.RawData, HeaderSize + addressBytes.Size, connectData.Length); + return packet; + } + +#if LITENETLIB_SPANS || NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1 || NETCOREAPP3_1 || NET5_0 || NETSTANDARD2_1 + public static NetPacket Make(ReadOnlySpan connectData, SocketAddress addressBytes, long connectTime, int localId) + { + //Make initial packet + var packet = new NetPacket(PacketProperty.ConnectRequest, connectData.Length+addressBytes.Size); + + //Add data + FastBitConverter.GetBytes(packet.RawData, 1, NetConstants.ProtocolId); + FastBitConverter.GetBytes(packet.RawData, 5, connectTime); + FastBitConverter.GetBytes(packet.RawData, 13, localId); + packet.RawData[HeaderSize - 1] = (byte)addressBytes.Size; + for (int i = 0; i < addressBytes.Size; i++) + packet.RawData[HeaderSize + i] = addressBytes[i]; + connectData.CopyTo(packet.RawData.AsSpan(HeaderSize + addressBytes.Size)); + return packet; + } +#endif + } + + internal sealed class NetConnectAcceptPacket + { + public const int Size = 15; + public readonly long ConnectionTime; + public readonly byte ConnectionNumber; + public readonly int PeerId; + public readonly bool PeerNetworkChanged; + + private NetConnectAcceptPacket(long connectionTime, byte connectionNumber, int peerId, bool peerNetworkChanged) + { + ConnectionTime = connectionTime; + ConnectionNumber = connectionNumber; + PeerId = peerId; + PeerNetworkChanged = peerNetworkChanged; + } + + public static NetConnectAcceptPacket FromData(NetPacket packet) + { + if (packet.Size != Size) + return null; + + long connectionId = BitConverter.ToInt64(packet.RawData, 1); + + //check connect num + byte connectionNumber = packet.RawData[9]; + if (connectionNumber >= NetConstants.MaxConnectionNumber) + return null; + + //check reused flag + byte isReused = packet.RawData[10]; + if (isReused > 1) + return null; + + //get remote peer id + int peerId = BitConverter.ToInt32(packet.RawData, 11); + if (peerId < 0) + return null; + + return new NetConnectAcceptPacket(connectionId, connectionNumber, peerId, isReused == 1); + } + + public static NetPacket Make(long connectTime, byte connectNum, int localPeerId) + { + var packet = new NetPacket(PacketProperty.ConnectAccept, 0); + FastBitConverter.GetBytes(packet.RawData, 1, connectTime); + packet.RawData[9] = connectNum; + FastBitConverter.GetBytes(packet.RawData, 11, localPeerId); + return packet; + } + + public static NetPacket MakeNetworkChanged(NetPeer peer) + { + var packet = new NetPacket(PacketProperty.PeerNotFound, Size-1); + FastBitConverter.GetBytes(packet.RawData, 1, peer.ConnectTime); + packet.RawData[9] = peer.ConnectionNum; + packet.RawData[10] = 1; + FastBitConverter.GetBytes(packet.RawData, 11, peer.RemoteId); + return packet; + } + } +} diff --git a/Assets/Lib/LiteNetLib/InternalPackets.cs.meta b/Assets/Lib/LiteNetLib/InternalPackets.cs.meta new file mode 100644 index 0000000..a57a94d --- /dev/null +++ b/Assets/Lib/LiteNetLib/InternalPackets.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b75f0f21bf404ea4f8f0921341ddbc1b \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/Layers.meta b/Assets/Lib/LiteNetLib/Layers.meta new file mode 100644 index 0000000..d65a1af --- /dev/null +++ b/Assets/Lib/LiteNetLib/Layers.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ca14be937aaf861498878f6766c6d31f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Lib/LiteNetLib/Layers/Crc32cLayer.cs b/Assets/Lib/LiteNetLib/Layers/Crc32cLayer.cs new file mode 100644 index 0000000..dc07fe1 --- /dev/null +++ b/Assets/Lib/LiteNetLib/Layers/Crc32cLayer.cs @@ -0,0 +1,41 @@ +using LiteNetLib.Utils; +using System; +using System.Net; + +namespace LiteNetLib.Layers +{ + public sealed class Crc32cLayer : PacketLayerBase + { + public Crc32cLayer() : base(CRC32C.ChecksumSize) + { + + } + + public override void ProcessInboundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int length) + { + if (length < NetConstants.HeaderSize + CRC32C.ChecksumSize) + { + NetDebug.WriteError("[NM] DataReceived size: bad!"); + //Set length to 0 to have netManager drop the packet. + length = 0; + return; + } + + int checksumPoint = length - CRC32C.ChecksumSize; + if (CRC32C.Compute(data, 0, checksumPoint) != BitConverter.ToUInt32(data, checksumPoint)) + { + NetDebug.Write("[NM] DataReceived checksum: bad!"); + //Set length to 0 to have netManager drop the packet. + length = 0; + return; + } + length -= CRC32C.ChecksumSize; + } + + public override void ProcessOutBoundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int offset, ref int length) + { + FastBitConverter.GetBytes(data, length, CRC32C.Compute(data, offset, length)); + length += CRC32C.ChecksumSize; + } + } +} diff --git a/Assets/Lib/LiteNetLib/Layers/Crc32cLayer.cs.meta b/Assets/Lib/LiteNetLib/Layers/Crc32cLayer.cs.meta new file mode 100644 index 0000000..a29477b --- /dev/null +++ b/Assets/Lib/LiteNetLib/Layers/Crc32cLayer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a3b0cf665c7a79147b5950f15975d5fd \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/Layers/PacketLayerBase.cs b/Assets/Lib/LiteNetLib/Layers/PacketLayerBase.cs new file mode 100644 index 0000000..f2e4c88 --- /dev/null +++ b/Assets/Lib/LiteNetLib/Layers/PacketLayerBase.cs @@ -0,0 +1,17 @@ +using System.Net; + +namespace LiteNetLib.Layers +{ + public abstract class PacketLayerBase + { + public readonly int ExtraPacketSizeForLayer; + + protected PacketLayerBase(int extraPacketSizeForLayer) + { + ExtraPacketSizeForLayer = extraPacketSizeForLayer; + } + + public abstract void ProcessInboundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int length); + public abstract void ProcessOutBoundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int offset, ref int length); + } +} diff --git a/Assets/Lib/LiteNetLib/Layers/PacketLayerBase.cs.meta b/Assets/Lib/LiteNetLib/Layers/PacketLayerBase.cs.meta new file mode 100644 index 0000000..6dc4f0a --- /dev/null +++ b/Assets/Lib/LiteNetLib/Layers/PacketLayerBase.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e6dd115bb85e6f247aee518e1730132a \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/Layers/XorEncryptLayer.cs b/Assets/Lib/LiteNetLib/Layers/XorEncryptLayer.cs new file mode 100644 index 0000000..a3a130b --- /dev/null +++ b/Assets/Lib/LiteNetLib/Layers/XorEncryptLayer.cs @@ -0,0 +1,59 @@ +using System; +using System.Net; +using System.Text; + +namespace LiteNetLib.Layers +{ + public class XorEncryptLayer : PacketLayerBase + { + private byte[] _byteKey; + + public XorEncryptLayer() : base(0) + { + + } + + public XorEncryptLayer(byte[] key) : this() + { + SetKey(key); + } + + public XorEncryptLayer(string key) : this() + { + SetKey(key); + } + + public void SetKey(string key) + { + _byteKey = Encoding.UTF8.GetBytes(key); + } + + public void SetKey(byte[] key) + { + if (_byteKey == null || _byteKey.Length != key.Length) + _byteKey = new byte[key.Length]; + Buffer.BlockCopy(key, 0, _byteKey, 0, key.Length); + } + + public override void ProcessInboundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int length) + { + if (_byteKey == null) + return; + for (int i = 0; i < length; i++) + { + data[i] = (byte)(data[i] ^ _byteKey[i % _byteKey.Length]); + } + } + + public override void ProcessOutBoundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int offset, ref int length) + { + if (_byteKey == null) + return; + int cur = offset; + for (int i = 0; i < length; i++, cur++) + { + data[cur] = (byte)(data[cur] ^ _byteKey[i % _byteKey.Length]); + } + } + } +} diff --git a/Assets/Lib/LiteNetLib/Layers/XorEncryptLayer.cs.meta b/Assets/Lib/LiteNetLib/Layers/XorEncryptLayer.cs.meta new file mode 100644 index 0000000..82902b5 --- /dev/null +++ b/Assets/Lib/LiteNetLib/Layers/XorEncryptLayer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a2b7ba67611167844b4680f69c77d8e7 \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/LiteNetLib.asmdef b/Assets/Lib/LiteNetLib/LiteNetLib.asmdef new file mode 100644 index 0000000..530c72e --- /dev/null +++ b/Assets/Lib/LiteNetLib/LiteNetLib.asmdef @@ -0,0 +1,13 @@ +{ + "name": "LiteNetLib", + "references": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/LiteNetLib.asmdef.meta b/Assets/Lib/LiteNetLib/LiteNetLib.asmdef.meta new file mode 100644 index 0000000..c86ebbb --- /dev/null +++ b/Assets/Lib/LiteNetLib/LiteNetLib.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 752efba0c615f924f9d20616b1a8ba1d +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Lib/LiteNetLib/LiteNetLib.csproj.meta b/Assets/Lib/LiteNetLib/LiteNetLib.csproj.meta new file mode 100644 index 0000000..7748c47 --- /dev/null +++ b/Assets/Lib/LiteNetLib/LiteNetLib.csproj.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 555ad056044deb94097c76e6d7e2ffa9 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Lib/LiteNetLib/NatPunchModule.cs b/Assets/Lib/LiteNetLib/NatPunchModule.cs new file mode 100644 index 0000000..fdd156f --- /dev/null +++ b/Assets/Lib/LiteNetLib/NatPunchModule.cs @@ -0,0 +1,264 @@ +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using System.Net; +using System.Net.Sockets; +using LiteNetLib.Utils; + +namespace LiteNetLib +{ + public enum NatAddressType + { + Internal, + External + } + + public interface INatPunchListener + { + void OnNatIntroductionRequest(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, string token); + void OnNatIntroductionSuccess(IPEndPoint targetEndPoint, NatAddressType type, string token); + } + + public class EventBasedNatPunchListener : INatPunchListener + { + public delegate void OnNatIntroductionRequest(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, string token); + public delegate void OnNatIntroductionSuccess(IPEndPoint targetEndPoint, NatAddressType type, string token); + + public event OnNatIntroductionRequest NatIntroductionRequest; + public event OnNatIntroductionSuccess NatIntroductionSuccess; + + void INatPunchListener.OnNatIntroductionRequest(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, string token) + { + if(NatIntroductionRequest != null) + NatIntroductionRequest(localEndPoint, remoteEndPoint, token); + } + + void INatPunchListener.OnNatIntroductionSuccess(IPEndPoint targetEndPoint, NatAddressType type, string token) + { + if (NatIntroductionSuccess != null) + NatIntroductionSuccess(targetEndPoint, type, token); + } + } + + /// + /// Module for UDP NAT Hole punching operations. Can be accessed from NetManager + /// + public sealed class NatPunchModule + { + struct RequestEventData + { + public IPEndPoint LocalEndPoint; + public IPEndPoint RemoteEndPoint; + public string Token; + } + + struct SuccessEventData + { + public IPEndPoint TargetEndPoint; + public NatAddressType Type; + public string Token; + } + + class NatIntroduceRequestPacket + { + public IPEndPoint Internal { [Preserve] get; [Preserve] set; } + public string Token { [Preserve] get; [Preserve] set; } + } + + class NatIntroduceResponsePacket + { + public IPEndPoint Internal { [Preserve] get; [Preserve] set; } + public IPEndPoint External { [Preserve] get; [Preserve] set; } + public string Token { [Preserve] get; [Preserve] set; } + } + + class NatPunchPacket + { + public string Token { [Preserve] get; [Preserve] set; } + public bool IsExternal { [Preserve] get; [Preserve] set; } + } + + private readonly NetManager _socket; + private readonly ConcurrentQueue _requestEvents = new ConcurrentQueue(); + private readonly ConcurrentQueue _successEvents = new ConcurrentQueue(); + private readonly NetDataReader _cacheReader = new NetDataReader(); + private readonly NetDataWriter _cacheWriter = new NetDataWriter(); + private readonly NetPacketProcessor _netPacketProcessor = new NetPacketProcessor(MaxTokenLength); + private INatPunchListener _natPunchListener; + public const int MaxTokenLength = 256; + + /// + /// Events automatically will be called without PollEvents method from another thread + /// + public bool UnsyncedEvents = false; + + internal NatPunchModule(NetManager socket) + { + _socket = socket; + _netPacketProcessor.SubscribeReusable(OnNatIntroductionResponse); + _netPacketProcessor.SubscribeReusable(OnNatIntroductionRequest); + _netPacketProcessor.SubscribeReusable(OnNatPunch); + } + + internal void ProcessMessage(IPEndPoint senderEndPoint, NetPacket packet) + { + lock (_cacheReader) + { + _cacheReader.SetSource(packet.RawData, NetConstants.HeaderSize, packet.Size); + _netPacketProcessor.ReadAllPackets(_cacheReader, senderEndPoint); + } + } + + public void Init(INatPunchListener listener) + { + _natPunchListener = listener; + } + + private void Send< +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers(Trimming.SerializerMemberTypes)] +#endif + T>(T packet, IPEndPoint target) where T : class, new() + { + _cacheWriter.Reset(); + _cacheWriter.Put((byte)PacketProperty.NatMessage); + _netPacketProcessor.Write(_cacheWriter, packet); + _socket.SendRaw(_cacheWriter.Data, 0, _cacheWriter.Length, target); + } + + public void NatIntroduce( + IPEndPoint hostInternal, + IPEndPoint hostExternal, + IPEndPoint clientInternal, + IPEndPoint clientExternal, + string additionalInfo) + { + var req = new NatIntroduceResponsePacket + { + Token = additionalInfo + }; + + //First packet (server) send to client + req.Internal = hostInternal; + req.External = hostExternal; + Send(req, clientExternal); + + //Second packet (client) send to server + req.Internal = clientInternal; + req.External = clientExternal; + Send(req, hostExternal); + } + + public void PollEvents() + { + if (UnsyncedEvents) + return; + + if (_natPunchListener == null || (_successEvents.IsEmpty && _requestEvents.IsEmpty)) + return; + + while (_successEvents.TryDequeue(out var evt)) + { + _natPunchListener.OnNatIntroductionSuccess( + evt.TargetEndPoint, + evt.Type, + evt.Token); + } + + while (_requestEvents.TryDequeue(out var evt)) + { + _natPunchListener.OnNatIntroductionRequest(evt.LocalEndPoint, evt.RemoteEndPoint, evt.Token); + } + } + + public void SendNatIntroduceRequest(string host, int port, string additionalInfo) + { + SendNatIntroduceRequest(NetUtils.MakeEndPoint(host, port), additionalInfo); + } + + public void SendNatIntroduceRequest(IPEndPoint masterServerEndPoint, string additionalInfo) + { + //prepare outgoing data + string networkIp = NetUtils.GetLocalIp(LocalAddrType.IPv4); + if (string.IsNullOrEmpty(networkIp) || masterServerEndPoint.AddressFamily == AddressFamily.InterNetworkV6) + { + networkIp = NetUtils.GetLocalIp(LocalAddrType.IPv6); + } + + Send( + new NatIntroduceRequestPacket + { + Internal = NetUtils.MakeEndPoint(networkIp, _socket.LocalPort), + Token = additionalInfo + }, + masterServerEndPoint); + } + + //We got request and must introduce + private void OnNatIntroductionRequest(NatIntroduceRequestPacket req, IPEndPoint senderEndPoint) + { + if (UnsyncedEvents) + { + _natPunchListener.OnNatIntroductionRequest( + req.Internal, + senderEndPoint, + req.Token); + } + else + { + _requestEvents.Enqueue(new RequestEventData + { + LocalEndPoint = req.Internal, + RemoteEndPoint = senderEndPoint, + Token = req.Token + }); + } + } + + //We got introduce and must punch + private void OnNatIntroductionResponse(NatIntroduceResponsePacket req) + { + NetDebug.Write(NetLogLevel.Trace, "[NAT] introduction received"); + + // send internal punch + var punchPacket = new NatPunchPacket {Token = req.Token}; + Send(punchPacket, req.Internal); + NetDebug.Write(NetLogLevel.Trace, $"[NAT] internal punch sent to {req.Internal}"); + + // hack for some routers + _socket.Ttl = 2; + _socket.SendRaw(new[] { (byte)PacketProperty.Empty }, 0, 1, req.External); + + // send external punch + _socket.Ttl = NetConstants.SocketTTL; + punchPacket.IsExternal = true; + Send(punchPacket, req.External); + NetDebug.Write(NetLogLevel.Trace, $"[NAT] external punch sent to {req.External}"); + } + + //We got punch and can connect + private void OnNatPunch(NatPunchPacket req, IPEndPoint senderEndPoint) + { + //Read info + NetDebug.Write(NetLogLevel.Trace, $"[NAT] punch received from {senderEndPoint} - additional info: {req.Token}"); + + //Release punch success to client; enabling him to Connect() to Sender if token is ok + if(UnsyncedEvents) + { + _natPunchListener.OnNatIntroductionSuccess( + senderEndPoint, + req.IsExternal ? NatAddressType.External : NatAddressType.Internal, + req.Token + ); + } + else + { + _successEvents.Enqueue(new SuccessEventData + { + TargetEndPoint = senderEndPoint, + Type = req.IsExternal ? NatAddressType.External : NatAddressType.Internal, + Token = req.Token + }); + } + } + } +} diff --git a/Assets/Lib/LiteNetLib/NatPunchModule.cs.meta b/Assets/Lib/LiteNetLib/NatPunchModule.cs.meta new file mode 100644 index 0000000..ff5b9cc --- /dev/null +++ b/Assets/Lib/LiteNetLib/NatPunchModule.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b8e382ee187fdab488009012d849045c \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/NativeSocket.cs b/Assets/Lib/LiteNetLib/NativeSocket.cs new file mode 100644 index 0000000..e2959cb --- /dev/null +++ b/Assets/Lib/LiteNetLib/NativeSocket.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace LiteNetLib +{ + internal static class NativeSocket + { + static unsafe class WinSock + { + private const string LibName = "ws2_32.dll"; + + [DllImport(LibName, SetLastError = true)] + public static extern int recvfrom( + IntPtr socketHandle, + [In, Out] byte[] pinnedBuffer, + [In] int len, + [In] SocketFlags socketFlags, + [Out] byte[] socketAddress, + [In, Out] ref int socketAddressSize); + + [DllImport(LibName, SetLastError = true)] + internal static extern int sendto( + IntPtr socketHandle, + byte* pinnedBuffer, + [In] int len, + [In] SocketFlags socketFlags, + [In] byte[] socketAddress, + [In] int socketAddressSize); + } + + static unsafe class UnixSock + { + private const string LibName = "libc"; + + [DllImport(LibName, SetLastError = true)] + public static extern int recvfrom( + IntPtr socketHandle, + [In, Out] byte[] pinnedBuffer, + [In] int len, + [In] SocketFlags socketFlags, + [Out] byte[] socketAddress, + [In, Out] ref int socketAddressSize); + + [DllImport(LibName, SetLastError = true)] + internal static extern int sendto( + IntPtr socketHandle, + byte* pinnedBuffer, + [In] int len, + [In] SocketFlags socketFlags, + [In] byte[] socketAddress, + [In] int socketAddressSize); + } + + public static readonly bool IsSupported = false; + public static readonly bool UnixMode = false; + + public const int IPv4AddrSize = 16; + public const int IPv6AddrSize = 28; + public const int AF_INET = 2; + public const int AF_INET6 = 10; + + private static readonly Dictionary NativeErrorToSocketError = new Dictionary + { + { 13, SocketError.AccessDenied }, //EACCES + { 98, SocketError.AddressAlreadyInUse }, //EADDRINUSE + { 99, SocketError.AddressNotAvailable }, //EADDRNOTAVAIL + { 97, SocketError.AddressFamilyNotSupported }, //EAFNOSUPPORT + { 11, SocketError.WouldBlock }, //EAGAIN + { 114, SocketError.AlreadyInProgress }, //EALREADY + { 9, SocketError.OperationAborted }, //EBADF + { 125, SocketError.OperationAborted }, //ECANCELED + { 103, SocketError.ConnectionAborted }, //ECONNABORTED + { 111, SocketError.ConnectionRefused }, //ECONNREFUSED + { 104, SocketError.ConnectionReset }, //ECONNRESET + { 89, SocketError.DestinationAddressRequired }, //EDESTADDRREQ + { 14, SocketError.Fault }, //EFAULT + { 112, SocketError.HostDown }, //EHOSTDOWN + { 6, SocketError.HostNotFound }, //ENXIO + { 113, SocketError.HostUnreachable }, //EHOSTUNREACH + { 115, SocketError.InProgress }, //EINPROGRESS + { 4, SocketError.Interrupted }, //EINTR + { 22, SocketError.InvalidArgument }, //EINVAL + { 106, SocketError.IsConnected }, //EISCONN + { 24, SocketError.TooManyOpenSockets }, //EMFILE + { 90, SocketError.MessageSize }, //EMSGSIZE + { 100, SocketError.NetworkDown }, //ENETDOWN + { 102, SocketError.NetworkReset }, //ENETRESET + { 101, SocketError.NetworkUnreachable }, //ENETUNREACH + { 23, SocketError.TooManyOpenSockets }, //ENFILE + { 105, SocketError.NoBufferSpaceAvailable }, //ENOBUFS + { 61, SocketError.NoData }, //ENODATA + { 2, SocketError.AddressNotAvailable }, //ENOENT + { 92, SocketError.ProtocolOption }, //ENOPROTOOPT + { 107, SocketError.NotConnected }, //ENOTCONN + { 88, SocketError.NotSocket }, //ENOTSOCK + { 3440, SocketError.OperationNotSupported }, //ENOTSUP + { 1, SocketError.AccessDenied }, //EPERM + { 32, SocketError.Shutdown }, //EPIPE + { 96, SocketError.ProtocolFamilyNotSupported }, //EPFNOSUPPORT + { 93, SocketError.ProtocolNotSupported }, //EPROTONOSUPPORT + { 91, SocketError.ProtocolType }, //EPROTOTYPE + { 94, SocketError.SocketNotSupported }, //ESOCKTNOSUPPORT + { 108, SocketError.Disconnecting }, //ESHUTDOWN + { 110, SocketError.TimedOut }, //ETIMEDOUT + { 0, SocketError.Success } + }; + + static NativeSocket() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + IsSupported = true; + UnixMode = true; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + IsSupported = true; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int RecvFrom( + IntPtr socketHandle, + byte[] pinnedBuffer, + int len, + byte[] socketAddress, + ref int socketAddressSize) + { + return UnixMode + ? UnixSock.recvfrom(socketHandle, pinnedBuffer, len, 0, socketAddress, ref socketAddressSize) + : WinSock.recvfrom(socketHandle, pinnedBuffer, len, 0, socketAddress, ref socketAddressSize); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe int SendTo( + IntPtr socketHandle, + byte* pinnedBuffer, + int len, + byte[] socketAddress, + int socketAddressSize) + { + return UnixMode + ? UnixSock.sendto(socketHandle, pinnedBuffer, len, 0, socketAddress, socketAddressSize) + : WinSock.sendto(socketHandle, pinnedBuffer, len, 0, socketAddress, socketAddressSize); + } + + public static SocketError GetSocketError() + { + int error = Marshal.GetLastWin32Error(); + if (UnixMode) + return NativeErrorToSocketError.TryGetValue(error, out var err) + ? err + : SocketError.SocketError; + return (SocketError)error; + } + + public static SocketException GetSocketException() + { + int error = Marshal.GetLastWin32Error(); + if (UnixMode) + return NativeErrorToSocketError.TryGetValue(error, out var err) + ? new SocketException((int)err) + : new SocketException((int)SocketError.SocketError); + return new SocketException(error); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static short GetNativeAddressFamily(IPEndPoint remoteEndPoint) + { + return UnixMode + ? (short)(remoteEndPoint.AddressFamily == AddressFamily.InterNetwork ? AF_INET : AF_INET6) + : (short)remoteEndPoint.AddressFamily; + } + } +} diff --git a/Assets/Lib/LiteNetLib/NativeSocket.cs.meta b/Assets/Lib/LiteNetLib/NativeSocket.cs.meta new file mode 100644 index 0000000..985c2d5 --- /dev/null +++ b/Assets/Lib/LiteNetLib/NativeSocket.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: cafb06b2528a07d4b8f75b6d9dd694f3 \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/NetConstants.cs b/Assets/Lib/LiteNetLib/NetConstants.cs new file mode 100644 index 0000000..31495d1 --- /dev/null +++ b/Assets/Lib/LiteNetLib/NetConstants.cs @@ -0,0 +1,78 @@ +namespace LiteNetLib +{ + /// + /// Sending method type + /// + public enum DeliveryMethod : byte + { + /// + /// Unreliable. Packets can be dropped, can be duplicated, can arrive without order. + /// + Unreliable = 4, + + /// + /// Reliable. Packets won't be dropped, won't be duplicated, can arrive without order. + /// + ReliableUnordered = 0, + + /// + /// Unreliable. Packets can be dropped, won't be duplicated, will arrive in order. + /// + Sequenced = 1, + + /// + /// Reliable and ordered. Packets won't be dropped, won't be duplicated, will arrive in order. + /// + ReliableOrdered = 2, + + /// + /// Reliable only last packet. Packets can be dropped (except the last one), won't be duplicated, will arrive in order. + /// Cannot be fragmented + /// + ReliableSequenced = 3 + } + + /// + /// Network constants. Can be tuned from sources for your purposes. + /// + public static class NetConstants + { + //can be tuned + public const int DefaultWindowSize = 64; + public const int SocketBufferSize = 1024 * 1024; //1mb + public const int SocketTTL = 255; + + public const int HeaderSize = 1; + public const int ChanneledHeaderSize = 4; + public const int FragmentHeaderSize = 6; + public const int FragmentedHeaderTotalSize = ChanneledHeaderSize + FragmentHeaderSize; + public const ushort MaxSequence = 32768; + public const ushort HalfMaxSequence = MaxSequence / 2; + + //protocol + internal const int ProtocolId = 13; + internal const int MaxUdpHeaderSize = 68; + internal const int ChannelTypeCount = 4; + internal const int FragmentedChannelsCount = 2; + internal const int MaxFragmentsInWindow = DefaultWindowSize / 2; + + internal static readonly int[] PossibleMtu = + { + //576 - MaxUdpHeaderSize minimal (RFC 1191) + 1024, //most games standard + 1232 - MaxUdpHeaderSize, + 1460 - MaxUdpHeaderSize, //google cloud + 1472 - MaxUdpHeaderSize, //VPN + 1492 - MaxUdpHeaderSize, //Ethernet with LLC and SNAP, PPPoE (RFC 1042) + 1500 - MaxUdpHeaderSize //Ethernet II (RFC 1191) + }; + + //Max possible single packet size + public static readonly int InitialMtu = PossibleMtu[0]; + public static readonly int MaxPacketSize = PossibleMtu[PossibleMtu.Length - 1]; + public static readonly int MaxUnreliableDataSize = MaxPacketSize - HeaderSize; + + //peer specific + public const byte MaxConnectionNumber = 4; + } +} diff --git a/Assets/Lib/LiteNetLib/NetConstants.cs.meta b/Assets/Lib/LiteNetLib/NetConstants.cs.meta new file mode 100644 index 0000000..3abde17 --- /dev/null +++ b/Assets/Lib/LiteNetLib/NetConstants.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 57e049c4ade420343902c3b016d4e271 \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/NetDebug.cs b/Assets/Lib/LiteNetLib/NetDebug.cs new file mode 100644 index 0000000..44cb6f3 --- /dev/null +++ b/Assets/Lib/LiteNetLib/NetDebug.cs @@ -0,0 +1,92 @@ +using System; +using System.Diagnostics; + +namespace LiteNetLib +{ + public class InvalidPacketException : ArgumentException + { + public InvalidPacketException(string message) : base(message) + { + } + } + + public class TooBigPacketException : InvalidPacketException + { + public TooBigPacketException(string message) : base(message) + { + } + } + + public enum NetLogLevel + { + Warning, + Error, + Trace, + Info + } + + /// + /// Interface to implement for your own logger + /// + public interface INetLogger + { + void WriteNet(NetLogLevel level, string str, params object[] args); + } + + /// + /// Static class for defining your own LiteNetLib logger instead of Console.WriteLine + /// or Debug.Log if compiled with UNITY flag + /// + public static class NetDebug + { + public static INetLogger Logger = null; + private static readonly object DebugLogLock = new object(); + private static void WriteLogic(NetLogLevel logLevel, string str, params object[] args) + { + lock (DebugLogLock) + { + if (Logger == null) + { +#if UNITY_5_3_OR_NEWER + UnityEngine.Debug.Log(string.Format(str, args)); +#else + Console.WriteLine(str, args); +#endif + } + else + { + Logger.WriteNet(logLevel, str, args); + } + } + } + + [Conditional("DEBUG_MESSAGES")] + internal static void Write(string str) + { + WriteLogic(NetLogLevel.Trace, str); + } + + [Conditional("DEBUG_MESSAGES")] + internal static void Write(NetLogLevel level, string str) + { + WriteLogic(level, str); + } + + [Conditional("DEBUG_MESSAGES"), Conditional("DEBUG")] + internal static void WriteForce(string str) + { + WriteLogic(NetLogLevel.Trace, str); + } + + [Conditional("DEBUG_MESSAGES"), Conditional("DEBUG")] + internal static void WriteForce(NetLogLevel level, string str) + { + WriteLogic(level, str); + } + + internal static void WriteError(string str) + { + WriteLogic(NetLogLevel.Error, str); + } + } +} diff --git a/Assets/Lib/LiteNetLib/NetDebug.cs.meta b/Assets/Lib/LiteNetLib/NetDebug.cs.meta new file mode 100644 index 0000000..1b29dbb --- /dev/null +++ b/Assets/Lib/LiteNetLib/NetDebug.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a08cb565aae126340bd63be5d319c4f5 \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/NetManager.HashSet.cs b/Assets/Lib/LiteNetLib/NetManager.HashSet.cs new file mode 100644 index 0000000..78737ad --- /dev/null +++ b/Assets/Lib/LiteNetLib/NetManager.HashSet.cs @@ -0,0 +1,322 @@ +using System; +using System.Net; +using System.Threading; + +namespace LiteNetLib +{ + //minimal hashset class from dotnet with some optimizations + public partial class NetManager + { + private const int MaxPrimeArrayLength = 0x7FFFFFC3; + private const int HashPrime = 101; + private const int Lower31BitMask = 0x7FFFFFFF; + private static readonly int[] Primes = + { + 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919, + 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, + 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437, + 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, + 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369 + }; + + private static int HashSetGetPrime(int min) + { + foreach (int prime in Primes) + { + if (prime >= min) + return prime; + } + + // Outside of our predefined table. Compute the hard way. + for (int i = (min | 1); i < int.MaxValue; i += 2) + { + if (IsPrime(i) && ((i - 1) % HashPrime != 0)) + return i; + } + return min; + + bool IsPrime(int candidate) + { + if ((candidate & 1) != 0) + { + int limit = (int)Math.Sqrt(candidate); + for (int divisor = 3; divisor <= limit; divisor += 2) + { + if (candidate % divisor == 0) + return false; + } + return true; + } + return candidate == 2; + } + } + + private struct Slot + { + internal int HashCode; + internal int Next; + internal NetPeer Value; + } + + private int[] _buckets; + private Slot[] _slots; + private int _count; + private int _lastIndex; + private int _freeList = -1; + private NetPeer[] _peersArray = new NetPeer[32]; + private readonly ReaderWriterLockSlim _peersLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); + private volatile NetPeer _headPeer; + + private void ClearPeerSet() + { + _peersLock.EnterWriteLock(); + _headPeer = null; + if (_lastIndex > 0) + { + Array.Clear(_slots, 0, _lastIndex); + Array.Clear(_buckets, 0, _buckets.Length); + _lastIndex = 0; + _count = 0; + _freeList = -1; + } + _peersArray = new NetPeer[32]; + _peersLock.ExitWriteLock(); + } + + private bool ContainsPeer(NetPeer item) + { + if (item == null) + { + NetDebug.WriteError($"Contains peer null: {item}"); + return false; + } + if (_buckets != null) + { + int hashCode = item.GetHashCode() & Lower31BitMask; + for (int i = _buckets[hashCode % _buckets.Length] - 1; i >= 0; i = _slots[i].Next) + { + if (_slots[i].HashCode == hashCode && _slots[i].Value.Equals(item)) + return true; + } + } + return false; + } + + /// + /// Gets peer by peer id + /// + /// id of peer + /// Peer if peer with id exist, otherwise null + public NetPeer GetPeerById(int id) + { + return id >= 0 && id < _peersArray.Length ? _peersArray[id] : null; + } + + /// + /// Gets peer by peer id + /// + /// id of peer + /// resulting peer + /// True if peer with id exist, otherwise false + public bool TryGetPeerById(int id, out NetPeer peer) + { + peer = GetPeerById(id); + return peer != null; + } + + private void AddPeer(NetPeer peer) + { + if (peer == null) + { + NetDebug.WriteError($"Add peer null: {peer}"); + return; + } + _peersLock.EnterWriteLock(); + if (_headPeer != null) + { + peer.NextPeer = _headPeer; + _headPeer.PrevPeer = peer; + } + _headPeer = peer; + AddPeerToSet(peer); + if (peer.Id >= _peersArray.Length) + { + int newSize = _peersArray.Length * 2; + while (peer.Id >= newSize) + newSize *= 2; + Array.Resize(ref _peersArray, newSize); + } + _peersArray[peer.Id] = peer; + _peersLock.ExitWriteLock(); + } + + private void RemovePeer(NetPeer peer, bool enableWriteLock) + { + if(enableWriteLock) + _peersLock.EnterWriteLock(); + if (!RemovePeerFromSet(peer)) + { + if(enableWriteLock) + _peersLock.ExitWriteLock(); + return; + } + if (peer == _headPeer) + _headPeer = peer.NextPeer; + + if (peer.PrevPeer != null) + peer.PrevPeer.NextPeer = peer.NextPeer; + if (peer.NextPeer != null) + peer.NextPeer.PrevPeer = peer.PrevPeer; + peer.PrevPeer = null; + + _peersArray[peer.Id] = null; + _peerIds.Enqueue(peer.Id); + + if(enableWriteLock) + _peersLock.ExitWriteLock(); + } + + private bool RemovePeerFromSet(NetPeer peer) + { + if (_buckets == null || peer == null) + return false; + int hashCode = peer.GetHashCode() & Lower31BitMask; + int bucket = hashCode % _buckets.Length; + int last = -1; + for (int i = _buckets[bucket] - 1; i >= 0; last = i, i = _slots[i].Next) + { + if (_slots[i].HashCode == hashCode && _slots[i].Value.Equals(peer)) + { + if (last < 0) + _buckets[bucket] = _slots[i].Next + 1; + else + _slots[last].Next = _slots[i].Next; + _slots[i].HashCode = -1; + _slots[i].Value = null; + _slots[i].Next = _freeList; + + _count--; + if (_count == 0) + { + _lastIndex = 0; + _freeList = -1; + } + else + { + _freeList = i; + } + return true; + } + } + return false; + } + + private bool TryGetPeer(IPEndPoint endPoint, out NetPeer actualValue) + { + if (_buckets != null) + { +#if NET8_0_OR_GREATER + //can be NetPeer or IPEndPoint + int hashCode = (UseNativeSockets ? endPoint.GetHashCode() : endPoint.Serialize().GetHashCode()) & Lower31BitMask; +#else + int hashCode = endPoint.GetHashCode() & Lower31BitMask; +#endif + _peersLock.EnterReadLock(); + for (int i = _buckets[hashCode % _buckets.Length] - 1; i >= 0; i = _slots[i].Next) + { + if (_slots[i].HashCode == hashCode && _slots[i].Value.Equals(endPoint)) + { + actualValue = _slots[i].Value; + _peersLock.ExitReadLock(); + return true; + } + } + _peersLock.ExitReadLock(); + } + actualValue = null; + return false; + } + + //only used for NET8 + private bool TryGetPeer(SocketAddress saddr, out NetPeer actualValue) + { + if (_buckets != null) + { + int hashCode = saddr.GetHashCode() & Lower31BitMask; + _peersLock.EnterReadLock(); + for (int i = _buckets[hashCode % _buckets.Length] - 1; i >= 0; i = _slots[i].Next) + { + if (_slots[i].HashCode == hashCode && _slots[i].Value.Serialize().Equals(saddr)) + { + actualValue = _slots[i].Value; + _peersLock.ExitReadLock(); + return true; + } + } + _peersLock.ExitReadLock(); + } + actualValue = null; + return false; + } + + private bool AddPeerToSet(NetPeer value) + { + if (_buckets == null) + { + int size = HashSetGetPrime(0); + _buckets = new int[size]; + _slots = new Slot[size]; + } + + int hashCode = value.GetHashCode() & Lower31BitMask; + int bucket = hashCode % _buckets.Length; + for (int i = _buckets[hashCode % _buckets.Length] - 1; i >= 0; i = _slots[i].Next) + { + if (_slots[i].HashCode == hashCode && _slots[i].Value.Equals(value)) + return false; + } + + int index; + if (_freeList >= 0) + { + index = _freeList; + _freeList = _slots[index].Next; + } + else + { + if (_lastIndex == _slots.Length) + { + //increase capacity + int newSize = 2 * _count; + newSize = (uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > _count + ? MaxPrimeArrayLength + : HashSetGetPrime(newSize); + + // Able to increase capacity; copy elements to larger array and rehash + Slot[] newSlots = new Slot[newSize]; + Array.Copy(_slots, 0, newSlots, 0, _lastIndex); + _buckets = new int[newSize]; + for (int i = 0; i < _lastIndex; i++) + { + int b = newSlots[i].HashCode % newSize; + newSlots[i].Next = _buckets[b] - 1; + _buckets[b] = i + 1; + } + _slots = newSlots; + // this will change during resize + bucket = hashCode % _buckets.Length; + } + index = _lastIndex; + _lastIndex++; + } + _slots[index].HashCode = hashCode; + _slots[index].Value = value; + _slots[index].Next = _buckets[bucket] - 1; + _buckets[bucket] = index + 1; + _count++; + + return true; + } + } + +} diff --git a/Assets/Lib/LiteNetLib/NetManager.HashSet.cs.meta b/Assets/Lib/LiteNetLib/NetManager.HashSet.cs.meta new file mode 100644 index 0000000..2625951 --- /dev/null +++ b/Assets/Lib/LiteNetLib/NetManager.HashSet.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e9de5cf1cdeb088458ef7d16889ca00b \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/NetManager.PacketPool.cs b/Assets/Lib/LiteNetLib/NetManager.PacketPool.cs new file mode 100644 index 0000000..0831220 --- /dev/null +++ b/Assets/Lib/LiteNetLib/NetManager.PacketPool.cs @@ -0,0 +1,82 @@ +using System; + +namespace LiteNetLib +{ + public partial class NetManager + { + private NetPacket _poolHead; + private int _poolCount; + private readonly object _poolLock = new object(); + + /// + /// Maximum packet pool size (increase if you have tons of packets sending) + /// + public int PacketPoolSize = 1000; + + public int PoolCount => _poolCount; + + private NetPacket PoolGetWithData(PacketProperty property, byte[] data, int start, int length) + { + int headerSize = NetPacket.GetHeaderSize(property); + NetPacket packet = PoolGetPacket(length + headerSize); + packet.Property = property; + Buffer.BlockCopy(data, start, packet.RawData, headerSize, length); + return packet; + } + + //Get packet with size + private NetPacket PoolGetWithProperty(PacketProperty property, int size) + { + NetPacket packet = PoolGetPacket(size + NetPacket.GetHeaderSize(property)); + packet.Property = property; + return packet; + } + + private NetPacket PoolGetWithProperty(PacketProperty property) + { + NetPacket packet = PoolGetPacket(NetPacket.GetHeaderSize(property)); + packet.Property = property; + return packet; + } + + internal NetPacket PoolGetPacket(int size) + { + if (size > NetConstants.MaxPacketSize) + return new NetPacket(size); + + NetPacket packet; + lock (_poolLock) + { + packet = _poolHead; + if (packet == null) + return new NetPacket(size); + + _poolHead = _poolHead.Next; + _poolCount--; + } + + packet.Size = size; + if (packet.RawData.Length < size) + packet.RawData = new byte[size]; + return packet; + } + + internal void PoolRecycle(NetPacket packet) + { + if (packet.RawData.Length > NetConstants.MaxPacketSize || _poolCount >= PacketPoolSize) + { + //Don't pool big packets. Save memory + return; + } + + //Clean fragmented flag + packet.RawData[0] = 0; + lock (_poolLock) + { + packet.Next = _poolHead; + _poolHead = packet; + _poolCount++; + } + } + } +} diff --git a/Assets/Lib/LiteNetLib/NetManager.PacketPool.cs.meta b/Assets/Lib/LiteNetLib/NetManager.PacketPool.cs.meta new file mode 100644 index 0000000..303ea26 --- /dev/null +++ b/Assets/Lib/LiteNetLib/NetManager.PacketPool.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d8fc2b315ee5f604aabf4f14b3a985f2 \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/NetManager.Socket.cs b/Assets/Lib/LiteNetLib/NetManager.Socket.cs new file mode 100644 index 0000000..6a89336 --- /dev/null +++ b/Assets/Lib/LiteNetLib/NetManager.Socket.cs @@ -0,0 +1,743 @@ +#if UNITY_2018_3_OR_NEWER +#define UNITY_SOCKET_FIX +#endif +using System.Runtime.InteropServices; +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using LiteNetLib.Utils; + +namespace LiteNetLib +{ + public partial class NetManager + { + private Socket _udpSocketv4; + private Socket _udpSocketv6; + private Thread _receiveThread; + private IPEndPoint _bufferEndPointv4; + private IPEndPoint _bufferEndPointv6; +#if UNITY_SOCKET_FIX + private PausedSocketFix _pausedSocketFix; + private bool _useSocketFix; +#endif + +#if NET8_0_OR_GREATER + private readonly SocketAddress _sockAddrCacheV4 = new SocketAddress(AddressFamily.InterNetwork); + private readonly SocketAddress _sockAddrCacheV6 = new SocketAddress(AddressFamily.InterNetworkV6); +#endif + + private const int SioUdpConnreset = -1744830452; //SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12 + private static readonly IPAddress MulticastAddressV6 = IPAddress.Parse("ff02::1"); + public static readonly bool IPv6Support; + + // special case in iOS (and possibly android that should be resolved in unity) + internal bool NotConnected; + + /// + /// Poll timeout in microseconds. Increasing can slightly increase performance in cost of slow NetManager.Stop(Socket.Close) + /// + public int ReceivePollingTime = 50000; //0.05 second + + public short Ttl + { + get + { +#if UNITY_SWITCH + return 0; +#else + return _udpSocketv4.Ttl; +#endif + } + internal set + { +#if !UNITY_SWITCH + _udpSocketv4.Ttl = value; +#endif + } + } + + static NetManager() + { +#if DISABLE_IPV6 + IPv6Support = false; +#elif !UNITY_2019_1_OR_NEWER && !UNITY_2018_4_OR_NEWER && (!UNITY_EDITOR && ENABLE_IL2CPP) + string version = UnityEngine.Application.unityVersion; + IPv6Support = Socket.OSSupportsIPv6 && int.Parse(version.Remove(version.IndexOf('f')).Split('.')[2]) >= 6; +#else + IPv6Support = Socket.OSSupportsIPv6; +#endif + } + + private bool ProcessError(SocketException ex) + { + switch (ex.SocketErrorCode) + { + case SocketError.NotConnected: + NotConnected = true; + return true; + case SocketError.Interrupted: + case SocketError.NotSocket: + case SocketError.OperationAborted: + return true; + case SocketError.ConnectionReset: + case SocketError.MessageSize: + case SocketError.TimedOut: + case SocketError.NetworkReset: + case SocketError.WouldBlock: + //NetDebug.Write($"[R]Ignored error: {(int)ex.SocketErrorCode} - {ex}"); + break; + default: + NetDebug.WriteError($"[R]Error code: {(int)ex.SocketErrorCode} - {ex}"); + CreateEvent(NetEvent.EType.Error, errorCode: ex.SocketErrorCode); + break; + } + return false; + } + + private void ManualReceive(Socket socket, EndPoint bufferEndPoint, int maxReceive) + { + //Reading data + try + { + int packetsReceived = 0; + while (socket.Available > 0) + { + ReceiveFrom(socket, ref bufferEndPoint); + packetsReceived++; + if (packetsReceived == maxReceive) + break; + } + } + catch (SocketException ex) + { + ProcessError(ex); + } + catch (ObjectDisposedException) + { + + } + catch (Exception e) + { + //protects socket receive thread + NetDebug.WriteError("[NM] SocketReceiveThread error: " + e); + } + } + + private void NativeReceiveLogic() + { + IntPtr socketHandle4 = _udpSocketv4.Handle; + IntPtr socketHandle6 = _udpSocketv6?.Handle ?? IntPtr.Zero; + byte[] addrBuffer4 = new byte[NativeSocket.IPv4AddrSize]; + byte[] addrBuffer6 = new byte[NativeSocket.IPv6AddrSize]; + var tempEndPoint = new IPEndPoint(IPAddress.Any, 0); + var selectReadList = new List(2); + var socketv4 = _udpSocketv4; + var socketV6 = _udpSocketv6; + var packet = PoolGetPacket(NetConstants.MaxPacketSize); + + while (_isRunning) + { + try + { + if (socketV6 == null) + { + if (NativeReceiveFrom(socketHandle4, addrBuffer4) == false) + return; + continue; + } + bool messageReceived = false; + if (socketv4.Available != 0 || selectReadList.Contains(socketv4)) + { + if (NativeReceiveFrom(socketHandle4, addrBuffer4) == false) + return; + messageReceived = true; + } + if (socketV6.Available != 0 || selectReadList.Contains(socketV6)) + { + if (NativeReceiveFrom(socketHandle6, addrBuffer6) == false) + return; + messageReceived = true; + } + + selectReadList.Clear(); + + if (messageReceived) + continue; + + selectReadList.Add(socketv4); + selectReadList.Add(socketV6); + + Socket.Select(selectReadList, null, null, ReceivePollingTime); + } + catch (SocketException ex) + { + if (ProcessError(ex)) + return; + } + catch (ObjectDisposedException) + { + //socket closed + return; + } + catch (ThreadAbortException) + { + //thread closed + return; + } + catch (Exception e) + { + //protects socket receive thread + NetDebug.WriteError("[NM] SocketReceiveThread error: " + e); + } + } + + bool NativeReceiveFrom(IntPtr s, byte[] address) + { + int addrSize = address.Length; + packet.Size = NativeSocket.RecvFrom(s, packet.RawData, NetConstants.MaxPacketSize, address, ref addrSize); + if (packet.Size == 0) + return true; //socket closed or empty packet + + if (packet.Size == -1) + { + //Linux timeout EAGAIN + return ProcessError(new SocketException((int)NativeSocket.GetSocketError())) == false; + } + + //NetDebug.WriteForce($"[R]Received data from {endPoint}, result: {packet.Size}"); + //refresh temp Addr/Port + short family = (short)((address[1] << 8) | address[0]); + tempEndPoint.Port = (ushort)((address[2] << 8) | address[3]); + if ((NativeSocket.UnixMode && family == NativeSocket.AF_INET6) || (!NativeSocket.UnixMode && (AddressFamily)family == AddressFamily.InterNetworkV6)) + { + uint scope = unchecked((uint)( + (address[27] << 24) + + (address[26] << 16) + + (address[25] << 8) + + (address[24]))); +#if NETCOREAPP || NETSTANDARD2_1 || NETSTANDARD2_1_OR_GREATER + tempEndPoint.Address = new IPAddress(new ReadOnlySpan(address, 8, 16), scope); +#else + byte[] addrBuffer = new byte[16]; + Buffer.BlockCopy(address, 8, addrBuffer, 0, 16); + tempEndPoint.Address = new IPAddress(addrBuffer, scope); +#endif + } + else //IPv4 + { + long ipv4Addr = unchecked((uint)((address[4] & 0x000000FF) | + (address[5] << 8 & 0x0000FF00) | + (address[6] << 16 & 0x00FF0000) | + (address[7] << 24))); + tempEndPoint.Address = new IPAddress(ipv4Addr); + } + + if (TryGetPeer(tempEndPoint, out var peer)) + { + //use cached native ep + OnMessageReceived(packet, peer); + } + else + { + OnMessageReceived(packet, tempEndPoint); + tempEndPoint = new IPEndPoint(IPAddress.Any, 0); + } + packet = PoolGetPacket(NetConstants.MaxPacketSize); + return true; + } + } + + private void ReceiveFrom(Socket s, ref EndPoint bufferEndPoint) + { + var packet = PoolGetPacket(NetConstants.MaxPacketSize); +#if NET8_0_OR_GREATER + var sockAddr = s.AddressFamily == AddressFamily.InterNetwork ? _sockAddrCacheV4 : _sockAddrCacheV6; + packet.Size = s.ReceiveFrom(packet, SocketFlags.None, sockAddr); + OnMessageReceived(packet, TryGetPeer(sockAddr, out var peer) ? peer : (IPEndPoint)bufferEndPoint.Create(sockAddr)); +#else + packet.Size = s.ReceiveFrom(packet.RawData, 0, NetConstants.MaxPacketSize, SocketFlags.None, ref bufferEndPoint); + OnMessageReceived(packet, (IPEndPoint)bufferEndPoint); +#endif + } + + private void ReceiveLogic() + { + EndPoint bufferEndPoint4 = new IPEndPoint(IPAddress.Any, 0); + EndPoint bufferEndPoint6 = new IPEndPoint(IPAddress.IPv6Any, 0); + var selectReadList = new List(2); + var socketv4 = _udpSocketv4; + var socketV6 = _udpSocketv6; + + while (_isRunning) + { + //Reading data + try + { + if (socketV6 == null) + { + if (socketv4.Available == 0 && !socketv4.Poll(ReceivePollingTime, SelectMode.SelectRead)) + continue; + ReceiveFrom(socketv4, ref bufferEndPoint4); + } + else + { + bool messageReceived = false; + if (socketv4.Available != 0 || selectReadList.Contains(socketv4)) + { + ReceiveFrom(socketv4, ref bufferEndPoint4); + messageReceived = true; + } + if (socketV6.Available != 0 || selectReadList.Contains(socketV6)) + { + ReceiveFrom(socketV6, ref bufferEndPoint6); + messageReceived = true; + } + + selectReadList.Clear(); + + if (messageReceived) + continue; + + selectReadList.Add(socketv4); + selectReadList.Add(socketV6); + Socket.Select(selectReadList, null, null, ReceivePollingTime); + } + //NetDebug.Write(NetLogLevel.Trace, $"[R]Received data from {bufferEndPoint}, result: {packet.Size}"); + } + catch (SocketException ex) + { + if (ProcessError(ex)) + return; + } + catch (ObjectDisposedException) + { + //socket closed + return; + } + catch (ThreadAbortException) + { + //thread closed + return; + } + catch (Exception e) + { + //protects socket receive thread + NetDebug.WriteError("[NM] SocketReceiveThread error: " + e); + } + } + } + + /// + /// Start logic thread and listening on selected port + /// + /// bind to specific ipv4 address + /// bind to specific ipv6 address + /// port to listen + /// mode of library + public bool Start(IPAddress addressIPv4, IPAddress addressIPv6, int port, bool manualMode) + { + if (IsRunning && NotConnected == false) + return false; + + NotConnected = false; + _manualMode = manualMode; + UseNativeSockets = UseNativeSockets && NativeSocket.IsSupported; + _udpSocketv4 = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + if (!BindSocket(_udpSocketv4, new IPEndPoint(addressIPv4, port))) + return false; + + LocalPort = ((IPEndPoint)_udpSocketv4.LocalEndPoint).Port; + +#if UNITY_SOCKET_FIX + if (_useSocketFix && _pausedSocketFix == null) + _pausedSocketFix = new PausedSocketFix(this, addressIPv4, addressIPv6, port, manualMode); +#endif + + _isRunning = true; + if (_manualMode) + { + _bufferEndPointv4 = new IPEndPoint(IPAddress.Any, 0); + } + + //Check IPv6 support + if (IPv6Support && IPv6Enabled) + { + _udpSocketv6 = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp); + //Use one port for two sockets + if (BindSocket(_udpSocketv6, new IPEndPoint(addressIPv6, LocalPort))) + { + if (_manualMode) + _bufferEndPointv6 = new IPEndPoint(IPAddress.IPv6Any, 0); + } + else + { + _udpSocketv6 = null; + } + } + + if (!manualMode) + { + ThreadStart ts = ReceiveLogic; + if (UseNativeSockets) + ts = NativeReceiveLogic; + _receiveThread = new Thread(ts) + { + Name = $"ReceiveThread({LocalPort})", + IsBackground = true + }; + _receiveThread.Start(); + if (_logicThread == null) + { + _logicThread = new Thread(UpdateLogic) { Name = "LogicThread", IsBackground = true }; + _logicThread.Start(); + } + } + + return true; + } + + private bool BindSocket(Socket socket, IPEndPoint ep) + { + //Setup socket + socket.ReceiveTimeout = 500; + socket.SendTimeout = 500; + socket.ReceiveBufferSize = NetConstants.SocketBufferSize; + socket.SendBufferSize = NetConstants.SocketBufferSize; + socket.Blocking = true; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + try + { + socket.IOControl(SioUdpConnreset, new byte[] { 0 }, null); + } + catch + { + //ignored + } + } + + try + { + socket.ExclusiveAddressUse = !ReuseAddress; + socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, ReuseAddress); + socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontRoute, DontRoute); + } + catch + { + //Unity with IL2CPP throws an exception here, it doesn't matter in most cases so just ignore it + } + if (ep.AddressFamily == AddressFamily.InterNetwork) + { + Ttl = NetConstants.SocketTTL; + + try { socket.EnableBroadcast = true; } + catch (SocketException e) + { + NetDebug.WriteError($"[B]Broadcast error: {e.SocketErrorCode}"); + } + + if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + try { socket.DontFragment = true; } + catch (SocketException e) + { + NetDebug.WriteError($"[B]DontFragment error: {e.SocketErrorCode}"); + } + } + } + //Bind + try + { + socket.Bind(ep); + NetDebug.Write(NetLogLevel.Trace, $"[B]Successfully binded to port: {((IPEndPoint)socket.LocalEndPoint).Port}, AF: {socket.AddressFamily}"); + + //join multicast + if (ep.AddressFamily == AddressFamily.InterNetworkV6) + { + try + { +#if !UNITY_SOCKET_FIX + socket.SetSocketOption( + SocketOptionLevel.IPv6, + SocketOptionName.AddMembership, + new IPv6MulticastOption(MulticastAddressV6)); +#endif + } + catch (Exception) + { + // Unity3d throws exception - ignored + } + } + } + catch (SocketException bindException) + { + switch (bindException.SocketErrorCode) + { + //IPv6 bind fix + case SocketError.AddressAlreadyInUse: + if (socket.AddressFamily == AddressFamily.InterNetworkV6) + { + try + { + //Set IPv6Only + socket.DualMode = false; + socket.Bind(ep); + } + catch (SocketException ex) + { + //because its fixed in 2018_3 + NetDebug.WriteError($"[B]Bind exception: {ex}, errorCode: {ex.SocketErrorCode}"); + return false; + } + return true; + } + break; + //hack for iOS (Unity3D) + case SocketError.AddressFamilyNotSupported: + return true; + } + NetDebug.WriteError($"[B]Bind exception: {bindException}, errorCode: {bindException.SocketErrorCode}"); + return false; + } + return true; + } + + internal int SendRawAndRecycle(NetPacket packet, IPEndPoint remoteEndPoint) + { + int result = SendRaw(packet.RawData, 0, packet.Size, remoteEndPoint); + PoolRecycle(packet); + return result; + } + + internal int SendRaw(NetPacket packet, IPEndPoint remoteEndPoint) + { + return SendRaw(packet.RawData, 0, packet.Size, remoteEndPoint); + } + + internal int SendRaw(byte[] message, int start, int length, IPEndPoint remoteEndPoint) + { + if (!_isRunning) + return 0; + + NetPacket expandedPacket = null; + if (_extraPacketLayer != null) + { + expandedPacket = PoolGetPacket(length + _extraPacketLayer.ExtraPacketSizeForLayer); + Buffer.BlockCopy(message, start, expandedPacket.RawData, 0, length); + start = 0; + _extraPacketLayer.ProcessOutBoundPacket(ref remoteEndPoint, ref expandedPacket.RawData, ref start, ref length); + message = expandedPacket.RawData; + } + +#if DEBUG || SIMULATE_NETWORK + if (HandleSimulateOutboundPacketLoss()) + { + if (expandedPacket != null) + PoolRecycle(expandedPacket); + return 0; // Simulate successful send to avoid triggering error handling + } + + if (HandleSimulateOutboundLatency(message, start, length, remoteEndPoint)) + { + if (expandedPacket != null) + PoolRecycle(expandedPacket); + return length; // Simulate successful send + } +#endif + + return SendRawCoreWithCleanup(message, start, length, remoteEndPoint, expandedPacket); + } + + private int SendRawCoreWithCleanup(byte[] message, int start, int length, IPEndPoint remoteEndPoint, NetPacket expandedPacket) + { + try + { + return SendRawCore(message, start, length, remoteEndPoint); + } + finally + { + if (expandedPacket != null) + PoolRecycle(expandedPacket); + } + } + + // Core socket sending logic without simulation - used by both SendRaw and delayed packet processing + internal int SendRawCore(byte[] message, int start, int length, IPEndPoint remoteEndPoint) + { + if (!_isRunning) + return 0; + + var socket = _udpSocketv4; + if (remoteEndPoint.AddressFamily == AddressFamily.InterNetworkV6 && IPv6Support) + { + socket = _udpSocketv6; + if (socket == null) + return 0; + } + + int result; + try + { + if (UseNativeSockets && remoteEndPoint is NetPeer peer) + { + unsafe + { + fixed (byte* dataWithOffset = &message[start]) + result = NativeSocket.SendTo(socket.Handle, dataWithOffset, length, peer.NativeAddress, peer.NativeAddress.Length); + } + if (result == -1) + throw NativeSocket.GetSocketException(); + } + else + { +#if NET8_0_OR_GREATER + result = socket.SendTo(new ReadOnlySpan(message, start, length), SocketFlags.None, remoteEndPoint.Serialize()); +#else + result = socket.SendTo(message, start, length, SocketFlags.None, remoteEndPoint); +#endif + } + //NetDebug.WriteForce("[S]Send packet to {0}, result: {1}", remoteEndPoint, result); + } + catch (SocketException ex) + { + switch (ex.SocketErrorCode) + { + case SocketError.NoBufferSpaceAvailable: + case SocketError.Interrupted: + return 0; + case SocketError.MessageSize: + NetDebug.Write(NetLogLevel.Trace, $"[SRD] 10040, datalen: {length}"); + return 0; + + case SocketError.HostUnreachable: + case SocketError.NetworkUnreachable: + if (DisconnectOnUnreachable && remoteEndPoint is NetPeer peer) + { + DisconnectPeerForce( + peer, + ex.SocketErrorCode == SocketError.HostUnreachable + ? DisconnectReason.HostUnreachable + : DisconnectReason.NetworkUnreachable, + ex.SocketErrorCode, + null); + } + + CreateEvent(NetEvent.EType.Error, remoteEndPoint: remoteEndPoint, errorCode: ex.SocketErrorCode); + return -1; + + case SocketError.Shutdown: + CreateEvent(NetEvent.EType.Error, remoteEndPoint: remoteEndPoint, errorCode: ex.SocketErrorCode); + return -1; + + default: + NetDebug.WriteError($"[S] {ex}"); + return -1; + } + } + catch (Exception ex) + { + NetDebug.WriteError($"[S] {ex}"); + return 0; + } + + if (result <= 0) + return 0; + + if (EnableStatistics) + { + Statistics.IncrementPacketsSent(); + Statistics.AddBytesSent(length); + } + + return result; + } + + public bool SendBroadcast(NetDataWriter writer, int port) + { + return SendBroadcast(writer.Data, 0, writer.Length, port); + } + + public bool SendBroadcast(byte[] data, int port) + { + return SendBroadcast(data, 0, data.Length, port); + } + + public bool SendBroadcast(byte[] data, int start, int length, int port) + { + if (!IsRunning) + return false; + + NetPacket packet; + if (_extraPacketLayer != null) + { + var headerSize = NetPacket.GetHeaderSize(PacketProperty.Broadcast); + packet = PoolGetPacket(headerSize + length + _extraPacketLayer.ExtraPacketSizeForLayer); + packet.Property = PacketProperty.Broadcast; + Buffer.BlockCopy(data, start, packet.RawData, headerSize, length); + var checksumComputeStart = 0; + int preCrcLength = length + headerSize; + IPEndPoint emptyEp = null; + _extraPacketLayer.ProcessOutBoundPacket(ref emptyEp, ref packet.RawData, ref checksumComputeStart, ref preCrcLength); + } + else + { + packet = PoolGetWithData(PacketProperty.Broadcast, data, start, length); + } + + bool broadcastSuccess = false; + bool multicastSuccess = false; + try + { + broadcastSuccess = _udpSocketv4.SendTo( + packet.RawData, + 0, + packet.Size, + SocketFlags.None, + new IPEndPoint(IPAddress.Broadcast, port)) > 0; + + if (_udpSocketv6 != null) + { + multicastSuccess = _udpSocketv6.SendTo( + packet.RawData, + 0, + packet.Size, + SocketFlags.None, + new IPEndPoint(MulticastAddressV6, port)) > 0; + } + } + catch (SocketException ex) + { + if (ex.SocketErrorCode == SocketError.HostUnreachable) + return broadcastSuccess; + NetDebug.WriteError($"[S][MCAST] {ex}"); + return broadcastSuccess; + } + catch (Exception ex) + { + NetDebug.WriteError($"[S][MCAST] {ex}"); + return broadcastSuccess; + } + finally + { + PoolRecycle(packet); + } + + return broadcastSuccess || multicastSuccess; + } + + private void CloseSocket() + { + _isRunning = false; + if (_receiveThread != null && _receiveThread != Thread.CurrentThread) + _receiveThread.Join(); + _receiveThread = null; + _udpSocketv4?.Close(); + _udpSocketv6?.Close(); + _udpSocketv4 = null; + _udpSocketv6 = null; + } + } +} diff --git a/Assets/Lib/LiteNetLib/NetManager.Socket.cs.meta b/Assets/Lib/LiteNetLib/NetManager.Socket.cs.meta new file mode 100644 index 0000000..792eb3e --- /dev/null +++ b/Assets/Lib/LiteNetLib/NetManager.Socket.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: bbf3ea1730dac8f45a6e2476860759e4 \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/NetManager.cs b/Assets/Lib/LiteNetLib/NetManager.cs new file mode 100644 index 0000000..1151ad5 --- /dev/null +++ b/Assets/Lib/LiteNetLib/NetManager.cs @@ -0,0 +1,1943 @@ +#if UNITY_2018_3_OR_NEWER +#define UNITY_SOCKET_FIX +#endif +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using LiteNetLib.Layers; +using LiteNetLib.Utils; + +namespace LiteNetLib +{ + public sealed class NetPacketReader : NetDataReader + { + private NetPacket _packet; + private readonly NetManager _manager; + private readonly NetEvent _evt; + + internal NetPacketReader(NetManager manager, NetEvent evt) + { + _manager = manager; + _evt = evt; + } + + internal void SetSource(NetPacket packet, int headerSize) + { + if (packet == null) + return; + _packet = packet; + SetSource(packet.RawData, headerSize, packet.Size); + } + + internal void RecycleInternal() + { + Clear(); + if (_packet != null) + _manager.PoolRecycle(_packet); + _packet = null; + _manager.RecycleEvent(_evt); + } + + public void Recycle() + { + if (_manager.AutoRecycle) + return; + RecycleInternal(); + } + } + + internal sealed class NetEvent + { + public NetEvent Next; + + public enum EType + { + Connect, + Disconnect, + Receive, + ReceiveUnconnected, + Error, + ConnectionLatencyUpdated, + Broadcast, + ConnectionRequest, + MessageDelivered, + PeerAddressChanged + } + public EType Type; + + public NetPeer Peer; + public IPEndPoint RemoteEndPoint; + public object UserData; + public int Latency; + public SocketError ErrorCode; + public DisconnectReason DisconnectReason; + public ConnectionRequest ConnectionRequest; + public DeliveryMethod DeliveryMethod; + public byte ChannelNumber; + public readonly NetPacketReader DataReader; + + public NetEvent(NetManager manager) + { + DataReader = new NetPacketReader(manager, this); + } + } + + /// + /// Main class for all network operations. Can be used as client and/or server. + /// + public partial class NetManager : IEnumerable + { + public struct NetPeerEnumerator : IEnumerator + { + private readonly NetPeer _initialPeer; + private NetPeer _p; + + public NetPeerEnumerator(NetPeer p) + { + _initialPeer = p; + _p = null; + } + + public void Dispose() + { + + } + + public bool MoveNext() + { + _p = _p == null ? _initialPeer : _p.NextPeer; + return _p != null; + } + + public void Reset() + { + throw new NotSupportedException(); + } + + public NetPeer Current => _p; + object IEnumerator.Current => _p; + } + + private struct IncomingData + { + public NetPacket Data; + public IPEndPoint EndPoint; + public DateTime TimeWhenGet; + } + private readonly List _pingSimulationList = new List(); + + private struct OutboundDelayedPacket + { + public byte[] Data; + public int Start; + public int Length; + public IPEndPoint EndPoint; + public DateTime TimeWhenSend; + } + private readonly List _outboundSimulationList = new List(); + + private readonly Random _randomGenerator = new Random(); + private const int MinLatencyThreshold = 5; + + private Thread _logicThread; + private bool _manualMode; + private readonly AutoResetEvent _updateTriggerEvent = new AutoResetEvent(true); + + private NetEvent _pendingEventHead; + private NetEvent _pendingEventTail; + + private NetEvent _netEventPoolHead; + private readonly INetEventListener _netEventListener; + private readonly IDeliveryEventListener _deliveryEventListener; + private readonly INtpEventListener _ntpEventListener; + private readonly IPeerAddressChangedListener _peerAddressChangedListener; + + private readonly Dictionary _requestsDict = new Dictionary(); + private readonly ConcurrentDictionary _ntpRequests = new ConcurrentDictionary(); + private long _connectedPeersCount; + private readonly List _connectedPeerListCache = new List(); + private readonly PacketLayerBase _extraPacketLayer; + private int _lastPeerId; + private ConcurrentQueue _peerIds = new ConcurrentQueue(); + private byte _channelsCount = 1; + private readonly object _eventLock = new object(); + private volatile bool _isRunning; + + /// + /// Used with and to tag packets that + /// need to be dropped. Only relevant when DEBUG is defined. + /// + private bool _dropPacket; + + //config section + /// + /// Enable messages receiving without connection. (with SendUnconnectedMessage method) + /// + public bool UnconnectedMessagesEnabled = false; + + /// + /// Enable nat punch messages + /// + public bool NatPunchEnabled = false; + + /// + /// Library logic update and send period in milliseconds + /// Lowest values in Windows doesn't change much because of Thread.Sleep precision + /// To more frequent sends (or sends tied to your game logic) use + /// + public int UpdateTime = 15; + + /// + /// Interval for latency detection and checking connection (in milliseconds) + /// + public int PingInterval = 1000; + + /// + /// If NetManager doesn't receive any packet from remote peer during this time (in milliseconds) then connection will be closed + /// (including library internal keepalive packets) + /// + public int DisconnectTimeout = 5000; + + /// + /// Simulate packet loss by dropping random amount of packets. (Works only in DEBUG builds or when SIMULATE_NETWORK is defined) + /// + public bool SimulatePacketLoss = false; + + /// + /// Simulate latency by holding packets for random time. (Works only in DEBUG builds or when SIMULATE_NETWORK is defined) + /// + public bool SimulateLatency = false; + + /// + /// Chance of packet loss when simulation enabled. value in percents (1 - 100). + /// + public int SimulationPacketLossChance = 10; + + /// + /// Minimum simulated round-trip latency (in milliseconds). Actual latency applied per direction is half of this value. + /// + public int SimulationMinLatency = 30; + + /// + /// Maximum simulated round-trip latency (in milliseconds). Actual latency applied per direction is half of this value. + /// + public int SimulationMaxLatency = 100; + + /// + /// Events automatically will be called without PollEvents method from another thread + /// + public bool UnsyncedEvents = false; + + /// + /// If true - receive event will be called from "receive" thread immediately otherwise on PollEvents call + /// + public bool UnsyncedReceiveEvent = false; + + /// + /// If true - delivery event will be called from "receive" thread immediately otherwise on PollEvents call + /// + public bool UnsyncedDeliveryEvent = false; + + /// + /// Allows receive broadcast packets + /// + public bool BroadcastReceiveEnabled = false; + + /// + /// Delay between initial connection attempts (in milliseconds) + /// + public int ReconnectDelay = 500; + + /// + /// Maximum connection attempts before client stops and call disconnect event. + /// + public int MaxConnectAttempts = 10; + + /// + /// Enables socket option "ReuseAddress" for specific purposes + /// + public bool ReuseAddress = false; + + /// + /// UDP Only Socket Option + /// Normally IP sockets send packets of data through routers and gateways until they reach the final destination. + /// If the DontRoute flag is set to True, then data will be delivered on the local subnet only. + /// + public bool DontRoute = false; + + /// + /// Statistics of all connections + /// + public readonly NetStatistics Statistics = new NetStatistics(); + + /// + /// Toggles the collection of network statistics for the instance and all known peers + /// + public bool EnableStatistics = false; + + /// + /// NatPunchModule for NAT hole punching operations + /// + public readonly NatPunchModule NatPunchModule; + + /// + /// Returns true if socket listening and update thread is running + /// + public bool IsRunning => _isRunning; + + /// + /// Local EndPoint (host and port) + /// + public int LocalPort { get; private set; } + + /// + /// Automatically recycle NetPacketReader after OnReceive event + /// + public bool AutoRecycle; + + /// + /// IPv6 support + /// + public bool IPv6Enabled = true; + + /// + /// Override MTU for all new peers registered in this NetManager, will ignores MTU Discovery! + /// + public int MtuOverride = 0; + + /// + /// Automatically discovery mtu starting from. Use at own risk because some routers can break MTU detection + /// and connection in result + /// + public bool MtuDiscovery = false; + + /// + /// First peer. Useful for Client mode + /// + public NetPeer FirstPeer => _headPeer; + + /// + /// Experimental feature mostly for servers. Only for Windows/Linux + /// use direct socket calls for send/receive to drastically increase speed and reduce GC pressure + /// + public bool UseNativeSockets = false; + + /// + /// Disconnect peers if HostUnreachable or NetworkUnreachable spawned (old behaviour 0.9.x was true) + /// + public bool DisconnectOnUnreachable = false; + + /// + /// Allows peer change it's ip (lte to wifi, wifi to lte, etc). Use only on server + /// + public bool AllowPeerAddressChange = false; + + /// + /// QoS channel count per message type (value must be between 1 and 64 channels) + /// + public byte ChannelsCount + { + get => _channelsCount; + set + { + if (value < 1 || value > 64) + throw new ArgumentException("Channels count must be between 1 and 64"); + _channelsCount = value; + } + } + + /// + /// Returns connected peers list (with internal cached list) + /// + public List ConnectedPeerList + { + get + { + GetPeersNonAlloc(_connectedPeerListCache, ConnectionState.Connected); + return _connectedPeerListCache; + } + } + + /// + /// Returns connected peers count + /// + public int ConnectedPeersCount => (int)Interlocked.Read(ref _connectedPeersCount); + + public int ExtraPacketSizeForLayer => _extraPacketLayer?.ExtraPacketSizeForLayer ?? 0; + + /// + /// NetManager constructor + /// + /// Network events listener (also can implement IDeliveryEventListener) + /// Extra processing of packages, like CRC checksum or encryption. All connected NetManagers must have same layer. +#if UNITY_SOCKET_FIX + public NetManager(INetEventListener listener, PacketLayerBase extraPacketLayer = null, bool useSocketFix = true) + { + _useSocketFix = useSocketFix; +#else + public NetManager(INetEventListener listener, PacketLayerBase extraPacketLayer = null) + { +#endif + _netEventListener = listener; + _deliveryEventListener = listener as IDeliveryEventListener; + _ntpEventListener = listener as INtpEventListener; + _peerAddressChangedListener = listener as IPeerAddressChangedListener; + NatPunchModule = new NatPunchModule(this); + _extraPacketLayer = extraPacketLayer; + } + + internal void ConnectionLatencyUpdated(NetPeer fromPeer, int latency) + { + CreateEvent(NetEvent.EType.ConnectionLatencyUpdated, fromPeer, latency: latency); + } + + internal void MessageDelivered(NetPeer fromPeer, object userData) + { + if (_deliveryEventListener != null) + CreateEvent(NetEvent.EType.MessageDelivered, fromPeer, userData: userData); + } + + internal void DisconnectPeerForce(NetPeer peer, + DisconnectReason reason, + SocketError socketErrorCode, + NetPacket eventData) + { + DisconnectPeer(peer, reason, socketErrorCode, true, null, 0, 0, eventData); + } + + private void DisconnectPeer( + NetPeer peer, + DisconnectReason reason, + SocketError socketErrorCode, + bool force, + byte[] data, + int start, + int count, + NetPacket eventData) + { + var shutdownResult = peer.Shutdown(data, start, count, force); + if (shutdownResult == ShutdownResult.None) + return; + if (shutdownResult == ShutdownResult.WasConnected) + Interlocked.Decrement(ref _connectedPeersCount); + CreateEvent( + NetEvent.EType.Disconnect, + peer, + errorCode: socketErrorCode, + disconnectReason: reason, + readerSource: eventData); + } + + private void CreateEvent( + NetEvent.EType type, + NetPeer peer = null, + IPEndPoint remoteEndPoint = null, + SocketError errorCode = 0, + int latency = 0, + DisconnectReason disconnectReason = DisconnectReason.ConnectionFailed, + ConnectionRequest connectionRequest = null, + DeliveryMethod deliveryMethod = DeliveryMethod.Unreliable, + byte channelNumber = 0, + NetPacket readerSource = null, + object userData = null) + { + NetEvent evt; + bool unsyncEvent = UnsyncedEvents; + + if (type == NetEvent.EType.Connect) + Interlocked.Increment(ref _connectedPeersCount); + else if (type == NetEvent.EType.MessageDelivered) + unsyncEvent = UnsyncedDeliveryEvent; + + lock (_eventLock) + { + evt = _netEventPoolHead; + if (evt == null) + evt = new NetEvent(this); + else + _netEventPoolHead = evt.Next; + } + + evt.Next = null; + evt.Type = type; + evt.DataReader.SetSource(readerSource, readerSource?.GetHeaderSize() ?? 0); + evt.Peer = peer; + evt.RemoteEndPoint = remoteEndPoint; + evt.Latency = latency; + evt.ErrorCode = errorCode; + evt.DisconnectReason = disconnectReason; + evt.ConnectionRequest = connectionRequest; + evt.DeliveryMethod = deliveryMethod; + evt.ChannelNumber = channelNumber; + evt.UserData = userData; + + if (unsyncEvent || _manualMode) + { + ProcessEvent(evt); + } + else + { + lock (_eventLock) + { + if (_pendingEventTail == null) + _pendingEventHead = evt; + else + _pendingEventTail.Next = evt; + _pendingEventTail = evt; + } + } + } + + private void ProcessEvent(NetEvent evt) + { + NetDebug.Write("[NM] Processing event: " + evt.Type); + bool emptyData = evt.DataReader.IsNull; + switch (evt.Type) + { + case NetEvent.EType.Connect: + _netEventListener.OnPeerConnected(evt.Peer); + break; + case NetEvent.EType.Disconnect: + var info = new DisconnectInfo + { + Reason = evt.DisconnectReason, + AdditionalData = evt.DataReader, + SocketErrorCode = evt.ErrorCode + }; + _netEventListener.OnPeerDisconnected(evt.Peer, info); + break; + case NetEvent.EType.Receive: + _netEventListener.OnNetworkReceive(evt.Peer, evt.DataReader, evt.ChannelNumber, evt.DeliveryMethod); + break; + case NetEvent.EType.ReceiveUnconnected: + _netEventListener.OnNetworkReceiveUnconnected(evt.RemoteEndPoint, evt.DataReader, UnconnectedMessageType.BasicMessage); + break; + case NetEvent.EType.Broadcast: + _netEventListener.OnNetworkReceiveUnconnected(evt.RemoteEndPoint, evt.DataReader, UnconnectedMessageType.Broadcast); + break; + case NetEvent.EType.Error: + _netEventListener.OnNetworkError(evt.RemoteEndPoint, evt.ErrorCode); + break; + case NetEvent.EType.ConnectionLatencyUpdated: + _netEventListener.OnNetworkLatencyUpdate(evt.Peer, evt.Latency); + break; + case NetEvent.EType.ConnectionRequest: + _netEventListener.OnConnectionRequest(evt.ConnectionRequest); + break; + case NetEvent.EType.MessageDelivered: + _deliveryEventListener.OnMessageDelivered(evt.Peer, evt.UserData); + break; + case NetEvent.EType.PeerAddressChanged: + _peersLock.EnterUpgradeableReadLock(); + IPEndPoint previousAddress = null; + if (ContainsPeer(evt.Peer)) + { + _peersLock.EnterWriteLock(); + RemovePeerFromSet(evt.Peer); + previousAddress = new IPEndPoint(evt.Peer.Address, evt.Peer.Port); + evt.Peer.FinishEndPointChange(evt.RemoteEndPoint); + AddPeerToSet(evt.Peer); + _peersLock.ExitWriteLock(); + } + _peersLock.ExitUpgradeableReadLock(); + if (previousAddress != null && _peerAddressChangedListener != null) + _peerAddressChangedListener.OnPeerAddressChanged(evt.Peer, previousAddress); + break; + } + //Recycle if not message + if (emptyData) + RecycleEvent(evt); + else if (AutoRecycle) + evt.DataReader.RecycleInternal(); + } + + internal void RecycleEvent(NetEvent evt) + { + evt.Peer = null; + evt.ErrorCode = 0; + evt.RemoteEndPoint = null; + evt.ConnectionRequest = null; + lock (_eventLock) + { + evt.Next = _netEventPoolHead; + _netEventPoolHead = evt; + } + } + + //Update function + private void UpdateLogic() + { + var peersToRemove = new List(); + var stopwatch = new Stopwatch(); + stopwatch.Start(); + + while (_isRunning) + { + try + { + ProcessDelayedPackets(); + float elapsed = (float)(stopwatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000.0); + elapsed = elapsed <= 0.0f ? 0.001f : elapsed; + stopwatch.Restart(); + + for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) + { + if (netPeer.ConnectionState == ConnectionState.Disconnected && + netPeer.TimeSinceLastPacket > DisconnectTimeout) + { + peersToRemove.Add(netPeer); + } + else + { + netPeer.Update(elapsed); + } + } + + if (peersToRemove.Count > 0) + { + _peersLock.EnterWriteLock(); + for (int i = 0; i < peersToRemove.Count; i++) + RemovePeer(peersToRemove[i], false); + _peersLock.ExitWriteLock(); + peersToRemove.Clear(); + } + + ProcessNtpRequests(elapsed); + + int sleepTime = UpdateTime - (int)stopwatch.ElapsedMilliseconds; + if (sleepTime > 0) + _updateTriggerEvent.WaitOne(sleepTime); + } + catch (ThreadAbortException) + { + return; + } + catch (Exception e) + { + NetDebug.WriteError("[NM] LogicThread error: " + e); + } + } + stopwatch.Stop(); + } + + [Conditional("DEBUG"), Conditional("SIMULATE_NETWORK")] + private void ProcessDelayedPackets() + { + if (!SimulateLatency) + return; + + var time = DateTime.UtcNow; + lock (_pingSimulationList) + { + for (int i = 0; i < _pingSimulationList.Count; i++) + { + var incomingData = _pingSimulationList[i]; + if (incomingData.TimeWhenGet <= time) + { + HandleMessageReceived(incomingData.Data, incomingData.EndPoint); + _pingSimulationList.RemoveAt(i); + i--; + } + } + } + + lock (_outboundSimulationList) + { + for (int i = 0; i < _outboundSimulationList.Count; i++) + { + var outboundData = _outboundSimulationList[i]; + if (outboundData.TimeWhenSend <= time) + { + // Send the delayed packet directly to socket layer bypassing simulation + SendRawCore(outboundData.Data, outboundData.Start, outboundData.Length, outboundData.EndPoint); + _outboundSimulationList.RemoveAt(i); + i--; + } + } + } + } + + private void ProcessNtpRequests(float elapsedMilliseconds) + { + if (_ntpRequests.IsEmpty) + { + return; + } + + List requestsToRemove = null; + foreach (var ntpRequest in _ntpRequests) + { + ntpRequest.Value.Send(_udpSocketv4, elapsedMilliseconds); + if (ntpRequest.Value.NeedToKill) + { + if (requestsToRemove == null) + requestsToRemove = new List(); + requestsToRemove.Add(ntpRequest.Key); + } + } + + if (requestsToRemove != null) + { + foreach (var ipEndPoint in requestsToRemove) + { + _ntpRequests.TryRemove(ipEndPoint, out _); + } + } + } + + /// + /// Update and send logic. Use this only when NetManager started in manual mode + /// + /// elapsed milliseconds since last update call + public void ManualUpdate(float elapsedMilliseconds) + { + if (!_manualMode) + return; + + for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) + { + if (netPeer.ConnectionState == ConnectionState.Disconnected && netPeer.TimeSinceLastPacket > DisconnectTimeout) + { + RemovePeer(netPeer, false); + } + else + { + netPeer.Update(elapsedMilliseconds); + } + } + ProcessNtpRequests(elapsedMilliseconds); + } + + internal NetPeer OnConnectionSolved(ConnectionRequest request, byte[] rejectData, int start, int length) + { + NetPeer netPeer = null; + + if (request.Result == ConnectionRequestResult.RejectForce) + { + NetDebug.Write(NetLogLevel.Trace, "[NM] Peer connect reject force."); + if (rejectData != null && length > 0) + { + var shutdownPacket = PoolGetWithProperty(PacketProperty.Disconnect, length); + shutdownPacket.ConnectionNumber = request.InternalPacket.ConnectionNumber; + FastBitConverter.GetBytes(shutdownPacket.RawData, 1, request.InternalPacket.ConnectionTime); + if (shutdownPacket.Size >= NetConstants.PossibleMtu[0]) + NetDebug.WriteError("[Peer] Disconnect additional data size more than MTU!"); + else + Buffer.BlockCopy(rejectData, start, shutdownPacket.RawData, 9, length); + SendRawAndRecycle(shutdownPacket, request.RemoteEndPoint); + } + lock (_requestsDict) + _requestsDict.Remove(request.RemoteEndPoint); + } + else lock (_requestsDict) + { + if (TryGetPeer(request.RemoteEndPoint, out netPeer)) + { + //already have peer + } + else if (request.Result == ConnectionRequestResult.Reject) + { + netPeer = new NetPeer(this, request.RemoteEndPoint, GetNextPeerId()); + netPeer.Reject(request.InternalPacket, rejectData, start, length); + AddPeer(netPeer); + NetDebug.Write(NetLogLevel.Trace, "[NM] Peer connect reject."); + } + else //Accept + { + netPeer = new NetPeer(this, request, GetNextPeerId()); + AddPeer(netPeer); + CreateEvent(NetEvent.EType.Connect, netPeer); + NetDebug.Write(NetLogLevel.Trace, $"[NM] Received peer connection Id: {netPeer.ConnectTime}, EP: {netPeer}"); + } + _requestsDict.Remove(request.RemoteEndPoint); + } + + return netPeer; + } + + private int GetNextPeerId() + { + return _peerIds.TryDequeue(out int id) ? id : _lastPeerId++; + } + + private void ProcessConnectRequest( + IPEndPoint remoteEndPoint, + NetPeer netPeer, + NetConnectRequestPacket connRequest) + { + //if we have peer + if (netPeer != null) + { + var processResult = netPeer.ProcessConnectRequest(connRequest); + NetDebug.Write($"ConnectRequest LastId: {netPeer.ConnectTime}, NewId: {connRequest.ConnectionTime}, EP: {remoteEndPoint}, Result: {processResult}"); + + switch (processResult) + { + case ConnectRequestResult.Reconnection: + DisconnectPeerForce(netPeer, DisconnectReason.Reconnect, 0, null); + RemovePeer(netPeer, true); + //go to new connection + break; + case ConnectRequestResult.NewConnection: + RemovePeer(netPeer, true); + //go to new connection + break; + case ConnectRequestResult.P2PLose: + DisconnectPeerForce(netPeer, DisconnectReason.PeerToPeerConnection, 0, null); + RemovePeer(netPeer, true); + //go to new connection + break; + default: + //no operations needed + return; + } + //ConnectRequestResult.NewConnection + //Set next connection number + if (processResult != ConnectRequestResult.P2PLose) + connRequest.ConnectionNumber = (byte)((netPeer.ConnectionNum + 1) % NetConstants.MaxConnectionNumber); + //To reconnect peer + } + else + { + NetDebug.Write($"ConnectRequest Id: {connRequest.ConnectionTime}, EP: {remoteEndPoint}"); + } + + ConnectionRequest req; + lock (_requestsDict) + { + if (_requestsDict.TryGetValue(remoteEndPoint, out req)) + { + req.UpdateRequest(connRequest); + return; + } + req = new ConnectionRequest(remoteEndPoint, connRequest, this); + _requestsDict.Add(remoteEndPoint, req); + } + NetDebug.Write($"[NM] Creating request event: {connRequest.ConnectionTime}"); + CreateEvent(NetEvent.EType.ConnectionRequest, connectionRequest: req); + } + + private void OnMessageReceived(NetPacket packet, IPEndPoint remoteEndPoint) + { + if (packet.Size == 0) + { + PoolRecycle(packet); + return; + } + + _dropPacket = false; + HandleSimulateLatency(packet, remoteEndPoint); + HandleSimulatePacketLoss(); + if (_dropPacket) + { + return; + } + + // ProcessEvents + HandleMessageReceived(packet, remoteEndPoint); + } + + [Conditional("DEBUG"), Conditional("SIMULATE_NETWORK")] + private void HandleSimulateLatency(NetPacket packet, IPEndPoint remoteEndPoint) + { + if (!SimulateLatency) + { + return; + } + + int roundTripLatency = _randomGenerator.Next(SimulationMinLatency, SimulationMaxLatency); + int inboundLatency = roundTripLatency / 2; + if (inboundLatency > MinLatencyThreshold) + { + lock (_pingSimulationList) + { + _pingSimulationList.Add(new IncomingData + { + Data = packet, + EndPoint = remoteEndPoint, + TimeWhenGet = DateTime.UtcNow.AddMilliseconds(inboundLatency) + }); + } + // hold packet + _dropPacket = true; + } + } + + [Conditional("DEBUG"), Conditional("SIMULATE_NETWORK")] + private void HandleSimulatePacketLoss() + { + if (SimulatePacketLoss && _randomGenerator.NextDouble() * 100 < SimulationPacketLossChance) + { + _dropPacket = true; + } + } + +#if DEBUG || SIMULATE_NETWORK + private bool HandleSimulateOutboundLatency(byte[] data, int start, int length, IPEndPoint remoteEndPoint) + { + if (!SimulateLatency) + { + return false; + } + + int roundTripLatency = _randomGenerator.Next(SimulationMinLatency, SimulationMaxLatency); + int outboundLatency = roundTripLatency / 2; + if (outboundLatency > MinLatencyThreshold) + { + // Create a copy of the data to avoid issues with recycled packets + byte[] dataCopy = new byte[length]; + Array.Copy(data, start, dataCopy, 0, length); + + lock (_outboundSimulationList) + { + _outboundSimulationList.Add(new OutboundDelayedPacket + { + Data = dataCopy, + Start = 0, + Length = length, + EndPoint = remoteEndPoint, + TimeWhenSend = DateTime.UtcNow.AddMilliseconds(outboundLatency) + }); + } + + return true; + } + return false; + } +#endif + +#if DEBUG || SIMULATE_NETWORK + private bool HandleSimulateOutboundPacketLoss() + { + bool shouldDrop = SimulatePacketLoss && _randomGenerator.NextDouble() * 100 < SimulationPacketLossChance; + return shouldDrop; + } +#endif + + private void HandleMessageReceived(NetPacket packet, IPEndPoint remoteEndPoint) + { + var originalPacketSize = packet.Size; + if (EnableStatistics) + { + Statistics.IncrementPacketsReceived(); + Statistics.AddBytesReceived(originalPacketSize); + } + + if (_ntpRequests.Count > 0 && _ntpRequests.TryGetValue(remoteEndPoint, out var request)) + { + if (packet.Size < 48) + { + NetDebug.Write(NetLogLevel.Trace, $"NTP response too short: {packet.Size}"); + return; + } + + byte[] copiedData = new byte[packet.Size]; + Buffer.BlockCopy(packet.RawData, 0, copiedData, 0, packet.Size); + NtpPacket ntpPacket = NtpPacket.FromServerResponse(copiedData, DateTime.UtcNow); + try + { + ntpPacket.ValidateReply(); + } + catch (InvalidOperationException ex) + { + NetDebug.Write(NetLogLevel.Trace, $"NTP response error: {ex.Message}"); + ntpPacket = null; + } + + if (ntpPacket != null) + { + _ntpRequests.TryRemove(remoteEndPoint, out _); + _ntpEventListener?.OnNtpResponse(ntpPacket); + } + return; + } + + if (_extraPacketLayer != null) + { + _extraPacketLayer.ProcessInboundPacket(ref remoteEndPoint, ref packet.RawData, ref packet.Size); + if (packet.Size == 0) + return; + } + + if (!packet.Verify()) + { + NetDebug.WriteError("[NM] DataReceived: bad!"); + PoolRecycle(packet); + return; + } + + switch (packet.Property) + { + //special case connect request + case PacketProperty.ConnectRequest: + if (NetConnectRequestPacket.GetProtocolId(packet) != NetConstants.ProtocolId) + { + SendRawAndRecycle(PoolGetWithProperty(PacketProperty.InvalidProtocol), remoteEndPoint); + return; + } + break; + //unconnected messages + case PacketProperty.Broadcast: + if (!BroadcastReceiveEnabled) + return; + CreateEvent(NetEvent.EType.Broadcast, remoteEndPoint: remoteEndPoint, readerSource: packet); + return; + case PacketProperty.UnconnectedMessage: + if (!UnconnectedMessagesEnabled) + return; + CreateEvent(NetEvent.EType.ReceiveUnconnected, remoteEndPoint: remoteEndPoint, readerSource: packet); + return; + case PacketProperty.NatMessage: + if (NatPunchEnabled) + NatPunchModule.ProcessMessage(remoteEndPoint, packet); + return; + } + + //Check normal packets + bool peerFound = remoteEndPoint is NetPeer netPeer || TryGetPeer(remoteEndPoint, out netPeer); + + if (peerFound && EnableStatistics) + { + netPeer.Statistics.IncrementPacketsReceived(); + netPeer.Statistics.AddBytesReceived(originalPacketSize); + } + + switch (packet.Property) + { + case PacketProperty.ConnectRequest: + var connRequest = NetConnectRequestPacket.FromData(packet); + if (connRequest != null) + ProcessConnectRequest(remoteEndPoint, netPeer, connRequest); + break; + case PacketProperty.PeerNotFound: + if (peerFound) //local + { + if (netPeer.ConnectionState != ConnectionState.Connected) + return; + if (packet.Size == 1) + { + //first reply + //send NetworkChanged packet + netPeer.ResetMtu(); + SendRaw(NetConnectAcceptPacket.MakeNetworkChanged(netPeer), remoteEndPoint); + NetDebug.Write($"PeerNotFound sending connection info: {remoteEndPoint}"); + } + else if (packet.Size == 2 && packet.RawData[1] == 1) + { + //second reply + DisconnectPeerForce(netPeer, DisconnectReason.PeerNotFound, 0, null); + } + } + else if (packet.Size > 1) //remote + { + //check if this is old peer + bool isOldPeer = false; + + if (AllowPeerAddressChange) + { + NetDebug.Write($"[NM] Looks like address change: {packet.Size}"); + var remoteData = NetConnectAcceptPacket.FromData(packet); + if (remoteData != null && + remoteData.PeerNetworkChanged && + remoteData.PeerId < _peersArray.Length) + { + _peersLock.EnterUpgradeableReadLock(); + var peer = _peersArray[remoteData.PeerId]; + _peersLock.ExitUpgradeableReadLock(); + if (peer != null && + peer.ConnectTime == remoteData.ConnectionTime && + peer.ConnectionNum == remoteData.ConnectionNumber) + { + if (peer.ConnectionState == ConnectionState.Connected) + { + peer.InitiateEndPointChange(); + CreateEvent(NetEvent.EType.PeerAddressChanged, peer, remoteEndPoint); + NetDebug.Write("[NM] PeerNotFound change address of remote peer"); + } + isOldPeer = true; + } + } + } + + PoolRecycle(packet); + + //else peer really not found + if (!isOldPeer) + { + var secondResponse = PoolGetWithProperty(PacketProperty.PeerNotFound, 1); + secondResponse.RawData[1] = 1; + SendRawAndRecycle(secondResponse, remoteEndPoint); + } + } + break; + case PacketProperty.InvalidProtocol: + if (peerFound && netPeer.ConnectionState == ConnectionState.Outgoing) + DisconnectPeerForce(netPeer, DisconnectReason.InvalidProtocol, 0, null); + break; + case PacketProperty.Disconnect: + if (peerFound) + { + var disconnectResult = netPeer.ProcessDisconnect(packet); + if (disconnectResult == DisconnectResult.None) + { + PoolRecycle(packet); + return; + } + DisconnectPeerForce( + netPeer, + disconnectResult == DisconnectResult.Disconnect + ? DisconnectReason.RemoteConnectionClose + : DisconnectReason.ConnectionRejected, + 0, packet); + } + else + { + PoolRecycle(packet); + } + //Send shutdown + SendRawAndRecycle(PoolGetWithProperty(PacketProperty.ShutdownOk), remoteEndPoint); + break; + case PacketProperty.ConnectAccept: + if (!peerFound) + return; + var connAccept = NetConnectAcceptPacket.FromData(packet); + if (connAccept != null && netPeer.ProcessConnectAccept(connAccept)) + CreateEvent(NetEvent.EType.Connect, netPeer); + break; + default: + if (peerFound) + netPeer.ProcessPacket(packet); + else + SendRawAndRecycle(PoolGetWithProperty(PacketProperty.PeerNotFound), remoteEndPoint); + break; + } + } + + internal void CreateReceiveEvent(NetPacket packet, DeliveryMethod method, byte channelNumber, int headerSize, NetPeer fromPeer) + { + NetEvent evt; + + if (UnsyncedEvents || UnsyncedReceiveEvent || _manualMode) + { + lock (_eventLock) + { + evt = _netEventPoolHead; + if (evt == null) + evt = new NetEvent(this); + else + _netEventPoolHead = evt.Next; + } + evt.Next = null; + evt.Type = NetEvent.EType.Receive; + evt.DataReader.SetSource(packet, headerSize); + evt.Peer = fromPeer; + evt.DeliveryMethod = method; + evt.ChannelNumber = channelNumber; + ProcessEvent(evt); + } + else + { + lock (_eventLock) + { + evt = _netEventPoolHead; + if (evt == null) + evt = new NetEvent(this); + else + _netEventPoolHead = evt.Next; + + evt.Next = null; + evt.Type = NetEvent.EType.Receive; + evt.DataReader.SetSource(packet, headerSize); + evt.Peer = fromPeer; + evt.DeliveryMethod = method; + evt.ChannelNumber = channelNumber; + + if (_pendingEventTail == null) + _pendingEventHead = evt; + else + _pendingEventTail.Next = evt; + _pendingEventTail = evt; + } + } + } + + /// + /// Send data to all connected peers (channel - 0) + /// + /// DataWriter with data + /// Send options (reliable, unreliable, etc.) + public void SendToAll(NetDataWriter writer, DeliveryMethod options) + { + SendToAll(writer.Data, 0, writer.Length, options); + } + + /// + /// Send data to all connected peers (channel - 0) + /// + /// Data + /// Send options (reliable, unreliable, etc.) + public void SendToAll(byte[] data, DeliveryMethod options) + { + SendToAll(data, 0, data.Length, options); + } + + /// + /// Send data to all connected peers (channel - 0) + /// + /// Data + /// Start of data + /// Length of data + /// Send options (reliable, unreliable, etc.) + public void SendToAll(byte[] data, int start, int length, DeliveryMethod options) + { + SendToAll(data, start, length, 0, options); + } + + /// + /// Send data to all connected peers + /// + /// DataWriter with data + /// Number of channel (from 0 to channelsCount - 1) + /// Send options (reliable, unreliable, etc.) + public void SendToAll(NetDataWriter writer, byte channelNumber, DeliveryMethod options) + { + SendToAll(writer.Data, 0, writer.Length, channelNumber, options); + } + + /// + /// Send data to all connected peers + /// + /// Data + /// Number of channel (from 0 to channelsCount - 1) + /// Send options (reliable, unreliable, etc.) + public void SendToAll(byte[] data, byte channelNumber, DeliveryMethod options) + { + SendToAll(data, 0, data.Length, channelNumber, options); + } + + /// + /// Send data to all connected peers + /// + /// Data + /// Start of data + /// Length of data + /// Number of channel (from 0 to channelsCount - 1) + /// Send options (reliable, unreliable, etc.) + public void SendToAll(byte[] data, int start, int length, byte channelNumber, DeliveryMethod options) + { + try + { + _peersLock.EnterReadLock(); + for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) + netPeer.Send(data, start, length, channelNumber, options); + } + finally + { + _peersLock.ExitReadLock(); + } + } + + /// + /// Send data to all connected peers (channel - 0) + /// + /// DataWriter with data + /// Send options (reliable, unreliable, etc.) + /// Excluded peer + public void SendToAll(NetDataWriter writer, DeliveryMethod options, NetPeer excludePeer) + { + SendToAll(writer.Data, 0, writer.Length, 0, options, excludePeer); + } + + /// + /// Send data to all connected peers (channel - 0) + /// + /// Data + /// Send options (reliable, unreliable, etc.) + /// Excluded peer + public void SendToAll(byte[] data, DeliveryMethod options, NetPeer excludePeer) + { + SendToAll(data, 0, data.Length, 0, options, excludePeer); + } + + /// + /// Send data to all connected peers (channel - 0) + /// + /// Data + /// Start of data + /// Length of data + /// Send options (reliable, unreliable, etc.) + /// Excluded peer + public void SendToAll(byte[] data, int start, int length, DeliveryMethod options, NetPeer excludePeer) + { + SendToAll(data, start, length, 0, options, excludePeer); + } + + /// + /// Send data to all connected peers + /// + /// DataWriter with data + /// Number of channel (from 0 to channelsCount - 1) + /// Send options (reliable, unreliable, etc.) + /// Excluded peer + public void SendToAll(NetDataWriter writer, byte channelNumber, DeliveryMethod options, NetPeer excludePeer) + { + SendToAll(writer.Data, 0, writer.Length, channelNumber, options, excludePeer); + } + + /// + /// Send data to all connected peers + /// + /// Data + /// Number of channel (from 0 to channelsCount - 1) + /// Send options (reliable, unreliable, etc.) + /// Excluded peer + public void SendToAll(byte[] data, byte channelNumber, DeliveryMethod options, NetPeer excludePeer) + { + SendToAll(data, 0, data.Length, channelNumber, options, excludePeer); + } + + /// + /// Send data to all connected peers + /// + /// Data + /// Start of data + /// Length of data + /// Number of channel (from 0 to channelsCount - 1) + /// Send options (reliable, unreliable, etc.) + /// Excluded peer + public void SendToAll(byte[] data, int start, int length, byte channelNumber, DeliveryMethod options, NetPeer excludePeer) + { + try + { + _peersLock.EnterReadLock(); + for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) + { + if (netPeer != excludePeer) + netPeer.Send(data, start, length, channelNumber, options); + } + } + finally + { + _peersLock.ExitReadLock(); + } + } + +#if LITENETLIB_SPANS || NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1 || NETCOREAPP3_1 || NET5_0 || NETSTANDARD2_1 + /// + /// Send data to all connected peers (channel - 0) + /// + /// Data + /// Send options (reliable, unreliable, etc.) + public void SendToAll(ReadOnlySpan data, DeliveryMethod options) + { + SendToAll(data, 0, options, null); + } + + /// + /// Send data to all connected peers (channel - 0) + /// + /// Data + /// Send options (reliable, unreliable, etc.) + /// Excluded peer + public void SendToAll(ReadOnlySpan data, DeliveryMethod options, NetPeer excludePeer) + { + SendToAll(data, 0, options, excludePeer); + } + + /// + /// Send data to all connected peers + /// + /// Data + /// Number of channel (from 0 to channelsCount - 1) + /// Send options (reliable, unreliable, etc.) + /// Excluded peer + public void SendToAll(ReadOnlySpan data, byte channelNumber, DeliveryMethod options, NetPeer excludePeer) + { + try + { + _peersLock.EnterReadLock(); + for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) + { + if (netPeer != excludePeer) + netPeer.Send(data, channelNumber, options); + } + } + finally + { + _peersLock.ExitReadLock(); + } + } + + /// + /// Send message without connection + /// + /// Raw data + /// Packet destination + /// Operation result + public bool SendUnconnectedMessage(ReadOnlySpan message, IPEndPoint remoteEndPoint) + { + int headerSize = NetPacket.GetHeaderSize(PacketProperty.UnconnectedMessage); + var packet = PoolGetPacket(message.Length + headerSize); + packet.Property = PacketProperty.UnconnectedMessage; + message.CopyTo(new Span(packet.RawData, headerSize, message.Length)); + return SendRawAndRecycle(packet, remoteEndPoint) > 0; + } +#endif + + /// + /// Start logic thread and listening on available port + /// + public bool Start() + { + return Start(0); + } + + /// + /// Start logic thread and listening on selected port + /// + /// bind to specific ipv4 address + /// bind to specific ipv6 address + /// port to listen + public bool Start(IPAddress addressIPv4, IPAddress addressIPv6, int port) + { + return Start(addressIPv4, addressIPv6, port, false); + } + + /// + /// Start logic thread and listening on selected port + /// + /// bind to specific ipv4 address + /// bind to specific ipv6 address + /// port to listen + public bool Start(string addressIPv4, string addressIPv6, int port) + { + IPAddress ipv4 = NetUtils.ResolveAddress(addressIPv4); + IPAddress ipv6 = NetUtils.ResolveAddress(addressIPv6); + return Start(ipv4, ipv6, port); + } + + /// + /// Start logic thread and listening on selected port + /// + /// port to listen + public bool Start(int port) + { + return Start(IPAddress.Any, IPAddress.IPv6Any, port); + } + + /// + /// Start in manual mode and listening on selected port + /// In this mode you should use ManualReceive (without PollEvents) for receive packets + /// and ManualUpdate(...) for update and send packets + /// This mode useful mostly for single-threaded servers + /// + /// bind to specific ipv4 address + /// bind to specific ipv6 address + /// port to listen + public bool StartInManualMode(IPAddress addressIPv4, IPAddress addressIPv6, int port) + { + return Start(addressIPv4, addressIPv6, port, true); + } + + /// + /// Start in manual mode and listening on selected port + /// In this mode you should use ManualReceive (without PollEvents) for receive packets + /// and ManualUpdate(...) for update and send packets + /// This mode useful mostly for single-threaded servers + /// + /// bind to specific ipv4 address + /// bind to specific ipv6 address + /// port to listen + public bool StartInManualMode(string addressIPv4, string addressIPv6, int port) + { + IPAddress ipv4 = NetUtils.ResolveAddress(addressIPv4); + IPAddress ipv6 = NetUtils.ResolveAddress(addressIPv6); + return StartInManualMode(ipv4, ipv6, port); + } + + /// + /// Start in manual mode and listening on selected port + /// In this mode you should use ManualReceive (without PollEvents) for receive packets + /// and ManualUpdate(...) for update and send packets + /// This mode useful mostly for single-threaded servers + /// + /// port to listen + public bool StartInManualMode(int port) + { + return StartInManualMode(IPAddress.Any, IPAddress.IPv6Any, port); + } + + /// + /// Send message without connection + /// + /// Raw data + /// Packet destination + /// Operation result + public bool SendUnconnectedMessage(byte[] message, IPEndPoint remoteEndPoint) + { + return SendUnconnectedMessage(message, 0, message.Length, remoteEndPoint); + } + + /// + /// Send message without connection. WARNING This method allocates a new IPEndPoint object and + /// synchronously makes a DNS request. If you're calling this method every frame it will be + /// much faster to just cache the IPEndPoint. + /// + /// Data serializer + /// Packet destination IP or hostname + /// Packet destination port + /// Operation result + public bool SendUnconnectedMessage(NetDataWriter writer, string address, int port) + { + IPEndPoint remoteEndPoint = NetUtils.MakeEndPoint(address, port); + + return SendUnconnectedMessage(writer.Data, 0, writer.Length, remoteEndPoint); + } + + /// + /// Send message without connection + /// + /// Data serializer + /// Packet destination + /// Operation result + public bool SendUnconnectedMessage(NetDataWriter writer, IPEndPoint remoteEndPoint) + { + return SendUnconnectedMessage(writer.Data, 0, writer.Length, remoteEndPoint); + } + + /// + /// Send message without connection + /// + /// Raw data + /// data start + /// data length + /// Packet destination + /// Operation result + public bool SendUnconnectedMessage(byte[] message, int start, int length, IPEndPoint remoteEndPoint) + { + //No need for CRC here, SendRaw does that + NetPacket packet = PoolGetWithData(PacketProperty.UnconnectedMessage, message, start, length); + return SendRawAndRecycle(packet, remoteEndPoint) > 0; + } + + /// + /// Triggers update and send logic immediately (works asynchronously) + /// + public void TriggerUpdate() + { + _updateTriggerEvent.Set(); + } + + /// + /// Receive "maxProcessedEvents" pending events. Call this in game update code + /// In Manual mode it will call also socket Receive (which can be slow) + /// 0 - receive all events + /// + /// Max events that will be processed (called INetEventListener Connect/Receive/Etc), 0 - receive all events + public void PollEvents(int maxProcessedEvents = 0) + { + if (_manualMode) + { + if (_udpSocketv4 != null) + ManualReceive(_udpSocketv4, _bufferEndPointv4, maxProcessedEvents); + if (_udpSocketv6 != null && _udpSocketv6 != _udpSocketv4) + ManualReceive(_udpSocketv6, _bufferEndPointv6, maxProcessedEvents); + ProcessDelayedPackets(); + return; + } + if (UnsyncedEvents) + return; + NetEvent pendingEvent; + lock (_eventLock) + { + pendingEvent = _pendingEventHead; + _pendingEventHead = null; + _pendingEventTail = null; + } + + int counter = 0; + while (pendingEvent != null) + { + var next = pendingEvent.Next; + ProcessEvent(pendingEvent); + pendingEvent = next; + counter++; + if (counter == maxProcessedEvents) + break; + } + } + + /// + /// Connect to remote host + /// + /// Server IP or hostname + /// Server Port + /// Connection key + /// New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting + /// Manager is not running. Call + public NetPeer Connect(string address, int port, string key) + { + return Connect(address, port, NetDataWriter.FromString(key)); + } + + /// + /// Connect to remote host + /// + /// Server IP or hostname + /// Server Port + /// Additional data for remote peer + /// New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting + /// Manager is not running. Call + public NetPeer Connect(string address, int port, NetDataWriter connectionData) + { + IPEndPoint ep; + try + { + ep = NetUtils.MakeEndPoint(address, port); + } + catch + { + CreateEvent(NetEvent.EType.Disconnect, disconnectReason: DisconnectReason.UnknownHost); + return null; + } + return Connect(ep, connectionData); + } + + /// + /// Connect to remote host + /// + /// Server end point (ip and port) + /// Connection key + /// New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting + /// Manager is not running. Call + public NetPeer Connect(IPEndPoint target, string key) + { + return Connect(target, NetDataWriter.FromString(key)); + } + + /// + /// Connect to remote host + /// + /// Server end point (ip and port) + /// Additional data for remote peer + /// New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting + /// Manager is not running. Call + public NetPeer Connect(IPEndPoint target, NetDataWriter connectionData) + { + if (!_isRunning) + throw new InvalidOperationException("Client is not running"); + + lock (_requestsDict) + { + if (_requestsDict.ContainsKey(target)) + return null; + + byte connectionNumber = 0; + if (TryGetPeer(target, out var peer)) + { + switch (peer.ConnectionState) + { + //just return already connected peer + case ConnectionState.Connected: + case ConnectionState.Outgoing: + return peer; + } + //else reconnect + connectionNumber = (byte)((peer.ConnectionNum + 1) % NetConstants.MaxConnectionNumber); + RemovePeer(peer, true); + } + + //Create reliable connection + //And send connection request + peer = new NetPeer(this, target, GetNextPeerId(), connectionNumber, connectionData); + AddPeer(peer); + return peer; + } + } + +#if LITENETLIB_SPANS || NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1 || NETCOREAPP3_1 || NET5_0 || NETSTANDARD2_1 + /// + /// Connect to remote host + /// + /// Server end point (ip and port) + /// Additional data for remote peer + /// New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting + /// Manager is not running. Call + public NetPeer Connect(IPEndPoint target, ReadOnlySpan connectionData) + { + if (!_isRunning) + throw new InvalidOperationException("Client is not running"); + + lock (_requestsDict) + { + if (_requestsDict.ContainsKey(target)) + return null; + + byte connectionNumber = 0; + if (TryGetPeer(target, out var peer)) + { + switch (peer.ConnectionState) + { + //just return already connected peer + case ConnectionState.Connected: + case ConnectionState.Outgoing: + return peer; + } + //else reconnect + connectionNumber = (byte)((peer.ConnectionNum + 1) % NetConstants.MaxConnectionNumber); + RemovePeer(peer, true); + } + + //Create reliable connection + //And send connection request + peer = new NetPeer(this, target, GetNextPeerId(), connectionNumber, connectionData); + AddPeer(peer); + return peer; + } + } +#endif + + /// + /// Force closes connection and stop all threads. + /// + public void Stop() + { + Stop(true); + } + + /// + /// Force closes connection and stop all threads. + /// + /// Send disconnect messages + public void Stop(bool sendDisconnectMessages) + { + if (!_isRunning) + return; + NetDebug.Write("[NM] Stop"); + + //Send last disconnect + for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) + netPeer.Shutdown(null, 0, 0, !sendDisconnectMessages); + + //Stop + CloseSocket(); + +#if UNITY_SOCKET_FIX + if (_useSocketFix) + { + _pausedSocketFix.Deinitialize(); + _pausedSocketFix = null; + } +#endif + + _updateTriggerEvent.Set(); + if (!_manualMode) + { + _logicThread.Join(); + _logicThread = null; + } + + //clear peers + ClearPeerSet(); + _peerIds = new ConcurrentQueue(); + _lastPeerId = 0; + + ClearPingSimulationList(); + ClearOutboundSimulationList(); + + _connectedPeersCount = 0; + _pendingEventHead = null; + _pendingEventTail = null; + } + + [Conditional("DEBUG"), Conditional("SIMULATE_NETWORK")] + private void ClearPingSimulationList() + { + lock (_pingSimulationList) + _pingSimulationList.Clear(); + } + + [Conditional("DEBUG"), Conditional("SIMULATE_NETWORK")] + private void ClearOutboundSimulationList() + { + lock (_outboundSimulationList) + _outboundSimulationList.Clear(); + } + + /// + /// Return peers count with connection state + /// + /// peer connection state (you can use as bit flags) + /// peers count + public int GetPeersCount(ConnectionState peerState) + { + int count = 0; + _peersLock.EnterReadLock(); + for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) + { + if ((netPeer.ConnectionState & peerState) != 0) + count++; + } + _peersLock.ExitReadLock(); + return count; + } + + /// + /// Get copy of peers (without allocations) + /// + /// List that will contain result + /// State of peers + public void GetPeersNonAlloc(List peers, ConnectionState peerState) + { + peers.Clear(); + _peersLock.EnterReadLock(); + for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) + { + if ((netPeer.ConnectionState & peerState) != 0) + peers.Add(netPeer); + } + _peersLock.ExitReadLock(); + } + + /// + /// Disconnect all peers without any additional data + /// + public void DisconnectAll() + { + DisconnectAll(null, 0, 0); + } + + /// + /// Disconnect all peers with shutdown message + /// + /// Data to send (must be less or equal MTU) + /// Data start + /// Data count + public void DisconnectAll(byte[] data, int start, int count) + { + //Send disconnect packets + _peersLock.EnterReadLock(); + for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) + { + DisconnectPeer( + netPeer, + DisconnectReason.DisconnectPeerCalled, + 0, + false, + data, + start, + count, + null); + } + _peersLock.ExitReadLock(); + } + + /// + /// Immediately disconnect peer from server without additional data + /// + /// peer to disconnect + public void DisconnectPeerForce(NetPeer peer) + { + DisconnectPeerForce(peer, DisconnectReason.DisconnectPeerCalled, 0, null); + } + + /// + /// Disconnect peer from server + /// + /// peer to disconnect + public void DisconnectPeer(NetPeer peer) + { + DisconnectPeer(peer, null, 0, 0); + } + + /// + /// Disconnect peer from server and send additional data (Size must be less or equal MTU - 8) + /// + /// peer to disconnect + /// additional data + public void DisconnectPeer(NetPeer peer, byte[] data) + { + DisconnectPeer(peer, data, 0, data.Length); + } + + /// + /// Disconnect peer from server and send additional data (Size must be less or equal MTU - 8) + /// + /// peer to disconnect + /// additional data + public void DisconnectPeer(NetPeer peer, NetDataWriter writer) + { + DisconnectPeer(peer, writer.Data, 0, writer.Length); + } + + /// + /// Disconnect peer from server and send additional data (Size must be less or equal MTU - 8) + /// + /// peer to disconnect + /// additional data + /// data start + /// data length + public void DisconnectPeer(NetPeer peer, byte[] data, int start, int count) + { + DisconnectPeer( + peer, + DisconnectReason.DisconnectPeerCalled, + 0, + false, + data, + start, + count, + null); + } + + /// + /// Create the requests for NTP server + /// + /// NTP Server address. + public void CreateNtpRequest(IPEndPoint endPoint) + { + _ntpRequests.TryAdd(endPoint, new NtpRequest(endPoint)); + } + + /// + /// Create the requests for NTP server + /// + /// NTP Server address. + /// port + public void CreateNtpRequest(string ntpServerAddress, int port) + { + IPEndPoint endPoint = NetUtils.MakeEndPoint(ntpServerAddress, port); + _ntpRequests.TryAdd(endPoint, new NtpRequest(endPoint)); + } + + /// + /// Create the requests for NTP server (default port) + /// + /// NTP Server address. + public void CreateNtpRequest(string ntpServerAddress) + { + IPEndPoint endPoint = NetUtils.MakeEndPoint(ntpServerAddress, NtpRequest.DefaultPort); + _ntpRequests.TryAdd(endPoint, new NtpRequest(endPoint)); + } + + public NetPeerEnumerator GetEnumerator() + { + return new NetPeerEnumerator(_headPeer); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new NetPeerEnumerator(_headPeer); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new NetPeerEnumerator(_headPeer); + } + } +} diff --git a/Assets/Lib/LiteNetLib/NetManager.cs.meta b/Assets/Lib/LiteNetLib/NetManager.cs.meta new file mode 100644 index 0000000..c47b936 --- /dev/null +++ b/Assets/Lib/LiteNetLib/NetManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: baa362ccadee68346906cd154c8e87f3 \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/NetPacket.cs b/Assets/Lib/LiteNetLib/NetPacket.cs new file mode 100644 index 0000000..c1505d6 --- /dev/null +++ b/Assets/Lib/LiteNetLib/NetPacket.cs @@ -0,0 +1,164 @@ +using System; +using LiteNetLib.Utils; + +namespace LiteNetLib +{ + internal enum PacketProperty : byte + { + Unreliable, + Channeled, + Ack, + Ping, + Pong, + ConnectRequest, + ConnectAccept, + Disconnect, + UnconnectedMessage, + MtuCheck, + MtuOk, + Broadcast, + Merged, + ShutdownOk, + PeerNotFound, + InvalidProtocol, + NatMessage, + Empty + } + + internal sealed class NetPacket + { + private static readonly int PropertiesCount = Enum.GetValues(typeof(PacketProperty)).Length; + private static readonly int[] HeaderSizes; + + static NetPacket() + { + HeaderSizes = NetUtils.AllocatePinnedUninitializedArray(PropertiesCount); + for (int i = 0; i < HeaderSizes.Length; i++) + { + switch ((PacketProperty)i) + { + case PacketProperty.Channeled: + case PacketProperty.Ack: + HeaderSizes[i] = NetConstants.ChanneledHeaderSize; + break; + case PacketProperty.Ping: + HeaderSizes[i] = NetConstants.HeaderSize + 2; + break; + case PacketProperty.ConnectRequest: + HeaderSizes[i] = NetConnectRequestPacket.HeaderSize; + break; + case PacketProperty.ConnectAccept: + HeaderSizes[i] = NetConnectAcceptPacket.Size; + break; + case PacketProperty.Disconnect: + HeaderSizes[i] = NetConstants.HeaderSize + 8; + break; + case PacketProperty.Pong: + HeaderSizes[i] = NetConstants.HeaderSize + 10; + break; + default: + HeaderSizes[i] = NetConstants.HeaderSize; + break; + } + } + } + + //Header + public PacketProperty Property + { + get => (PacketProperty)(RawData[0] & 0x1F); + set => RawData[0] = (byte)((RawData[0] & 0xE0) | (byte)value); + } + + public byte ConnectionNumber + { + get => (byte)((RawData[0] & 0x60) >> 5); + set => RawData[0] = (byte) ((RawData[0] & 0x9F) | (value << 5)); + } + + public ushort Sequence + { + get => BitConverter.ToUInt16(RawData, 1); + set => FastBitConverter.GetBytes(RawData, 1, value); + } + + public bool IsFragmented => (RawData[0] & 0x80) != 0; + + public void MarkFragmented() + { + RawData[0] |= 0x80; //set first bit + } + + public byte ChannelId + { + get => RawData[3]; + set => RawData[3] = value; + } + + public ushort FragmentId + { + get => BitConverter.ToUInt16(RawData, 4); + set => FastBitConverter.GetBytes(RawData, 4, value); + } + + public ushort FragmentPart + { + get => BitConverter.ToUInt16(RawData, 6); + set => FastBitConverter.GetBytes(RawData, 6, value); + } + + public ushort FragmentsTotal + { + get => BitConverter.ToUInt16(RawData, 8); + set => FastBitConverter.GetBytes(RawData, 8, value); + } + + //Data + public byte[] RawData; + public int Size; + + //Delivery + public object UserData; + + //Pool node + public NetPacket Next; + + public NetPacket(int size) + { + RawData = new byte[size]; + Size = size; + } + + public NetPacket(PacketProperty property, int size) + { + size += GetHeaderSize(property); + RawData = new byte[size]; + Property = property; + Size = size; + } + + public static int GetHeaderSize(PacketProperty property) + { + return HeaderSizes[(int)property]; + } + + public int GetHeaderSize() + { + return HeaderSizes[RawData[0] & 0x1F]; + } + + public bool Verify() + { + byte property = (byte)(RawData[0] & 0x1F); + if (property >= PropertiesCount) + return false; + int headerSize = HeaderSizes[property]; + bool fragmented = (RawData[0] & 0x80) != 0; + return Size >= headerSize && (!fragmented || Size >= headerSize + NetConstants.FragmentHeaderSize); + } + + #if LITENETLIB_SPANS || NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1 || NETCOREAPP3_1 || NET5_0 || NETSTANDARD2_1 + public static implicit operator Span(NetPacket p) => new Span(p.RawData, 0, p.Size); + #endif + } +} diff --git a/Assets/Lib/LiteNetLib/NetPacket.cs.meta b/Assets/Lib/LiteNetLib/NetPacket.cs.meta new file mode 100644 index 0000000..995d932 --- /dev/null +++ b/Assets/Lib/LiteNetLib/NetPacket.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e36afb0c3e492af499a51e0cd5cddce5 \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/NetPeer.cs b/Assets/Lib/LiteNetLib/NetPeer.cs new file mode 100644 index 0000000..6e455bc --- /dev/null +++ b/Assets/Lib/LiteNetLib/NetPeer.cs @@ -0,0 +1,1489 @@ +#if DEBUG +#define STATS_ENABLED +#endif +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net; +using System.Runtime.CompilerServices; +using System.Threading; +using LiteNetLib.Utils; + +namespace LiteNetLib +{ + /// + /// Peer connection state + /// + [Flags] + public enum ConnectionState : byte + { + Outgoing = 1 << 1, + Connected = 1 << 2, + ShutdownRequested = 1 << 3, + Disconnected = 1 << 4, + EndPointChange = 1 << 5, + Any = Outgoing | Connected | ShutdownRequested | EndPointChange + } + + internal enum ConnectRequestResult + { + None, + P2PLose, //when peer connecting + Reconnection, //when peer was connected + NewConnection //when peer was disconnected + } + + internal enum DisconnectResult + { + None, + Reject, + Disconnect + } + + internal enum ShutdownResult + { + None, + Success, + WasConnected + } + + /// + /// Network peer. Main purpose is sending messages to specific peer. + /// + public class NetPeer : IPEndPoint + { + //Ping and RTT + private int _rtt; + private int _avgRtt; + private int _rttCount; + private double _resendDelay = 27.0; + private float _pingSendTimer; + private float _rttResetTimer; + private readonly Stopwatch _pingTimer = new Stopwatch(); + private volatile float _timeSinceLastPacket; + private long _remoteDelta; + + //Common + private readonly object _shutdownLock = new object(); + + internal volatile NetPeer NextPeer; + internal NetPeer PrevPeer; + + internal byte ConnectionNum + { + get => _connectNum; + private set + { + _connectNum = value; + _mergeData.ConnectionNumber = value; + _pingPacket.ConnectionNumber = value; + _pongPacket.ConnectionNumber = value; + } + } + + //Channels + private NetPacket[] _unreliableSecondQueue; + private NetPacket[] _unreliableChannel; + private int _unreliablePendingCount; + private readonly object _unreliableChannelLock = new object(); + + private readonly ConcurrentQueue _channelSendQueue; + private readonly BaseChannel[] _channels; + + //MTU + private int _mtu; + private int _mtuIdx; + private bool _finishMtu; + private float _mtuCheckTimer; + private int _mtuCheckAttempts; + private const int MtuCheckDelay = 1000; + private const int MaxMtuCheckAttempts = 4; + private readonly object _mtuMutex = new object(); + + //Fragment + private class IncomingFragments + { + public NetPacket[] Fragments; + public int ReceivedCount; + public int TotalSize; + public byte ChannelId; + } + private int _fragmentId; + private readonly Dictionary _holdedFragments; + private readonly Dictionary _deliveredFragments; + + //Merging + private readonly NetPacket _mergeData; + private int _mergePos; + private int _mergeCount; + + //Connection + private int _connectAttempts; + private float _connectTimer; + private long _connectTime; + private byte _connectNum; + private ConnectionState _connectionState; + private NetPacket _shutdownPacket; + private const int ShutdownDelay = 300; + private float _shutdownTimer; + private readonly NetPacket _pingPacket; + private readonly NetPacket _pongPacket; + private readonly NetPacket _connectRequestPacket; + private readonly NetPacket _connectAcceptPacket; + + /// + /// Peer parent NetManager + /// + public readonly NetManager NetManager; + + /// + /// Current connection state + /// + public ConnectionState ConnectionState => _connectionState; + + /// + /// Connection time for internal purposes + /// + internal long ConnectTime => _connectTime; + + /// + /// Peer id can be used as key in your dictionary of peers + /// + public readonly int Id; + + /// + /// Id assigned from server + /// + public int RemoteId { get; private set; } + + /// + /// Current one-way ping (RTT/2) in milliseconds + /// + public int Ping => _avgRtt/2; + + /// + /// Round trip time in milliseconds + /// + public int RoundTripTime => _avgRtt; + + /// + /// Current MTU - Maximum Transfer Unit ( maximum udp packet size without fragmentation ) + /// + public int Mtu => _mtu; + + /// + /// Delta with remote time in ticks (not accurate) + /// positive - remote time > our time + /// + public long RemoteTimeDelta => _remoteDelta; + + /// + /// Remote UTC time (not accurate) + /// + public DateTime RemoteUtcTime => new DateTime(DateTime.UtcNow.Ticks + _remoteDelta); + + /// + /// Time since last packet received (including internal library packets) in milliseconds + /// + public float TimeSinceLastPacket => _timeSinceLastPacket; + + internal double ResendDelay => _resendDelay; + + /// + /// Application defined object containing data about the connection + /// + public object Tag; + + /// + /// Statistics of peer connection + /// + public readonly NetStatistics Statistics; + + private SocketAddress _cachedSocketAddr; + private int _cachedHashCode; + + internal byte[] NativeAddress; + + /// + /// IPEndPoint serialize + /// + /// SocketAddress + public override SocketAddress Serialize() + { + return _cachedSocketAddr; + } + + public override int GetHashCode() + { + //uses SocketAddress hash in NET8 and IPEndPoint hash for NativeSockets and previous NET versions + return _cachedHashCode; + } + + //incoming connection constructor + internal NetPeer(NetManager netManager, IPEndPoint remoteEndPoint, int id) : base(remoteEndPoint.Address, remoteEndPoint.Port) + { + Id = id; + Statistics = new NetStatistics(); + NetManager = netManager; + + _cachedSocketAddr = base.Serialize(); + if (NetManager.UseNativeSockets) + { + NativeAddress = new byte[_cachedSocketAddr.Size]; + for (int i = 0; i < _cachedSocketAddr.Size; i++) + NativeAddress[i] = _cachedSocketAddr[i]; + } +#if NET8_0_OR_GREATER + _cachedHashCode = NetManager.UseNativeSockets ? base.GetHashCode() : _cachedSocketAddr.GetHashCode(); +#else + _cachedHashCode = base.GetHashCode(); +#endif + + ResetMtu(); + + _connectionState = ConnectionState.Connected; + _mergeData = new NetPacket(PacketProperty.Merged, NetConstants.MaxPacketSize); + _pongPacket = new NetPacket(PacketProperty.Pong, 0); + _pingPacket = new NetPacket(PacketProperty.Ping, 0) {Sequence = 1}; + + _unreliableSecondQueue = new NetPacket[8]; + _unreliableChannel = new NetPacket[8]; + _holdedFragments = new Dictionary(); + _deliveredFragments = new Dictionary(); + + _channels = new BaseChannel[netManager.ChannelsCount * NetConstants.ChannelTypeCount]; + _channelSendQueue = new ConcurrentQueue(); + } + + internal void InitiateEndPointChange() + { + ResetMtu(); + _connectionState = ConnectionState.EndPointChange; + } + + internal void FinishEndPointChange(IPEndPoint newEndPoint) + { + if (_connectionState != ConnectionState.EndPointChange) + return; + _connectionState = ConnectionState.Connected; + + Address = newEndPoint.Address; + Port = newEndPoint.Port; + + _cachedSocketAddr = base.Serialize(); + if (NetManager.UseNativeSockets) + { + NativeAddress = new byte[_cachedSocketAddr.Size]; + for (int i = 0; i < _cachedSocketAddr.Size; i++) + NativeAddress[i] = _cachedSocketAddr[i]; + } +#if NET8_0_OR_GREATER + _cachedHashCode = NetManager.UseNativeSockets ? base.GetHashCode() : _cachedSocketAddr.GetHashCode(); +#else + _cachedHashCode = base.GetHashCode(); +#endif + } + + internal void ResetMtu() + { + //finish if discovery disabled + _finishMtu = !NetManager.MtuDiscovery; + if (NetManager.MtuOverride > 0) + OverrideMtu(NetManager.MtuOverride); + else + SetMtu(0); + } + + private void SetMtu(int mtuIdx) + { + _mtuIdx = mtuIdx; + _mtu = NetConstants.PossibleMtu[mtuIdx] - NetManager.ExtraPacketSizeForLayer; + } + + private void OverrideMtu(int mtuValue) + { + _mtu = mtuValue; + _finishMtu = true; + } + + /// + /// Returns packets count in queue for reliable channel + /// + /// number of channel 0-63 + /// type of channel ReliableOrdered or ReliableUnordered + /// packets count in channel queue + public int GetPacketsCountInReliableQueue(byte channelNumber, bool ordered) + { + int idx = channelNumber * NetConstants.ChannelTypeCount + + (byte) (ordered ? DeliveryMethod.ReliableOrdered : DeliveryMethod.ReliableUnordered); + var channel = _channels[idx]; + return channel != null ? ((ReliableChannel)channel).PacketsInQueue : 0; + } + + /// + /// Create temporary packet (maximum size MTU - headerSize) to send later without additional copies + /// + /// Delivery method (reliable, unreliable, etc.) + /// Number of channel (from 0 to channelsCount - 1) + /// PooledPacket that you can use to write data starting from UserDataOffset + public PooledPacket CreatePacketFromPool(DeliveryMethod deliveryMethod, byte channelNumber) + { + //multithreaded variable + int mtu = _mtu; + var packet = NetManager.PoolGetPacket(mtu); + if (deliveryMethod == DeliveryMethod.Unreliable) + { + packet.Property = PacketProperty.Unreliable; + return new PooledPacket(packet, mtu, 0); + } + else + { + packet.Property = PacketProperty.Channeled; + return new PooledPacket(packet, mtu, (byte)(channelNumber * NetConstants.ChannelTypeCount + (byte)deliveryMethod)); + } + } + + /// + /// Sends pooled packet without data copy + /// + /// packet to send + /// size of user data you want to send + public void SendPooledPacket(PooledPacket packet, int userDataSize) + { + if (_connectionState != ConnectionState.Connected) + return; + packet._packet.Size = packet.UserDataOffset + userDataSize; + if (packet._packet.Property == PacketProperty.Channeled) + { + CreateChannel(packet._channelNumber).AddToQueue(packet._packet); + } + else + EnqueueUnreliable(packet._packet); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void EnqueueUnreliable(NetPacket packet) + { + lock (_unreliableChannelLock) + { + if (_unreliablePendingCount == _unreliableChannel.Length) + Array.Resize(ref _unreliableChannel, _unreliablePendingCount*2); + _unreliableChannel[_unreliablePendingCount++] = packet; + } + } + + private BaseChannel CreateChannel(byte idx) + { + BaseChannel newChannel = _channels[idx]; + if (newChannel != null) + return newChannel; + switch ((DeliveryMethod)(idx % NetConstants.ChannelTypeCount)) + { + case DeliveryMethod.ReliableUnordered: + newChannel = new ReliableChannel(this, false, idx); + break; + case DeliveryMethod.Sequenced: + newChannel = new SequencedChannel(this, false, idx); + break; + case DeliveryMethod.ReliableOrdered: + newChannel = new ReliableChannel(this, true, idx); + break; + case DeliveryMethod.ReliableSequenced: + newChannel = new SequencedChannel(this, true, idx); + break; + } + BaseChannel prevChannel = Interlocked.CompareExchange(ref _channels[idx], newChannel, null); + if (prevChannel != null) + return prevChannel; + + return newChannel; + } + + //"Connect to" constructor + internal NetPeer(NetManager netManager, IPEndPoint remoteEndPoint, int id, byte connectNum, NetDataWriter connectData) + : this(netManager, remoteEndPoint, id) + { + _connectTime = DateTime.UtcNow.Ticks; + _connectionState = ConnectionState.Outgoing; + ConnectionNum = connectNum; + + //Make initial packet + _connectRequestPacket = NetConnectRequestPacket.Make(connectData, remoteEndPoint.Serialize(), _connectTime, id); + _connectRequestPacket.ConnectionNumber = connectNum; + + //Send request + NetManager.SendRaw(_connectRequestPacket, this); + + NetDebug.Write(NetLogLevel.Trace, $"[CC] ConnectId: {_connectTime}, ConnectNum: {connectNum}"); + } + +#if LITENETLIB_SPANS || NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1 || NETCOREAPP3_1 || NET5_0 || NETSTANDARD2_1 + //"Connect to" constructor + internal NetPeer(NetManager netManager, IPEndPoint remoteEndPoint, int id, byte connectNum, ReadOnlySpan connectData) + : this(netManager, remoteEndPoint, id) + { + _connectTime = DateTime.UtcNow.Ticks; + _connectionState = ConnectionState.Outgoing; + ConnectionNum = connectNum; + + //Make initial packet + _connectRequestPacket = NetConnectRequestPacket.Make(connectData, remoteEndPoint.Serialize(), _connectTime, id); + _connectRequestPacket.ConnectionNumber = connectNum; + + //Send request + NetManager.SendRaw(_connectRequestPacket, this); + + NetDebug.Write(NetLogLevel.Trace, $"[CC] ConnectId: {_connectTime}, ConnectNum: {connectNum}"); + } +#endif + + //"Accept" incoming constructor + internal NetPeer(NetManager netManager, ConnectionRequest request, int id) + : this(netManager, request.RemoteEndPoint, id) + { + _connectTime = request.InternalPacket.ConnectionTime; + ConnectionNum = request.InternalPacket.ConnectionNumber; + RemoteId = request.InternalPacket.PeerId; + + //Make initial packet + _connectAcceptPacket = NetConnectAcceptPacket.Make(_connectTime, ConnectionNum, id); + + //Make Connected + _connectionState = ConnectionState.Connected; + + //Send + NetManager.SendRaw(_connectAcceptPacket, this); + + NetDebug.Write(NetLogLevel.Trace, $"[CC] ConnectId: {_connectTime}"); + } + + //Reject + internal void Reject(NetConnectRequestPacket requestData, byte[] data, int start, int length) + { + _connectTime = requestData.ConnectionTime; + _connectNum = requestData.ConnectionNumber; + Shutdown(data, start, length, false); + } + + internal bool ProcessConnectAccept(NetConnectAcceptPacket packet) + { + if (_connectionState != ConnectionState.Outgoing) + return false; + + //check connection id + if (packet.ConnectionTime != _connectTime) + { + NetDebug.Write(NetLogLevel.Trace, $"[NC] Invalid connectId: {packet.ConnectionTime} != our({_connectTime})"); + return false; + } + //check connect num + ConnectionNum = packet.ConnectionNumber; + RemoteId = packet.PeerId; + + NetDebug.Write(NetLogLevel.Trace, "[NC] Received connection accept"); + Interlocked.Exchange(ref _timeSinceLastPacket, 0); + _connectionState = ConnectionState.Connected; + return true; + } + + /// + /// Gets maximum size of packet that will be not fragmented. + /// + /// Type of packet that you want send + /// size in bytes + public int GetMaxSinglePacketSize(DeliveryMethod options) + { + return _mtu - NetPacket.GetHeaderSize(options == DeliveryMethod.Unreliable ? PacketProperty.Unreliable : PacketProperty.Channeled); + } + + /// + /// Send data to peer with delivery event called + /// + /// Data + /// Number of channel (from 0 to channelsCount - 1) + /// Delivery method (reliable, unreliable, etc.) + /// User data that will be received in DeliveryEvent + /// + /// If you trying to send unreliable packet type + /// + public void SendWithDeliveryEvent(byte[] data, byte channelNumber, DeliveryMethod deliveryMethod, object userData) + { + if (deliveryMethod != DeliveryMethod.ReliableOrdered && deliveryMethod != DeliveryMethod.ReliableUnordered) + throw new ArgumentException("Delivery event will work only for ReliableOrdered/Unordered packets"); + SendInternal(data, 0, data.Length, channelNumber, deliveryMethod, userData); + } + + /// + /// Send data to peer with delivery event called + /// + /// Data + /// Start of data + /// Length of data + /// Number of channel (from 0 to channelsCount - 1) + /// Delivery method (reliable, unreliable, etc.) + /// User data that will be received in DeliveryEvent + /// + /// If you trying to send unreliable packet type + /// + public void SendWithDeliveryEvent(byte[] data, int start, int length, byte channelNumber, DeliveryMethod deliveryMethod, object userData) + { + if (deliveryMethod != DeliveryMethod.ReliableOrdered && deliveryMethod != DeliveryMethod.ReliableUnordered) + throw new ArgumentException("Delivery event will work only for ReliableOrdered/Unordered packets"); + SendInternal(data, start, length, channelNumber, deliveryMethod, userData); + } + + /// + /// Send data to peer with delivery event called + /// + /// Data + /// Number of channel (from 0 to channelsCount - 1) + /// Delivery method (reliable, unreliable, etc.) + /// User data that will be received in DeliveryEvent + /// + /// If you trying to send unreliable packet type + /// + public void SendWithDeliveryEvent(NetDataWriter dataWriter, byte channelNumber, DeliveryMethod deliveryMethod, object userData) + { + if (deliveryMethod != DeliveryMethod.ReliableOrdered && deliveryMethod != DeliveryMethod.ReliableUnordered) + throw new ArgumentException("Delivery event will work only for ReliableOrdered/Unordered packets"); + SendInternal(dataWriter.Data, 0, dataWriter.Length, channelNumber, deliveryMethod, userData); + } + + /// + /// Send data to peer (channel - 0) + /// + /// Data + /// Send options (reliable, unreliable, etc.) + /// + /// If size exceeds maximum limit: + /// MTU - headerSize bytes for Unreliable + /// Fragment count exceeded ushort.MaxValue + /// + public void Send(byte[] data, DeliveryMethod deliveryMethod) + { + SendInternal(data, 0, data.Length, 0, deliveryMethod, null); + } + + /// + /// Send data to peer (channel - 0) + /// + /// DataWriter with data + /// Send options (reliable, unreliable, etc.) + /// + /// If size exceeds maximum limit: + /// MTU - headerSize bytes for Unreliable + /// Fragment count exceeded ushort.MaxValue + /// + public void Send(NetDataWriter dataWriter, DeliveryMethod deliveryMethod) + { + SendInternal(dataWriter.Data, 0, dataWriter.Length, 0, deliveryMethod, null); + } + + /// + /// Send data to peer (channel - 0) + /// + /// Data + /// Start of data + /// Length of data + /// Send options (reliable, unreliable, etc.) + /// + /// If size exceeds maximum limit: + /// MTU - headerSize bytes for Unreliable + /// Fragment count exceeded ushort.MaxValue + /// + public void Send(byte[] data, int start, int length, DeliveryMethod options) + { + SendInternal(data, start, length, 0, options, null); + } + + /// + /// Send data to peer + /// + /// Data + /// Number of channel (from 0 to channelsCount - 1) + /// Send options (reliable, unreliable, etc.) + /// + /// If size exceeds maximum limit: + /// MTU - headerSize bytes for Unreliable + /// Fragment count exceeded ushort.MaxValue + /// + public void Send(byte[] data, byte channelNumber, DeliveryMethod deliveryMethod) + { + SendInternal(data, 0, data.Length, channelNumber, deliveryMethod, null); + } + + /// + /// Send data to peer + /// + /// DataWriter with data + /// Number of channel (from 0 to channelsCount - 1) + /// Send options (reliable, unreliable, etc.) + /// + /// If size exceeds maximum limit: + /// MTU - headerSize bytes for Unreliable + /// Fragment count exceeded ushort.MaxValue + /// + public void Send(NetDataWriter dataWriter, byte channelNumber, DeliveryMethod deliveryMethod) + { + SendInternal(dataWriter.Data, 0, dataWriter.Length, channelNumber, deliveryMethod, null); + } + + /// + /// Send data to peer + /// + /// Data + /// Start of data + /// Length of data + /// Number of channel (from 0 to channelsCount - 1) + /// Delivery method (reliable, unreliable, etc.) + /// + /// If size exceeds maximum limit: + /// MTU - headerSize bytes for Unreliable + /// Fragment count exceeded ushort.MaxValue + /// + public void Send(byte[] data, int start, int length, byte channelNumber, DeliveryMethod deliveryMethod) + { + SendInternal(data, start, length, channelNumber, deliveryMethod, null); + } + + private void SendInternal( + byte[] data, + int start, + int length, + byte channelNumber, + DeliveryMethod deliveryMethod, + object userData) + { + if (_connectionState != ConnectionState.Connected || channelNumber >= _channels.Length) + return; + + //Select channel + PacketProperty property; + BaseChannel channel = null; + + if (deliveryMethod == DeliveryMethod.Unreliable) + { + property = PacketProperty.Unreliable; + } + else + { + property = PacketProperty.Channeled; + channel = CreateChannel((byte)(channelNumber * NetConstants.ChannelTypeCount + (byte)deliveryMethod)); + } + + //Prepare + NetDebug.Write("[RS]Packet: " + property); + + //Check fragmentation + int headerSize = NetPacket.GetHeaderSize(property); + //Save mtu for multithread + int mtu = _mtu; + if (length + headerSize > mtu) + { + //if cannot be fragmented + if (deliveryMethod != DeliveryMethod.ReliableOrdered && deliveryMethod != DeliveryMethod.ReliableUnordered) + throw new TooBigPacketException("Unreliable or ReliableSequenced packet size exceeded maximum of " + (mtu - headerSize) + " bytes, Check allowed size by GetMaxSinglePacketSize()"); + + int packetFullSize = mtu - headerSize; + int packetDataSize = packetFullSize - NetConstants.FragmentHeaderSize; + int totalPackets = length / packetDataSize + (length % packetDataSize == 0 ? 0 : 1); + + NetDebug.Write($@"FragmentSend: + MTU: {mtu} + headerSize: {headerSize} + packetFullSize: {packetFullSize} + packetDataSize: {packetDataSize} + totalPackets: {totalPackets}"); + + if (totalPackets > ushort.MaxValue) + throw new TooBigPacketException("Data was split in " + totalPackets + " fragments, which exceeds " + ushort.MaxValue); + + ushort currentFragmentId = (ushort)Interlocked.Increment(ref _fragmentId); + + for(ushort partIdx = 0; partIdx < totalPackets; partIdx++) + { + int sendLength = length > packetDataSize ? packetDataSize : length; + + NetPacket p = NetManager.PoolGetPacket(headerSize + sendLength + NetConstants.FragmentHeaderSize); + p.Property = property; + p.UserData = userData; + p.FragmentId = currentFragmentId; + p.FragmentPart = partIdx; + p.FragmentsTotal = (ushort)totalPackets; + p.MarkFragmented(); + + Buffer.BlockCopy(data, start + partIdx * packetDataSize, p.RawData, NetConstants.FragmentedHeaderTotalSize, sendLength); + channel.AddToQueue(p); + + length -= sendLength; + } + return; + } + + //Else just send + NetPacket packet = NetManager.PoolGetPacket(headerSize + length); + packet.Property = property; + Buffer.BlockCopy(data, start, packet.RawData, headerSize, length); + packet.UserData = userData; + + if (channel == null) //unreliable + { + EnqueueUnreliable(packet); + } + else + { + channel.AddToQueue(packet); + } + } + +#if LITENETLIB_SPANS || NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1 || NETCOREAPP3_1 || NET5_0 || NETSTANDARD2_1 + /// + /// Send data to peer with delivery event called + /// + /// Data + /// Number of channel (from 0 to channelsCount - 1) + /// Delivery method (reliable, unreliable, etc.) + /// User data that will be received in DeliveryEvent + /// + /// If you trying to send unreliable packet type + /// + public void SendWithDeliveryEvent(ReadOnlySpan data, byte channelNumber, DeliveryMethod deliveryMethod, object userData) + { + if (deliveryMethod != DeliveryMethod.ReliableOrdered && deliveryMethod != DeliveryMethod.ReliableUnordered) + throw new ArgumentException("Delivery event will work only for ReliableOrdered/Unordered packets"); + SendInternal(data, channelNumber, deliveryMethod, userData); + } + + /// + /// Send data to peer (channel - 0) + /// + /// Data + /// Send options (reliable, unreliable, etc.) + /// + /// If size exceeds maximum limit: + /// MTU - headerSize bytes for Unreliable + /// Fragment count exceeded ushort.MaxValue + /// + public void Send(ReadOnlySpan data, DeliveryMethod deliveryMethod) + { + SendInternal(data, 0, deliveryMethod, null); + } + + /// + /// Send data to peer + /// + /// Data + /// Number of channel (from 0 to channelsCount - 1) + /// Send options (reliable, unreliable, etc.) + /// + /// If size exceeds maximum limit: + /// MTU - headerSize bytes for Unreliable + /// Fragment count exceeded ushort.MaxValue + /// + public void Send(ReadOnlySpan data, byte channelNumber, DeliveryMethod deliveryMethod) + { + SendInternal(data, channelNumber, deliveryMethod, null); + } + + private void SendInternal( + ReadOnlySpan data, + byte channelNumber, + DeliveryMethod deliveryMethod, + object userData) + { + if (_connectionState != ConnectionState.Connected || channelNumber >= _channels.Length) + return; + + //Select channel + PacketProperty property; + BaseChannel channel = null; + + if (deliveryMethod == DeliveryMethod.Unreliable) + { + property = PacketProperty.Unreliable; + } + else + { + property = PacketProperty.Channeled; + channel = CreateChannel((byte)(channelNumber * NetConstants.ChannelTypeCount + (byte)deliveryMethod)); + } + + //Prepare + NetDebug.Write("[RS]Packet: " + property); + + //Check fragmentation + int headerSize = NetPacket.GetHeaderSize(property); + //Save mtu for multithread + int mtu = _mtu; + int length = data.Length; + if (length + headerSize > mtu) + { + //if cannot be fragmented + if (deliveryMethod != DeliveryMethod.ReliableOrdered && deliveryMethod != DeliveryMethod.ReliableUnordered) + throw new TooBigPacketException("Unreliable or ReliableSequenced packet size exceeded maximum of " + (mtu - headerSize) + " bytes, Check allowed size by GetMaxSinglePacketSize()"); + + int packetFullSize = mtu - headerSize; + int packetDataSize = packetFullSize - NetConstants.FragmentHeaderSize; + int totalPackets = length / packetDataSize + (length % packetDataSize == 0 ? 0 : 1); + + if (totalPackets > ushort.MaxValue) + throw new TooBigPacketException("Data was split in " + totalPackets + " fragments, which exceeds " + ushort.MaxValue); + + ushort currentFragmentId = (ushort)Interlocked.Increment(ref _fragmentId); + + for (ushort partIdx = 0; partIdx < totalPackets; partIdx++) + { + int sendLength = length > packetDataSize ? packetDataSize : length; + + NetPacket p = NetManager.PoolGetPacket(headerSize + sendLength + NetConstants.FragmentHeaderSize); + p.Property = property; + p.UserData = userData; + p.FragmentId = currentFragmentId; + p.FragmentPart = partIdx; + p.FragmentsTotal = (ushort)totalPackets; + p.MarkFragmented(); + + data.Slice(partIdx * packetDataSize, sendLength).CopyTo(new Span(p.RawData, NetConstants.FragmentedHeaderTotalSize, sendLength)); + channel.AddToQueue(p); + + length -= sendLength; + } + return; + } + + //Else just send + NetPacket packet = NetManager.PoolGetPacket(headerSize + length); + packet.Property = property; + data.CopyTo(new Span(packet.RawData, headerSize, length)); + packet.UserData = userData; + + if (channel == null) //unreliable + { + EnqueueUnreliable(packet); + } + else + { + channel.AddToQueue(packet); + } + } +#endif + + public void Disconnect(byte[] data) + { + NetManager.DisconnectPeer(this, data); + } + + public void Disconnect(NetDataWriter writer) + { + NetManager.DisconnectPeer(this, writer); + } + + public void Disconnect(byte[] data, int start, int count) + { + NetManager.DisconnectPeer(this, data, start, count); + } + + public void Disconnect() + { + NetManager.DisconnectPeer(this); + } + + internal DisconnectResult ProcessDisconnect(NetPacket packet) + { + if ((_connectionState == ConnectionState.Connected || _connectionState == ConnectionState.Outgoing) && + packet.Size >= 9 && + BitConverter.ToInt64(packet.RawData, 1) == _connectTime && + packet.ConnectionNumber == _connectNum) + { + return _connectionState == ConnectionState.Connected + ? DisconnectResult.Disconnect + : DisconnectResult.Reject; + } + return DisconnectResult.None; + } + + internal void AddToReliableChannelSendQueue(BaseChannel channel) + { + _channelSendQueue.Enqueue(channel); + } + + internal ShutdownResult Shutdown(byte[] data, int start, int length, bool force) + { + lock (_shutdownLock) + { + //trying to shutdown already disconnected + if (_connectionState == ConnectionState.Disconnected || + _connectionState == ConnectionState.ShutdownRequested) + { + return ShutdownResult.None; + } + + var result = _connectionState == ConnectionState.Connected + ? ShutdownResult.WasConnected + : ShutdownResult.Success; + + //don't send anything + if (force) + { + _connectionState = ConnectionState.Disconnected; + return result; + } + + //reset time for reconnect protection + Interlocked.Exchange(ref _timeSinceLastPacket, 0); + + //send shutdown packet + _shutdownPacket = new NetPacket(PacketProperty.Disconnect, length) {ConnectionNumber = _connectNum}; + FastBitConverter.GetBytes(_shutdownPacket.RawData, 1, _connectTime); + if (_shutdownPacket.Size >= _mtu) + { + //Drop additional data + NetDebug.WriteError("[Peer] Disconnect additional data size more than MTU - 8!"); + } + else if (data != null && length > 0) + { + Buffer.BlockCopy(data, start, _shutdownPacket.RawData, 9, length); + } + _connectionState = ConnectionState.ShutdownRequested; + NetDebug.Write("[Peer] Send disconnect"); + NetManager.SendRaw(_shutdownPacket, this); + return result; + } + } + + private void UpdateRoundTripTime(int roundTripTime) + { + _rtt += roundTripTime; + _rttCount++; + _avgRtt = _rtt/_rttCount; + _resendDelay = 25.0 + _avgRtt * 2.1; // 25 ms + double rtt + } + + internal void AddReliablePacket(DeliveryMethod method, NetPacket p) + { + if (p.IsFragmented) + { + NetDebug.Write($"Fragment. Id: {p.FragmentId}, Part: {p.FragmentPart}, Total: {p.FragmentsTotal}"); + //Get needed array from dictionary + ushort packetFragId = p.FragmentId; + byte packetChannelId = p.ChannelId; + if (!_holdedFragments.TryGetValue(packetFragId, out var incomingFragments)) + { + //Holded fragments limit reached + if (_holdedFragments.Count >= NetConstants.MaxFragmentsInWindow * NetManager.ChannelsCount * + NetConstants.FragmentedChannelsCount) + { + NetManager.PoolRecycle(p); + //NetDebug.WriteError($"Holded fragments limit reached ({_holdedFragments.Count}/{(NetConstants.DefaultWindowSize / 2) * ChannelsCount * NetConstants.FragmentedChannelsCount}). Dropping fragment id: {packetFragId}"); + return; + } + + incomingFragments = new IncomingFragments + { + Fragments = new NetPacket[p.FragmentsTotal], + ChannelId = p.ChannelId + }; + _holdedFragments.Add(packetFragId, incomingFragments); + } + + //Cache + var fragments = incomingFragments.Fragments; + + //Error check + if (p.FragmentPart >= fragments.Length || + fragments[p.FragmentPart] != null || + p.ChannelId != incomingFragments.ChannelId) + { + NetManager.PoolRecycle(p); + NetDebug.WriteError("Invalid fragment packet"); + return; + } + //Fill array + fragments[p.FragmentPart] = p; + + //Increase received fragments count + incomingFragments.ReceivedCount++; + + //Increase total size + incomingFragments.TotalSize += p.Size - NetConstants.FragmentedHeaderTotalSize; + + //Check for finish + if (incomingFragments.ReceivedCount != fragments.Length) + return; + + //just simple packet + NetPacket resultingPacket = NetManager.PoolGetPacket(incomingFragments.TotalSize); + + int pos = 0; + for (int i = 0; i < incomingFragments.ReceivedCount; i++) + { + var fragment = fragments[i]; + int writtenSize = fragment.Size - NetConstants.FragmentedHeaderTotalSize; + + if (pos+writtenSize > resultingPacket.RawData.Length) + { + _holdedFragments.Remove(packetFragId); + NetDebug.WriteError($"Fragment error pos: {pos + writtenSize} >= resultPacketSize: {resultingPacket.RawData.Length} , totalSize: {incomingFragments.TotalSize}"); + return; + } + if (fragment.Size > fragment.RawData.Length) + { + _holdedFragments.Remove(packetFragId); + NetDebug.WriteError($"Fragment error size: {fragment.Size} > fragment.RawData.Length: {fragment.RawData.Length}"); + return; + } + + //Create resulting big packet + Buffer.BlockCopy( + fragment.RawData, + NetConstants.FragmentedHeaderTotalSize, + resultingPacket.RawData, + pos, + writtenSize); + pos += writtenSize; + + //Free memory + NetManager.PoolRecycle(fragment); + fragments[i] = null; + } + + //Clear memory + _holdedFragments.Remove(packetFragId); + + //Send to process + NetManager.CreateReceiveEvent(resultingPacket, method, (byte)(packetChannelId / NetConstants.ChannelTypeCount), 0, this); + } + else //Just simple packet + { + NetManager.CreateReceiveEvent(p, method, (byte)(p.ChannelId / NetConstants.ChannelTypeCount), NetConstants.ChanneledHeaderSize, this); + } + } + + private void ProcessMtuPacket(NetPacket packet) + { + //header + int + if (packet.Size < NetConstants.PossibleMtu[0]) + return; + + //first stage check (mtu check and mtu ok) + int receivedMtu = BitConverter.ToInt32(packet.RawData, 1); + int endMtuCheck = BitConverter.ToInt32(packet.RawData, packet.Size - 4); + if (receivedMtu != packet.Size || receivedMtu != endMtuCheck || receivedMtu > NetConstants.MaxPacketSize) + { + NetDebug.WriteError($"[MTU] Broken packet. RMTU {receivedMtu}, EMTU {endMtuCheck}, PSIZE {packet.Size}"); + return; + } + + if (packet.Property == PacketProperty.MtuCheck) + { + _mtuCheckAttempts = 0; + NetDebug.Write("[MTU] check. send back: " + receivedMtu); + packet.Property = PacketProperty.MtuOk; + NetManager.SendRawAndRecycle(packet, this); + } + else if(receivedMtu > _mtu && !_finishMtu) //MtuOk + { + //invalid packet + if (receivedMtu != NetConstants.PossibleMtu[_mtuIdx + 1] - NetManager.ExtraPacketSizeForLayer) + return; + + lock (_mtuMutex) + { + SetMtu(_mtuIdx+1); + } + //if maxed - finish. + if (_mtuIdx == NetConstants.PossibleMtu.Length - 1) + _finishMtu = true; + NetManager.PoolRecycle(packet); + NetDebug.Write("[MTU] ok. Increase to: " + _mtu); + } + } + + private void UpdateMtuLogic(float deltaTime) + { + if (_finishMtu) + return; + + _mtuCheckTimer += deltaTime; + if (_mtuCheckTimer < MtuCheckDelay) + return; + + _mtuCheckTimer = 0; + _mtuCheckAttempts++; + if (_mtuCheckAttempts >= MaxMtuCheckAttempts) + { + _finishMtu = true; + return; + } + + lock (_mtuMutex) + { + if (_mtuIdx >= NetConstants.PossibleMtu.Length - 1) + return; + + //Send increased packet + int newMtu = NetConstants.PossibleMtu[_mtuIdx + 1] - NetManager.ExtraPacketSizeForLayer; + var p = NetManager.PoolGetPacket(newMtu); + p.Property = PacketProperty.MtuCheck; + FastBitConverter.GetBytes(p.RawData, 1, newMtu); //place into start + FastBitConverter.GetBytes(p.RawData, p.Size - 4, newMtu);//and end of packet + + //Must check result for MTU fix + if (NetManager.SendRawAndRecycle(p, this) <= 0) + _finishMtu = true; + } + } + + internal ConnectRequestResult ProcessConnectRequest(NetConnectRequestPacket connRequest) + { + //current or new request + switch (_connectionState) + { + //P2P case + case ConnectionState.Outgoing: + //fast check + if (connRequest.ConnectionTime < _connectTime) + { + return ConnectRequestResult.P2PLose; + } + //slow rare case check + if (connRequest.ConnectionTime == _connectTime) + { + var localBytes = connRequest.TargetAddress; + for (int i = _cachedSocketAddr.Size-1; i >= 0; i--) + { + byte rb = _cachedSocketAddr[i]; + if (rb == localBytes[i]) + continue; + if (rb < localBytes[i]) + return ConnectRequestResult.P2PLose; + } + } + break; + + case ConnectionState.Connected: + //Old connect request + if (connRequest.ConnectionTime == _connectTime) + { + //just reply accept + NetManager.SendRaw(_connectAcceptPacket, this); + } + //New connect request + else if (connRequest.ConnectionTime > _connectTime) + { + return ConnectRequestResult.Reconnection; + } + break; + + case ConnectionState.Disconnected: + case ConnectionState.ShutdownRequested: + if (connRequest.ConnectionTime >= _connectTime) + return ConnectRequestResult.NewConnection; + break; + } + return ConnectRequestResult.None; + } + + //Process incoming packet + internal void ProcessPacket(NetPacket packet) + { + //not initialized + if (_connectionState == ConnectionState.Outgoing || _connectionState == ConnectionState.Disconnected) + { + NetManager.PoolRecycle(packet); + return; + } + if (packet.Property == PacketProperty.ShutdownOk) + { + if (_connectionState == ConnectionState.ShutdownRequested) + _connectionState = ConnectionState.Disconnected; + NetManager.PoolRecycle(packet); + return; + } + if (packet.ConnectionNumber != _connectNum) + { + NetDebug.Write(NetLogLevel.Trace, "[RR]Old packet"); + NetManager.PoolRecycle(packet); + return; + } + Interlocked.Exchange(ref _timeSinceLastPacket, 0); + + NetDebug.Write($"[RR]PacketProperty: {packet.Property}"); + switch (packet.Property) + { + case PacketProperty.Merged: + int pos = NetConstants.HeaderSize; + while (pos < packet.Size) + { + ushort size = BitConverter.ToUInt16(packet.RawData, pos); + if (size == 0) + break; + + pos += 2; + if (packet.RawData.Length - pos < size) + break; + + NetPacket mergedPacket = NetManager.PoolGetPacket(size); + Buffer.BlockCopy(packet.RawData, pos, mergedPacket.RawData, 0, size); + mergedPacket.Size = size; + + if (!mergedPacket.Verify()) + break; + + pos += size; + ProcessPacket(mergedPacket); + } + NetManager.PoolRecycle(packet); + break; + //If we get ping, send pong + case PacketProperty.Ping: + if (NetUtils.RelativeSequenceNumber(packet.Sequence, _pongPacket.Sequence) > 0) + { + NetDebug.Write("[PP]Ping receive, send pong"); + FastBitConverter.GetBytes(_pongPacket.RawData, 3, DateTime.UtcNow.Ticks); + _pongPacket.Sequence = packet.Sequence; + NetManager.SendRaw(_pongPacket, this); + } + NetManager.PoolRecycle(packet); + break; + + //If we get pong, calculate ping time and rtt + case PacketProperty.Pong: + if (packet.Sequence == _pingPacket.Sequence) + { + _pingTimer.Stop(); + int elapsedMs = (int)_pingTimer.ElapsedMilliseconds; + _remoteDelta = BitConverter.ToInt64(packet.RawData, 3) + (elapsedMs * TimeSpan.TicksPerMillisecond ) / 2 - DateTime.UtcNow.Ticks; + UpdateRoundTripTime(elapsedMs); + NetManager.ConnectionLatencyUpdated(this, elapsedMs / 2); + NetDebug.Write($"[PP]Ping: {packet.Sequence} - {elapsedMs} - {_remoteDelta}"); + } + NetManager.PoolRecycle(packet); + break; + + case PacketProperty.Ack: + case PacketProperty.Channeled: + if (packet.ChannelId >= _channels.Length) + { + NetManager.PoolRecycle(packet); + break; + } + var channel = _channels[packet.ChannelId] ?? (packet.Property == PacketProperty.Ack ? null : CreateChannel(packet.ChannelId)); + if (channel != null) + { + if (!channel.ProcessPacket(packet)) + NetManager.PoolRecycle(packet); + } + break; + + //Simple packet without acks + case PacketProperty.Unreliable: + NetManager.CreateReceiveEvent(packet, DeliveryMethod.Unreliable, 0, NetConstants.HeaderSize, this); + return; + + case PacketProperty.MtuCheck: + case PacketProperty.MtuOk: + ProcessMtuPacket(packet); + break; + + default: + NetDebug.WriteError("Error! Unexpected packet type: " + packet.Property); + break; + } + } + + private void SendMerged() + { + if (_mergeCount == 0) + return; + int bytesSent; + if (_mergeCount > 1) + { + NetDebug.Write("[P]Send merged: " + _mergePos + ", count: " + _mergeCount); + bytesSent = NetManager.SendRaw(_mergeData.RawData, 0, NetConstants.HeaderSize + _mergePos, this); + } + else + { + //Send without length information and merging + bytesSent = NetManager.SendRaw(_mergeData.RawData, NetConstants.HeaderSize + 2, _mergePos - 2, this); + } + + if (NetManager.EnableStatistics) + { + Statistics.IncrementPacketsSent(); + Statistics.AddBytesSent(bytesSent); + } + + _mergePos = 0; + _mergeCount = 0; + } + + internal void SendUserData(NetPacket packet) + { + packet.ConnectionNumber = _connectNum; + int mergedPacketSize = NetConstants.HeaderSize + packet.Size + 2; + const int sizeTreshold = 20; + if (mergedPacketSize + sizeTreshold >= _mtu) + { + NetDebug.Write(NetLogLevel.Trace, "[P]SendingPacket: " + packet.Property); + int bytesSent = NetManager.SendRaw(packet, this); + + if (NetManager.EnableStatistics) + { + Statistics.IncrementPacketsSent(); + Statistics.AddBytesSent(bytesSent); + } + + return; + } + if (_mergePos + mergedPacketSize > _mtu) + SendMerged(); + + FastBitConverter.GetBytes(_mergeData.RawData, _mergePos + NetConstants.HeaderSize, (ushort)packet.Size); + Buffer.BlockCopy(packet.RawData, 0, _mergeData.RawData, _mergePos + NetConstants.HeaderSize + 2, packet.Size); + _mergePos += packet.Size + 2; + _mergeCount++; + //DebugWriteForce("Merged: " + _mergePos + "/" + (_mtu - 2) + ", count: " + _mergeCount); + } + + internal void Update(float deltaTime) + { + _timeSinceLastPacket = _timeSinceLastPacket + deltaTime; + switch (_connectionState) + { + case ConnectionState.Connected: + if (_timeSinceLastPacket > NetManager.DisconnectTimeout) + { + NetDebug.Write($"[UPDATE] Disconnect by timeout: {_timeSinceLastPacket} > {NetManager.DisconnectTimeout}"); + NetManager.DisconnectPeerForce(this, DisconnectReason.Timeout, 0, null); + return; + } + break; + + case ConnectionState.ShutdownRequested: + if (_timeSinceLastPacket > NetManager.DisconnectTimeout) + { + _connectionState = ConnectionState.Disconnected; + } + else + { + _shutdownTimer += deltaTime; + if (_shutdownTimer >= ShutdownDelay) + { + _shutdownTimer = 0; + NetManager.SendRaw(_shutdownPacket, this); + } + } + return; + + case ConnectionState.Outgoing: + _connectTimer += deltaTime; + if (_connectTimer > NetManager.ReconnectDelay) + { + _connectTimer = 0; + _connectAttempts++; + if (_connectAttempts > NetManager.MaxConnectAttempts) + { + NetManager.DisconnectPeerForce(this, DisconnectReason.ConnectionFailed, 0, null); + return; + } + + //else send connect again + NetManager.SendRaw(_connectRequestPacket, this); + } + return; + + case ConnectionState.Disconnected: + return; + } + + //Send ping + _pingSendTimer += deltaTime; + if (_pingSendTimer >= NetManager.PingInterval) + { + NetDebug.Write("[PP] Send ping..."); + //reset timer + _pingSendTimer = 0; + //send ping + _pingPacket.Sequence++; + //ping timeout + if (_pingTimer.IsRunning) + UpdateRoundTripTime((int)_pingTimer.ElapsedMilliseconds); + _pingTimer.Restart(); + NetManager.SendRaw(_pingPacket, this); + } + + //RTT - round trip time + _rttResetTimer += deltaTime; + if (_rttResetTimer >= NetManager.PingInterval * 3) + { + _rttResetTimer = 0; + _rtt = _avgRtt; + _rttCount = 1; + } + + UpdateMtuLogic(deltaTime); + + //Pending send + int count = _channelSendQueue.Count; + while (count-- > 0) + { + if (!_channelSendQueue.TryDequeue(out var channel)) + break; + if (channel.SendAndCheckQueue()) + { + // still has something to send, re-add it to the send queue + _channelSendQueue.Enqueue(channel); + } + } + + if (_unreliablePendingCount > 0) + { + int unreliableCount; + lock (_unreliableChannelLock) + { + (_unreliableChannel, _unreliableSecondQueue) = (_unreliableSecondQueue, _unreliableChannel); + unreliableCount = _unreliablePendingCount; + _unreliablePendingCount = 0; + } + for (int i = 0; i < unreliableCount; i++) + { + var packet = _unreliableSecondQueue[i]; + SendUserData(packet); + NetManager.PoolRecycle(packet); + } + } + + SendMerged(); + } + + //For reliable channel + internal void RecycleAndDeliver(NetPacket packet) + { + if (packet.UserData != null) + { + if (packet.IsFragmented) + { + _deliveredFragments.TryGetValue(packet.FragmentId, out ushort fragCount); + fragCount++; + if (fragCount == packet.FragmentsTotal) + { + NetManager.MessageDelivered(this, packet.UserData); + _deliveredFragments.Remove(packet.FragmentId); + } + else + { + _deliveredFragments[packet.FragmentId] = fragCount; + } + } + else + { + NetManager.MessageDelivered(this, packet.UserData); + } + packet.UserData = null; + } + NetManager.PoolRecycle(packet); + } + } +} diff --git a/Assets/Lib/LiteNetLib/NetPeer.cs.meta b/Assets/Lib/LiteNetLib/NetPeer.cs.meta new file mode 100644 index 0000000..9ed2eb1 --- /dev/null +++ b/Assets/Lib/LiteNetLib/NetPeer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0546c660d096f064eb3a98ff07f1de3b \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/NetStatistics.cs b/Assets/Lib/LiteNetLib/NetStatistics.cs new file mode 100644 index 0000000..032e275 --- /dev/null +++ b/Assets/Lib/LiteNetLib/NetStatistics.cs @@ -0,0 +1,81 @@ +using System.Threading; + +namespace LiteNetLib +{ + public sealed class NetStatistics + { + private long _packetsSent; + private long _packetsReceived; + private long _bytesSent; + private long _bytesReceived; + private long _packetLoss; + + public long PacketsSent => Interlocked.Read(ref _packetsSent); + public long PacketsReceived => Interlocked.Read(ref _packetsReceived); + public long BytesSent => Interlocked.Read(ref _bytesSent); + public long BytesReceived => Interlocked.Read(ref _bytesReceived); + public long PacketLoss => Interlocked.Read(ref _packetLoss); + + public long PacketLossPercent + { + get + { + long sent = PacketsSent, loss = PacketLoss; + + return sent == 0 ? 0 : loss * 100 / sent; + } + } + + public void Reset() + { + Interlocked.Exchange(ref _packetsSent, 0); + Interlocked.Exchange(ref _packetsReceived, 0); + Interlocked.Exchange(ref _bytesSent, 0); + Interlocked.Exchange(ref _bytesReceived, 0); + Interlocked.Exchange(ref _packetLoss, 0); + } + + public void IncrementPacketsSent() + { + Interlocked.Increment(ref _packetsSent); + } + + public void IncrementPacketsReceived() + { + Interlocked.Increment(ref _packetsReceived); + } + + public void AddBytesSent(long bytesSent) + { + Interlocked.Add(ref _bytesSent, bytesSent); + } + + public void AddBytesReceived(long bytesReceived) + { + Interlocked.Add(ref _bytesReceived, bytesReceived); + } + + public void IncrementPacketLoss() + { + Interlocked.Increment(ref _packetLoss); + } + + public void AddPacketLoss(long packetLoss) + { + Interlocked.Add(ref _packetLoss, packetLoss); + } + + public override string ToString() + { + return + string.Format( + "BytesReceived: {0}\nPacketsReceived: {1}\nBytesSent: {2}\nPacketsSent: {3}\nPacketLoss: {4}\nPacketLossPercent: {5}\n", + BytesReceived, + PacketsReceived, + BytesSent, + PacketsSent, + PacketLoss, + PacketLossPercent); + } + } +} diff --git a/Assets/Lib/LiteNetLib/NetStatistics.cs.meta b/Assets/Lib/LiteNetLib/NetStatistics.cs.meta new file mode 100644 index 0000000..c6d75cc --- /dev/null +++ b/Assets/Lib/LiteNetLib/NetStatistics.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 6568ee2158237bb42a70076ee05e36a3 \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/NetUtils.cs b/Assets/Lib/LiteNetLib/NetUtils.cs new file mode 100644 index 0000000..f7b2bd8 --- /dev/null +++ b/Assets/Lib/LiteNetLib/NetUtils.cs @@ -0,0 +1,238 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Net; +using System.Net.Sockets; +using System.Net.NetworkInformation; + +namespace LiteNetLib +{ + /// + /// Address type that you want to receive from NetUtils.GetLocalIp method + /// + [Flags] + public enum LocalAddrType + { + IPv4 = 1, + IPv6 = 2, + All = IPv4 | IPv6 + } + + /// + /// Some specific network utilities + /// + public static class NetUtils + { + private static readonly NetworkSorter NetworkSorter = new NetworkSorter(); + + public static IPEndPoint MakeEndPoint(string hostStr, int port) + { + return new IPEndPoint(ResolveAddress(hostStr), port); + } + + public static IPAddress ResolveAddress(string hostStr) + { + if(hostStr == "localhost") + return IPAddress.Loopback; + + if (!IPAddress.TryParse(hostStr, out var ipAddress)) + { + if (NetManager.IPv6Support) + ipAddress = ResolveAddress(hostStr, AddressFamily.InterNetworkV6); + if (ipAddress == null) + ipAddress = ResolveAddress(hostStr, AddressFamily.InterNetwork); + } + if (ipAddress == null) + throw new ArgumentException("Invalid address: " + hostStr); + + return ipAddress; + } + + public static IPAddress ResolveAddress(string hostStr, AddressFamily addressFamily) + { + IPAddress[] addresses = Dns.GetHostEntry(hostStr).AddressList; + foreach (IPAddress ip in addresses) + { + if (ip.AddressFamily == addressFamily) + { + return ip; + } + } + return null; + } + + /// + /// Get all local ip addresses + /// + /// type of address (IPv4, IPv6 or both) + /// List with all local ip addresses + public static List GetLocalIpList(LocalAddrType addrType) + { + List targetList = new List(); + GetLocalIpList(targetList, addrType); + return targetList; + } + + /// + /// Get all local ip addresses (non alloc version) + /// + /// result list + /// type of address (IPv4, IPv6 or both) + public static void GetLocalIpList(IList targetList, LocalAddrType addrType) + { + bool ipv4 = (addrType & LocalAddrType.IPv4) == LocalAddrType.IPv4; + bool ipv6 = (addrType & LocalAddrType.IPv6) == LocalAddrType.IPv6; + try + { + // Sort networks interfaces so it prefer Wifi over Cellular networks + // Most cellulars networks seems to be incompatible with NAT Punch + var networks = NetworkInterface.GetAllNetworkInterfaces(); + Array.Sort(networks, NetworkSorter); + + foreach (NetworkInterface ni in networks) + { + //Skip loopback and disabled network interfaces + if (ni.NetworkInterfaceType == NetworkInterfaceType.Loopback || + ni.OperationalStatus != OperationalStatus.Up) + continue; + + var ipProps = ni.GetIPProperties(); + + //Skip address without gateway + if (ipProps.GatewayAddresses.Count == 0) + continue; + + foreach (UnicastIPAddressInformation ip in ipProps.UnicastAddresses) + { + var address = ip.Address; + if ((ipv4 && address.AddressFamily == AddressFamily.InterNetwork) || + (ipv6 && address.AddressFamily == AddressFamily.InterNetworkV6)) + targetList.Add(address.ToString()); + } + } + + //Fallback mode (unity android) + if (targetList.Count == 0) + { + IPAddress[] addresses = Dns.GetHostEntry(Dns.GetHostName()).AddressList; + foreach (IPAddress ip in addresses) + { + if((ipv4 && ip.AddressFamily == AddressFamily.InterNetwork) || + (ipv6 && ip.AddressFamily == AddressFamily.InterNetworkV6)) + targetList.Add(ip.ToString()); + } + } + } + catch + { + //ignored + } + + if (targetList.Count == 0) + { + if(ipv4) + targetList.Add("127.0.0.1"); + if(ipv6) + targetList.Add("::1"); + } + } + + private static readonly List IpList = new List(); + /// + /// Get first detected local ip address + /// + /// type of address (IPv4, IPv6 or both) + /// IP address if available. Else - string.Empty + public static string GetLocalIp(LocalAddrType addrType) + { + lock (IpList) + { + IpList.Clear(); + GetLocalIpList(IpList, addrType); + return IpList.Count == 0 ? string.Empty : IpList[0]; + } + } + + // =========================================== + // Internal and debug log related stuff + // =========================================== + internal static void PrintInterfaceInfos() + { + NetDebug.WriteForce(NetLogLevel.Info, $"IPv6Support: { NetManager.IPv6Support}"); + try + { + foreach (NetworkInterface ni in NetworkInterface.GetAllNetworkInterfaces()) + { + foreach (UnicastIPAddressInformation ip in ni.GetIPProperties().UnicastAddresses) + { + if (ip.Address.AddressFamily == AddressFamily.InterNetwork || + ip.Address.AddressFamily == AddressFamily.InterNetworkV6) + { + NetDebug.WriteForce( + NetLogLevel.Info, + $"Interface: {ni.Name}, Type: {ni.NetworkInterfaceType}, Ip: {ip.Address}, OpStatus: {ni.OperationalStatus}"); + } + } + } + } + catch (Exception e) + { + NetDebug.WriteForce(NetLogLevel.Info, $"Error while getting interface infos: {e}"); + } + } + + internal static int RelativeSequenceNumber(int number, int expected) + { + return (number - expected + NetConstants.MaxSequence + NetConstants.HalfMaxSequence) % NetConstants.MaxSequence - NetConstants.HalfMaxSequence; + } + + internal static T[] AllocatePinnedUninitializedArray(int count) where T : unmanaged + { +#if NET5_0_OR_GREATER || NET5_0 + return GC.AllocateUninitializedArray(count, true); +#else + return new T[count]; +#endif + } + } + + // Pick the most obvious choice for the local IP + // Ethernet > Wifi > Others > Cellular + internal class NetworkSorter : IComparer + { + [SuppressMessage("ReSharper", "PossibleNullReferenceException")] + public int Compare(NetworkInterface a, NetworkInterface b) + { + var isCellularA = a.NetworkInterfaceType == NetworkInterfaceType.Wman || + a.NetworkInterfaceType == NetworkInterfaceType.Wwanpp || + a.NetworkInterfaceType == NetworkInterfaceType.Wwanpp2; + + var isCellularB = b.NetworkInterfaceType == NetworkInterfaceType.Wman || + b.NetworkInterfaceType == NetworkInterfaceType.Wwanpp || + b.NetworkInterfaceType == NetworkInterfaceType.Wwanpp2; + + var isWifiA = a.NetworkInterfaceType == NetworkInterfaceType.Wireless80211; + var isWifiB = b.NetworkInterfaceType == NetworkInterfaceType.Wireless80211; + + var isEthernetA = a.NetworkInterfaceType == NetworkInterfaceType.Ethernet || + a.NetworkInterfaceType == NetworkInterfaceType.Ethernet3Megabit || + a.NetworkInterfaceType == NetworkInterfaceType.GigabitEthernet || + a.NetworkInterfaceType == NetworkInterfaceType.FastEthernetFx || + a.NetworkInterfaceType == NetworkInterfaceType.FastEthernetT; + + var isEthernetB = b.NetworkInterfaceType == NetworkInterfaceType.Ethernet || + b.NetworkInterfaceType == NetworkInterfaceType.Ethernet3Megabit || + b.NetworkInterfaceType == NetworkInterfaceType.GigabitEthernet || + b.NetworkInterfaceType == NetworkInterfaceType.FastEthernetFx || + b.NetworkInterfaceType == NetworkInterfaceType.FastEthernetT; + + var isOtherA = !isCellularA && !isWifiA && !isEthernetA; + var isOtherB = !isCellularB && !isWifiB && !isEthernetB; + + var priorityA = isEthernetA ? 3 : isWifiA ? 2 : isOtherA ? 1 : 0; + var priorityB = isEthernetB ? 3 : isWifiB ? 2 : isOtherB ? 1 : 0; + + return priorityA > priorityB ? -1 : priorityA < priorityB ? 1 : 0; + } + } +} diff --git a/Assets/Lib/LiteNetLib/NetUtils.cs.meta b/Assets/Lib/LiteNetLib/NetUtils.cs.meta new file mode 100644 index 0000000..77ee040 --- /dev/null +++ b/Assets/Lib/LiteNetLib/NetUtils.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: bf4b0936f4b03924787113187c2b4530 \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/PausedSocketFix.cs b/Assets/Lib/LiteNetLib/PausedSocketFix.cs new file mode 100644 index 0000000..df54175 --- /dev/null +++ b/Assets/Lib/LiteNetLib/PausedSocketFix.cs @@ -0,0 +1,67 @@ +#if UNITY_2018_3_OR_NEWER +using System.Net; +using UnityEngine; + +namespace LiteNetLib +{ + public class PausedSocketFix + { + private readonly NetManager _netManager; + private readonly IPAddress _ipv4; + private readonly IPAddress _ipv6; + private readonly int _port; + private readonly bool _manualMode; + private bool _initialized; + + public PausedSocketFix(NetManager netManager, IPAddress ipv4, IPAddress ipv6, int port, bool manualMode) + { + _netManager = netManager; + _ipv4 = ipv4; + _ipv6 = ipv6; + _port = port; + _manualMode = manualMode; + Application.focusChanged += Application_focusChanged; + Application.quitting += Deinitialize; + _initialized = true; + } + + public void Deinitialize() + { + if (_initialized) + { + Application.focusChanged -= Application_focusChanged; + Application.quitting -= Deinitialize; + } + + if (_netManager.IsRunning) + { + _netManager.Stop(); + } + _initialized = false; + } + + private void Application_focusChanged(bool focused) + { + //If coming back into focus see if a reconnect is needed. + if (focused) + { + //try reconnect + if (!_initialized) + return; + //Was intentionally disconnected at some point. + if (!_netManager.IsRunning) + return; + //Socket is in working state. + if (_netManager.NotConnected == false) + return; + + //Socket isn't running but should be. Try to start again. + if (!_netManager.Start(_ipv4, _ipv6, _port, _manualMode)) + { + NetDebug.WriteError($"[S] Cannot restore connection. Ipv4 {_ipv4}, Ipv6 {_ipv6}, Port {_port}, ManualMode {_manualMode}"); + } + } + } + } +} +#endif diff --git a/Assets/Lib/LiteNetLib/PausedSocketFix.cs.meta b/Assets/Lib/LiteNetLib/PausedSocketFix.cs.meta new file mode 100644 index 0000000..6b205be --- /dev/null +++ b/Assets/Lib/LiteNetLib/PausedSocketFix.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 181dda6f5b5bc7e4bae1f79d595bf5cb \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/PooledPacket.cs b/Assets/Lib/LiteNetLib/PooledPacket.cs new file mode 100644 index 0000000..26ef7bd --- /dev/null +++ b/Assets/Lib/LiteNetLib/PooledPacket.cs @@ -0,0 +1,32 @@ +namespace LiteNetLib +{ + public readonly ref struct PooledPacket + { + internal readonly NetPacket _packet; + internal readonly byte _channelNumber; + + /// + /// Maximum data size that you can put into such packet + /// + public readonly int MaxUserDataSize; + + /// + /// Offset for user data when writing to Data array + /// + public readonly int UserDataOffset; + + /// + /// Raw packet data. Do not modify header! Use UserDataOffset as start point for your data + /// + public byte[] Data => _packet.RawData; + + internal PooledPacket(NetPacket packet, int maxDataSize, byte channelNumber) + { + _packet = packet; + UserDataOffset = _packet.GetHeaderSize(); + _packet.Size = UserDataOffset; + MaxUserDataSize = maxDataSize - UserDataOffset; + _channelNumber = channelNumber; + } + } +} diff --git a/Assets/Lib/LiteNetLib/PooledPacket.cs.meta b/Assets/Lib/LiteNetLib/PooledPacket.cs.meta new file mode 100644 index 0000000..7b35308 --- /dev/null +++ b/Assets/Lib/LiteNetLib/PooledPacket.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4a6675a4aa287e6429d2f30c68079078 \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/ReliableChannel.cs b/Assets/Lib/LiteNetLib/ReliableChannel.cs new file mode 100644 index 0000000..4a10d17 --- /dev/null +++ b/Assets/Lib/LiteNetLib/ReliableChannel.cs @@ -0,0 +1,337 @@ +using System; + +namespace LiteNetLib +{ + internal sealed class ReliableChannel : BaseChannel + { + private struct PendingPacket + { + private NetPacket _packet; + private long _timeStamp; + private bool _isSent; + + public override string ToString() + { + return _packet == null ? "Empty" : _packet.Sequence.ToString(); + } + + public void Init(NetPacket packet) + { + _packet = packet; + _isSent = false; + } + + //Returns true if there is a pending packet inside + public bool TrySend(long currentTime, NetPeer peer) + { + if (_packet == null) + return false; + + if (_isSent) //check send time + { + double resendDelay = peer.ResendDelay * TimeSpan.TicksPerMillisecond; + double packetHoldTime = currentTime - _timeStamp; + if (packetHoldTime < resendDelay) + return true; + NetDebug.Write($"[RC]Resend: {packetHoldTime} > {resendDelay}"); + } + _timeStamp = currentTime; + _isSent = true; + peer.SendUserData(_packet); + return true; + } + + public bool Clear(NetPeer peer) + { + if (_packet != null) + { + peer.RecycleAndDeliver(_packet); + _packet = null; + return true; + } + return false; + } + } + + private readonly NetPacket _outgoingAcks; //for send acks + private readonly PendingPacket[] _pendingPackets; //for unacked packets and duplicates + private readonly NetPacket[] _receivedPackets; //for order + private readonly bool[] _earlyReceived; //for unordered + + private int _localSeqence; + private int _remoteSequence; + private int _localWindowStart; + private int _remoteWindowStart; + + private bool _mustSendAcks; + + private readonly DeliveryMethod _deliveryMethod; + private readonly bool _ordered; + private readonly int _windowSize; + private const int BitsInByte = 8; + private readonly byte _id; + + public ReliableChannel(NetPeer peer, bool ordered, byte id) : base(peer) + { + _id = id; + _windowSize = NetConstants.DefaultWindowSize; + _ordered = ordered; + _pendingPackets = new PendingPacket[_windowSize]; + for (int i = 0; i < _pendingPackets.Length; i++) + _pendingPackets[i] = new PendingPacket(); + + if (_ordered) + { + _deliveryMethod = DeliveryMethod.ReliableOrdered; + _receivedPackets = new NetPacket[_windowSize]; + } + else + { + _deliveryMethod = DeliveryMethod.ReliableUnordered; + _earlyReceived = new bool[_windowSize]; + } + + _localWindowStart = 0; + _localSeqence = 0; + _remoteSequence = 0; + _remoteWindowStart = 0; + _outgoingAcks = new NetPacket(PacketProperty.Ack, (_windowSize - 1) / BitsInByte + 2) {ChannelId = id}; + } + + //ProcessAck in packet + private void ProcessAck(NetPacket packet) + { + if (packet.Size != _outgoingAcks.Size) + { + NetDebug.Write("[PA]Invalid acks packet size"); + return; + } + + ushort ackWindowStart = packet.Sequence; + int windowRel = NetUtils.RelativeSequenceNumber(_localWindowStart, ackWindowStart); + if (ackWindowStart >= NetConstants.MaxSequence || windowRel < 0) + { + NetDebug.Write("[PA]Bad window start"); + return; + } + + //check relevance + if (windowRel >= _windowSize) + { + NetDebug.Write("[PA]Old acks"); + return; + } + + byte[] acksData = packet.RawData; + lock (_pendingPackets) + { + for (int pendingSeq = _localWindowStart; + pendingSeq != _localSeqence; + pendingSeq = (pendingSeq + 1) % NetConstants.MaxSequence) + { + int rel = NetUtils.RelativeSequenceNumber(pendingSeq, ackWindowStart); + if (rel >= _windowSize) + { + NetDebug.Write("[PA]REL: " + rel); + break; + } + + int pendingIdx = pendingSeq % _windowSize; + int currentByte = NetConstants.ChanneledHeaderSize + pendingIdx / BitsInByte; + int currentBit = pendingIdx % BitsInByte; + if ((acksData[currentByte] & (1 << currentBit)) == 0) + { + if (Peer.NetManager.EnableStatistics) + { + Peer.Statistics.IncrementPacketLoss(); + Peer.NetManager.Statistics.IncrementPacketLoss(); + } + + //Skip false ack + NetDebug.Write($"[PA]False ack: {pendingSeq}"); + continue; + } + + if (pendingSeq == _localWindowStart) + { + //Move window + _localWindowStart = (_localWindowStart + 1) % NetConstants.MaxSequence; + } + + //clear packet + if (_pendingPackets[pendingIdx].Clear(Peer)) + NetDebug.Write($"[PA]Removing reliableInOrder ack: {pendingSeq} - true"); + } + } + } + + protected override bool SendNextPackets() + { + if (_mustSendAcks) + { + _mustSendAcks = false; + NetDebug.Write("[RR]SendAcks"); + lock(_outgoingAcks) + Peer.SendUserData(_outgoingAcks); + } + + long currentTime = DateTime.UtcNow.Ticks; + bool hasPendingPackets = false; + + lock (_pendingPackets) + { + //get packets from queue + lock (OutgoingQueue) + { + while (OutgoingQueue.Count > 0) + { + int relate = NetUtils.RelativeSequenceNumber(_localSeqence, _localWindowStart); + if (relate >= _windowSize) + break; + + var netPacket = OutgoingQueue.Dequeue(); + netPacket.Sequence = (ushort) _localSeqence; + netPacket.ChannelId = _id; + _pendingPackets[_localSeqence % _windowSize].Init(netPacket); + _localSeqence = (_localSeqence + 1) % NetConstants.MaxSequence; + } + } + + //send + for (int pendingSeq = _localWindowStart; pendingSeq != _localSeqence; pendingSeq = (pendingSeq + 1) % NetConstants.MaxSequence) + { + // Please note: TrySend is invoked on a mutable struct, it's important to not extract it into a variable here + if (_pendingPackets[pendingSeq % _windowSize].TrySend(currentTime, Peer)) + hasPendingPackets = true; + } + } + + return hasPendingPackets || _mustSendAcks || OutgoingQueue.Count > 0; + } + + //Process incoming packet + public override bool ProcessPacket(NetPacket packet) + { + if (packet.Property == PacketProperty.Ack) + { + ProcessAck(packet); + return false; + } + int seq = packet.Sequence; + if (seq >= NetConstants.MaxSequence) + { + NetDebug.Write("[RR]Bad sequence"); + return false; + } + + int relate = NetUtils.RelativeSequenceNumber(seq, _remoteWindowStart); + int relateSeq = NetUtils.RelativeSequenceNumber(seq, _remoteSequence); + + if (relateSeq > _windowSize) + { + NetDebug.Write("[RR]Bad sequence"); + return false; + } + + //Drop bad packets + if (relate < 0) + { + //Too old packet doesn't ack + NetDebug.Write("[RR]ReliableInOrder too old"); + return false; + } + if (relate >= _windowSize * 2) + { + //Some very new packet + NetDebug.Write("[RR]ReliableInOrder too new"); + return false; + } + + //If very new - move window + int ackIdx; + int ackByte; + int ackBit; + lock (_outgoingAcks) + { + if (relate >= _windowSize) + { + //New window position + int newWindowStart = (_remoteWindowStart + relate - _windowSize + 1) % NetConstants.MaxSequence; + _outgoingAcks.Sequence = (ushort) newWindowStart; + + //Clean old data + while (_remoteWindowStart != newWindowStart) + { + ackIdx = _remoteWindowStart % _windowSize; + ackByte = NetConstants.ChanneledHeaderSize + ackIdx / BitsInByte; + ackBit = ackIdx % BitsInByte; + _outgoingAcks.RawData[ackByte] &= (byte) ~(1 << ackBit); + _remoteWindowStart = (_remoteWindowStart + 1) % NetConstants.MaxSequence; + } + } + + //Final stage - process valid packet + //trigger acks send + _mustSendAcks = true; + + ackIdx = seq % _windowSize; + ackByte = NetConstants.ChanneledHeaderSize + ackIdx / BitsInByte; + ackBit = ackIdx % BitsInByte; + if ((_outgoingAcks.RawData[ackByte] & (1 << ackBit)) != 0) + { + NetDebug.Write("[RR]ReliableInOrder duplicate"); + //because _mustSendAcks == true + AddToPeerChannelSendQueue(); + return false; + } + + //save ack + _outgoingAcks.RawData[ackByte] |= (byte) (1 << ackBit); + } + + AddToPeerChannelSendQueue(); + + //detailed check + if (seq == _remoteSequence) + { + NetDebug.Write("[RR]ReliableInOrder packet succes"); + Peer.AddReliablePacket(_deliveryMethod, packet); + _remoteSequence = (_remoteSequence + 1) % NetConstants.MaxSequence; + + if (_ordered) + { + NetPacket p; + while ((p = _receivedPackets[_remoteSequence % _windowSize]) != null) + { + //process holden packet + _receivedPackets[_remoteSequence % _windowSize] = null; + Peer.AddReliablePacket(_deliveryMethod, p); + _remoteSequence = (_remoteSequence + 1) % NetConstants.MaxSequence; + } + } + else + { + while (_earlyReceived[_remoteSequence % _windowSize]) + { + //process early packet + _earlyReceived[_remoteSequence % _windowSize] = false; + _remoteSequence = (_remoteSequence + 1) % NetConstants.MaxSequence; + } + } + return true; + } + + //holden packet + if (_ordered) + { + _receivedPackets[ackIdx] = packet; + } + else + { + _earlyReceived[ackIdx] = true; + Peer.AddReliablePacket(_deliveryMethod, packet); + } + return true; + } + } +} diff --git a/Assets/Lib/LiteNetLib/ReliableChannel.cs.meta b/Assets/Lib/LiteNetLib/ReliableChannel.cs.meta new file mode 100644 index 0000000..931b1fd --- /dev/null +++ b/Assets/Lib/LiteNetLib/ReliableChannel.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b70dc6886338b594e9a5a4f65f4ee67d \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/SequencedChannel.cs b/Assets/Lib/LiteNetLib/SequencedChannel.cs new file mode 100644 index 0000000..bc47f86 --- /dev/null +++ b/Assets/Lib/LiteNetLib/SequencedChannel.cs @@ -0,0 +1,114 @@ +using System; + +namespace LiteNetLib +{ + internal sealed class SequencedChannel : BaseChannel + { + private int _localSequence; + private ushort _remoteSequence; + private readonly bool _reliable; + private NetPacket _lastPacket; + private readonly NetPacket _ackPacket; + private bool _mustSendAck; + private readonly byte _id; + private long _lastPacketSendTime; + + public SequencedChannel(NetPeer peer, bool reliable, byte id) : base(peer) + { + _id = id; + _reliable = reliable; + if (_reliable) + _ackPacket = new NetPacket(PacketProperty.Ack, 0) {ChannelId = id}; + } + + protected override bool SendNextPackets() + { + if (_reliable && OutgoingQueue.Count == 0) + { + long currentTime = DateTime.UtcNow.Ticks; + long packetHoldTime = currentTime - _lastPacketSendTime; + if (packetHoldTime >= Peer.ResendDelay * TimeSpan.TicksPerMillisecond) + { + var packet = _lastPacket; + if (packet != null) + { + _lastPacketSendTime = currentTime; + Peer.SendUserData(packet); + } + } + } + else + { + lock (OutgoingQueue) + { + while (OutgoingQueue.Count > 0) + { + NetPacket packet = OutgoingQueue.Dequeue(); + _localSequence = (_localSequence + 1) % NetConstants.MaxSequence; + packet.Sequence = (ushort)_localSequence; + packet.ChannelId = _id; + Peer.SendUserData(packet); + + if (_reliable && OutgoingQueue.Count == 0) + { + _lastPacketSendTime = DateTime.UtcNow.Ticks; + _lastPacket = packet; + } + else + { + Peer.NetManager.PoolRecycle(packet); + } + } + } + } + + if (_reliable && _mustSendAck) + { + _mustSendAck = false; + _ackPacket.Sequence = _remoteSequence; + Peer.SendUserData(_ackPacket); + } + + return _lastPacket != null; + } + + public override bool ProcessPacket(NetPacket packet) + { + if (packet.IsFragmented) + return false; + if (packet.Property == PacketProperty.Ack) + { + if (_reliable && _lastPacket != null && packet.Sequence == _lastPacket.Sequence) + _lastPacket = null; + return false; + } + int relative = NetUtils.RelativeSequenceNumber(packet.Sequence, _remoteSequence); + bool packetProcessed = false; + if (packet.Sequence < NetConstants.MaxSequence && relative > 0) + { + if (Peer.NetManager.EnableStatistics) + { + Peer.Statistics.AddPacketLoss(relative - 1); + Peer.NetManager.Statistics.AddPacketLoss(relative - 1); + } + + _remoteSequence = packet.Sequence; + Peer.NetManager.CreateReceiveEvent( + packet, + _reliable ? DeliveryMethod.ReliableSequenced : DeliveryMethod.Sequenced, + (byte)(packet.ChannelId / NetConstants.ChannelTypeCount), + NetConstants.ChanneledHeaderSize, + Peer); + packetProcessed = true; + } + + if (_reliable) + { + _mustSendAck = true; + AddToPeerChannelSendQueue(); + } + + return packetProcessed; + } + } +} diff --git a/Assets/Lib/LiteNetLib/SequencedChannel.cs.meta b/Assets/Lib/LiteNetLib/SequencedChannel.cs.meta new file mode 100644 index 0000000..99b9908 --- /dev/null +++ b/Assets/Lib/LiteNetLib/SequencedChannel.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5bc45363f40f62e4fa432ed967a290fa \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/Trimming.cs b/Assets/Lib/LiteNetLib/Trimming.cs new file mode 100644 index 0000000..6c575b6 --- /dev/null +++ b/Assets/Lib/LiteNetLib/Trimming.cs @@ -0,0 +1,12 @@ +#if NET5_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; +using static System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes; + +namespace LiteNetLib +{ + internal static class Trimming + { + internal const DynamicallyAccessedMemberTypes SerializerMemberTypes = PublicProperties | NonPublicProperties; + } +} +#endif diff --git a/Assets/Lib/LiteNetLib/Trimming.cs.meta b/Assets/Lib/LiteNetLib/Trimming.cs.meta new file mode 100644 index 0000000..1f8a660 --- /dev/null +++ b/Assets/Lib/LiteNetLib/Trimming.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5fa166daaa5c863459e7326faa6c3b4f \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/Utils.meta b/Assets/Lib/LiteNetLib/Utils.meta new file mode 100644 index 0000000..c400bff --- /dev/null +++ b/Assets/Lib/LiteNetLib/Utils.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6f59a194fe3e09d4599de6aefc91b207 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Lib/LiteNetLib/Utils/CRC32C.cs b/Assets/Lib/LiteNetLib/Utils/CRC32C.cs new file mode 100644 index 0000000..7e85680 --- /dev/null +++ b/Assets/Lib/LiteNetLib/Utils/CRC32C.cs @@ -0,0 +1,150 @@ +#if NETCOREAPP3_0_OR_GREATER || NETCOREAPP3_1 || NET5_0 +using System; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics.X86; +#endif +#if NET5_0_OR_GREATER || NET5_0 +using System.Runtime.Intrinsics.Arm; +#endif + +namespace LiteNetLib.Utils +{ + //Implementation from Crc32.NET + public static class CRC32C + { + public const int ChecksumSize = 4; + private const uint Poly = 0x82F63B78u; + private static readonly uint[] Table; + + static CRC32C() + { +#if NETCOREAPP3_0_OR_GREATER || NETCOREAPP3_1 || NET5_0 + if (Sse42.IsSupported) + return; +#endif +#if NET5_0_OR_GREATER || NET5_0 + if (Crc32.IsSupported) + return; +#endif + Table = NetUtils.AllocatePinnedUninitializedArray(16 * 256); + for (uint i = 0; i < 256; i++) + { + uint res = i; + for (int t = 0; t < 16; t++) + { + for (int k = 0; k < 8; k++) + res = (res & 1) == 1 ? Poly ^ (res >> 1) : (res >> 1); + Table[t * 256 + i] = res; + } + } + } + + /// + /// Compute CRC32C for data + /// + /// input data + /// offset + /// length + /// CRC32C checksum + public static uint Compute(byte[] input, int offset, int length) + { + uint crcLocal = uint.MaxValue; +#if NETCOREAPP3_0_OR_GREATER || NETCOREAPP3_1 || NET5_0 + if (Sse42.IsSupported) + { + var data = new ReadOnlySpan(input, offset, length); + int processed = 0; + if (Sse42.X64.IsSupported && data.Length > sizeof(ulong)) + { + processed = data.Length / sizeof(ulong) * sizeof(ulong); + var ulongs = MemoryMarshal.Cast(data.Slice(0, processed)); + ulong crclong = crcLocal; + for (int i = 0; i < ulongs.Length; i++) + { + crclong = Sse42.X64.Crc32(crclong, ulongs[i]); + } + + crcLocal = (uint)crclong; + } + else if (data.Length > sizeof(uint)) + { + processed = data.Length / sizeof(uint) * sizeof(uint); + var uints = MemoryMarshal.Cast(data.Slice(0, processed)); + for (int i = 0; i < uints.Length; i++) + { + crcLocal = Sse42.Crc32(crcLocal, uints[i]); + } + } + + for (int i = processed; i < data.Length; i++) + { + crcLocal = Sse42.Crc32(crcLocal, data[i]); + } + + return crcLocal ^ uint.MaxValue; + } +#endif +#if NET5_0_OR_GREATER || NET5_0 + if (Crc32.IsSupported) + { + var data = new ReadOnlySpan(input, offset, length); + int processed = 0; + if (Crc32.Arm64.IsSupported && data.Length > sizeof(ulong)) + { + processed = data.Length / sizeof(ulong) * sizeof(ulong); + var ulongs = MemoryMarshal.Cast(data.Slice(0, processed)); + for (int i = 0; i < ulongs.Length; i++) + { + crcLocal = Crc32.Arm64.ComputeCrc32C(crcLocal, ulongs[i]); + } + } + else if (data.Length > sizeof(uint)) + { + processed = data.Length / sizeof(uint) * sizeof(uint); + var uints = MemoryMarshal.Cast(data.Slice(0, processed)); + for (int i = 0; i < uints.Length; i++) + { + crcLocal = Crc32.ComputeCrc32C(crcLocal, uints[i]); + } + } + + for (int i = processed; i < data.Length; i++) + { + crcLocal = Crc32.ComputeCrc32C(crcLocal, data[i]); + } + + return crcLocal ^ uint.MaxValue; + } +#endif + while (length >= 16) + { + var a = Table[(3 * 256) + input[offset + 12]] + ^ Table[(2 * 256) + input[offset + 13]] + ^ Table[(1 * 256) + input[offset + 14]] + ^ Table[(0 * 256) + input[offset + 15]]; + + var b = Table[(7 * 256) + input[offset + 8]] + ^ Table[(6 * 256) + input[offset + 9]] + ^ Table[(5 * 256) + input[offset + 10]] + ^ Table[(4 * 256) + input[offset + 11]]; + + var c = Table[(11 * 256) + input[offset + 4]] + ^ Table[(10 * 256) + input[offset + 5]] + ^ Table[(9 * 256) + input[offset + 6]] + ^ Table[(8 * 256) + input[offset + 7]]; + + var d = Table[(15 * 256) + ((byte)crcLocal ^ input[offset])] + ^ Table[(14 * 256) + ((byte)(crcLocal >> 8) ^ input[offset + 1])] + ^ Table[(13 * 256) + ((byte)(crcLocal >> 16) ^ input[offset + 2])] + ^ Table[(12 * 256) + ((crcLocal >> 24) ^ input[offset + 3])]; + + crcLocal = d ^ c ^ b ^ a; + offset += 16; + length -= 16; + } + while (--length >= 0) + crcLocal = Table[(byte)(crcLocal ^ input[offset++])] ^ crcLocal >> 8; + return crcLocal ^ uint.MaxValue; + } + } +} diff --git a/Assets/Lib/LiteNetLib/Utils/CRC32C.cs.meta b/Assets/Lib/LiteNetLib/Utils/CRC32C.cs.meta new file mode 100644 index 0000000..1bd7a64 --- /dev/null +++ b/Assets/Lib/LiteNetLib/Utils/CRC32C.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a47871c42ac8f6c4ba0b965990785e2e \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/Utils/FastBitConverter.cs b/Assets/Lib/LiteNetLib/Utils/FastBitConverter.cs new file mode 100644 index 0000000..82bedf4 --- /dev/null +++ b/Assets/Lib/LiteNetLib/Utils/FastBitConverter.cs @@ -0,0 +1,175 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace LiteNetLib.Utils +{ + public static class FastBitConverter + { +#if (LITENETLIB_UNSAFE || NETCOREAPP3_1 || NET5_0 || NETCOREAPP3_0_OR_GREATER) && !BIGENDIAN +#if LITENETLIB_UNSAFE + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe void GetBytes(byte[] bytes, int startIndex, T value) where T : unmanaged + { + int size = sizeof(T); + if (bytes.Length < startIndex + size) + ThrowIndexOutOfRangeException(); +#if NETCOREAPP3_1 || NET5_0 || NETCOREAPP3_0_OR_GREATER + Unsafe.As(ref bytes[startIndex]) = value; +#else + fixed (byte* ptr = &bytes[startIndex]) + { +#if UNITY_ANDROID + // On some android systems, assigning *(T*)ptr throws a NRE if + // the ptr isn't aligned (i.e. if Position is 1,2,3,5, etc.). + // Here we have to use memcpy. + // + // => we can't get a pointer of a struct in C# without + // marshalling allocations + // => instead, we stack allocate an array of type T and use that + // => stackalloc avoids GC and is very fast. it only works for + // value types, but all blittable types are anyway. + T* valueBuffer = stackalloc T[1] { value }; + UnsafeUtility.MemCpy(ptr, valueBuffer, size); +#else + *(T*)ptr = value; +#endif + } +#endif + } +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GetBytes(byte[] bytes, int startIndex, T value) where T : unmanaged + { + if (bytes.Length < startIndex + Unsafe.SizeOf()) + ThrowIndexOutOfRangeException(); + Unsafe.As(ref bytes[startIndex]) = value; + } +#endif + + private static void ThrowIndexOutOfRangeException() => throw new IndexOutOfRangeException(); +#else + [StructLayout(LayoutKind.Explicit)] + private struct ConverterHelperDouble + { + [FieldOffset(0)] + public ulong Along; + + [FieldOffset(0)] + public double Adouble; + } + + [StructLayout(LayoutKind.Explicit)] + private struct ConverterHelperFloat + { + [FieldOffset(0)] + public int Aint; + + [FieldOffset(0)] + public float Afloat; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WriteLittleEndian(byte[] buffer, int offset, ulong data) + { +#if BIGENDIAN + buffer[offset + 7] = (byte)(data); + buffer[offset + 6] = (byte)(data >> 8); + buffer[offset + 5] = (byte)(data >> 16); + buffer[offset + 4] = (byte)(data >> 24); + buffer[offset + 3] = (byte)(data >> 32); + buffer[offset + 2] = (byte)(data >> 40); + buffer[offset + 1] = (byte)(data >> 48); + buffer[offset ] = (byte)(data >> 56); +#else + buffer[offset] = (byte)(data); + buffer[offset + 1] = (byte)(data >> 8); + buffer[offset + 2] = (byte)(data >> 16); + buffer[offset + 3] = (byte)(data >> 24); + buffer[offset + 4] = (byte)(data >> 32); + buffer[offset + 5] = (byte)(data >> 40); + buffer[offset + 6] = (byte)(data >> 48); + buffer[offset + 7] = (byte)(data >> 56); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WriteLittleEndian(byte[] buffer, int offset, int data) + { +#if BIGENDIAN + buffer[offset + 3] = (byte)(data); + buffer[offset + 2] = (byte)(data >> 8); + buffer[offset + 1] = (byte)(data >> 16); + buffer[offset ] = (byte)(data >> 24); +#else + buffer[offset] = (byte)(data); + buffer[offset + 1] = (byte)(data >> 8); + buffer[offset + 2] = (byte)(data >> 16); + buffer[offset + 3] = (byte)(data >> 24); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteLittleEndian(byte[] buffer, int offset, short data) + { +#if BIGENDIAN + buffer[offset + 1] = (byte)(data); + buffer[offset ] = (byte)(data >> 8); +#else + buffer[offset] = (byte)(data); + buffer[offset + 1] = (byte)(data >> 8); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GetBytes(byte[] bytes, int startIndex, double value) + { + ConverterHelperDouble ch = new ConverterHelperDouble { Adouble = value }; + WriteLittleEndian(bytes, startIndex, ch.Along); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GetBytes(byte[] bytes, int startIndex, float value) + { + ConverterHelperFloat ch = new ConverterHelperFloat { Afloat = value }; + WriteLittleEndian(bytes, startIndex, ch.Aint); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GetBytes(byte[] bytes, int startIndex, short value) + { + WriteLittleEndian(bytes, startIndex, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GetBytes(byte[] bytes, int startIndex, ushort value) + { + WriteLittleEndian(bytes, startIndex, (short)value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GetBytes(byte[] bytes, int startIndex, int value) + { + WriteLittleEndian(bytes, startIndex, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GetBytes(byte[] bytes, int startIndex, uint value) + { + WriteLittleEndian(bytes, startIndex, (int)value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GetBytes(byte[] bytes, int startIndex, long value) + { + WriteLittleEndian(bytes, startIndex, (ulong)value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GetBytes(byte[] bytes, int startIndex, ulong value) + { + WriteLittleEndian(bytes, startIndex, value); + } +#endif + } +} diff --git a/Assets/Lib/LiteNetLib/Utils/FastBitConverter.cs.meta b/Assets/Lib/LiteNetLib/Utils/FastBitConverter.cs.meta new file mode 100644 index 0000000..90b13b0 --- /dev/null +++ b/Assets/Lib/LiteNetLib/Utils/FastBitConverter.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 02a83b44bea06274291554b18636c0a3 \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/Utils/INetSerializable.cs b/Assets/Lib/LiteNetLib/Utils/INetSerializable.cs new file mode 100644 index 0000000..92f14be --- /dev/null +++ b/Assets/Lib/LiteNetLib/Utils/INetSerializable.cs @@ -0,0 +1,8 @@ +namespace LiteNetLib.Utils +{ + public interface INetSerializable + { + void Serialize(NetDataWriter writer); + void Deserialize(NetDataReader reader); + } +} diff --git a/Assets/Lib/LiteNetLib/Utils/INetSerializable.cs.meta b/Assets/Lib/LiteNetLib/Utils/INetSerializable.cs.meta new file mode 100644 index 0000000..05070f7 --- /dev/null +++ b/Assets/Lib/LiteNetLib/Utils/INetSerializable.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e8c7959351c6b444087f546718c9e0d3 \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/Utils/NetDataReader.cs b/Assets/Lib/LiteNetLib/Utils/NetDataReader.cs new file mode 100644 index 0000000..d163b2f --- /dev/null +++ b/Assets/Lib/LiteNetLib/Utils/NetDataReader.cs @@ -0,0 +1,797 @@ +using System; +using System.Net; +using System.Runtime.CompilerServices; + +namespace LiteNetLib.Utils +{ + public class NetDataReader + { + protected byte[] _data; + protected int _position; + protected int _dataSize; + private int _offset; + + public byte[] RawData + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data; + } + public int RawDataSize + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _dataSize; + } + public int UserDataOffset + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _offset; + } + public int UserDataSize + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _dataSize - _offset; + } + public bool IsNull + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data == null; + } + public int Position + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _position; + } + public bool EndOfData + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _position == _dataSize; + } + public int AvailableBytes + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _dataSize - _position; + } + + public void SkipBytes(int count) + { + _position += count; + } + + public void SetPosition(int position) + { + _position = position; + } + + public void SetSource(NetDataWriter dataWriter) + { + _data = dataWriter.Data; + _position = 0; + _offset = 0; + _dataSize = dataWriter.Length; + } + + public void SetSource(byte[] source) + { + _data = source; + _position = 0; + _offset = 0; + _dataSize = source.Length; + } + + public void SetSource(byte[] source, int offset, int maxSize) + { + _data = source; + _position = offset; + _offset = offset; + _dataSize = maxSize; + } + + public NetDataReader() + { + + } + + public NetDataReader(NetDataWriter writer) + { + SetSource(writer); + } + + public NetDataReader(byte[] source) + { + SetSource(source); + } + + public NetDataReader(byte[] source, int offset, int maxSize) + { + SetSource(source, offset, maxSize); + } + + #region GetMethods + + public void Get(out T result) where T : struct, INetSerializable + { + result = default(T); + result.Deserialize(this); + } + + public void Get(out T result, Func constructor) where T : class, INetSerializable + { + result = constructor(); + result.Deserialize(this); + } + + public void Get(out IPEndPoint result) + { + result = GetNetEndPoint(); + } + + public void Get(out byte result) + { + result = GetByte(); + } + + public void Get(out sbyte result) + { + result = (sbyte)GetByte(); + } + + public void Get(out bool result) + { + result = GetBool(); + } + + public void Get(out char result) + { + result = GetChar(); + } + + public void Get(out ushort result) + { + result = GetUShort(); + } + + public void Get(out short result) + { + result = GetShort(); + } + + public void Get(out ulong result) + { + result = GetULong(); + } + + public void Get(out long result) + { + result = GetLong(); + } + + public void Get(out uint result) + { + result = GetUInt(); + } + + public void Get(out int result) + { + result = GetInt(); + } + + public void Get(out double result) + { + result = GetDouble(); + } + + public void Get(out float result) + { + result = GetFloat(); + } + + public void Get(out string result) + { + result = GetString(); + } + + public void Get(out string result, int maxLength) + { + result = GetString(maxLength); + } + + public void Get(out Guid result) + { + result = GetGuid(); + } + + public IPEndPoint GetNetEndPoint() + { + string host = GetString(1000); + int port = GetInt(); + return NetUtils.MakeEndPoint(host, port); + } + + public byte GetByte() + { + byte res = _data[_position]; + _position++; + return res; + } + + public sbyte GetSByte() + { + return (sbyte)GetByte(); + } + + public T[] GetArray(ushort size) + { + ushort length = BitConverter.ToUInt16(_data, _position); + _position += 2; + T[] result = new T[length]; + length *= size; + Buffer.BlockCopy(_data, _position, result, 0, length); + _position += length; + return result; + } + + public T[] GetArray() where T : INetSerializable, new() + { + ushort length = BitConverter.ToUInt16(_data, _position); + _position += 2; + T[] result = new T[length]; + for (int i = 0; i < length; i++) + { + var item = new T(); + item.Deserialize(this); + result[i] = item; + } + return result; + } + + public T[] GetArray(Func constructor) where T : class, INetSerializable + { + ushort length = BitConverter.ToUInt16(_data, _position); + _position += 2; + T[] result = new T[length]; + for (int i = 0; i < length; i++) + Get(out result[i], constructor); + return result; + } + + public bool[] GetBoolArray() + { + return GetArray(1); + } + + public ushort[] GetUShortArray() + { + return GetArray(2); + } + + public short[] GetShortArray() + { + return GetArray(2); + } + + public int[] GetIntArray() + { + return GetArray(4); + } + + public uint[] GetUIntArray() + { + return GetArray(4); + } + + public float[] GetFloatArray() + { + return GetArray(4); + } + + public double[] GetDoubleArray() + { + return GetArray(8); + } + + public long[] GetLongArray() + { + return GetArray(8); + } + + public ulong[] GetULongArray() + { + return GetArray(8); + } + + public string[] GetStringArray() + { + ushort length = GetUShort(); + string[] arr = new string[length]; + for (int i = 0; i < length; i++) + { + arr[i] = GetString(); + } + return arr; + } + + /// + /// Note that "maxStringLength" only limits the number of characters in a string, not its size in bytes. + /// Strings that exceed this parameter are returned as empty + /// + public string[] GetStringArray(int maxStringLength) + { + ushort length = GetUShort(); + string[] arr = new string[length]; + for (int i = 0; i < length; i++) + { + arr[i] = GetString(maxStringLength); + } + return arr; + } + + public bool GetBool() + { + return GetByte() == 1; + } + + public char GetChar() + { + return (char)GetUShort(); + } + + public ushort GetUShort() + { + ushort result = BitConverter.ToUInt16(_data, _position); + _position += 2; + return result; + } + + public short GetShort() + { + short result = BitConverter.ToInt16(_data, _position); + _position += 2; + return result; + } + + public long GetLong() + { + long result = BitConverter.ToInt64(_data, _position); + _position += 8; + return result; + } + + public ulong GetULong() + { + ulong result = BitConverter.ToUInt64(_data, _position); + _position += 8; + return result; + } + + public int GetInt() + { + int result = BitConverter.ToInt32(_data, _position); + _position += 4; + return result; + } + + public uint GetUInt() + { + uint result = BitConverter.ToUInt32(_data, _position); + _position += 4; + return result; + } + + public float GetFloat() + { + float result = BitConverter.ToSingle(_data, _position); + _position += 4; + return result; + } + + public double GetDouble() + { + double result = BitConverter.ToDouble(_data, _position); + _position += 8; + return result; + } + + /// + /// Note that "maxLength" only limits the number of characters in a string, not its size in bytes. + /// + /// "string.Empty" if value > "maxLength" + public string GetString(int maxLength) + { + ushort size = GetUShort(); + if (size == 0) + return string.Empty; + + int actualSize = size - 1; + string result = maxLength > 0 && NetDataWriter.uTF8Encoding.Value.GetCharCount(_data, _position, actualSize) > maxLength ? + string.Empty : + NetDataWriter.uTF8Encoding.Value.GetString(_data, _position, actualSize); + _position += actualSize; + return result; + } + + public string GetString() + { + ushort size = GetUShort(); + if (size == 0) + return string.Empty; + + int actualSize = size - 1; + string result = NetDataWriter.uTF8Encoding.Value.GetString(_data, _position, actualSize); + _position += actualSize; + return result; + } + + public string GetLargeString() + { + int size = GetInt(); + if (size <= 0) + return string.Empty; + string result = NetDataWriter.uTF8Encoding.Value.GetString(_data, _position, size); + _position += size; + return result; + } + + public Guid GetGuid() + { +#if LITENETLIB_SPANS || NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1 || NETCOREAPP3_1 || NET5_0 || NETSTANDARD2_1 + var result = new Guid(_data.AsSpan(_position, 16)); + _position += 16; + return result; +#else + return new Guid(GetBytesWithLength()); +#endif + } + + public ArraySegment GetBytesSegment(int count) + { + ArraySegment segment = new ArraySegment(_data, _position, count); + _position += count; + return segment; + } + + public ArraySegment GetRemainingBytesSegment() + { + ArraySegment segment = new ArraySegment(_data, _position, AvailableBytes); + _position = _data.Length; + return segment; + } + + public T Get() where T : struct, INetSerializable + { + var obj = default(T); + obj.Deserialize(this); + return obj; + } + + public T Get(Func constructor) where T : class, INetSerializable + { + var obj = constructor(); + obj.Deserialize(this); + return obj; + } + +#if LITENETLIB_SPANS || NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1 || NETCOREAPP3_1 || NET5_0 || NETSTANDARD2_1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan GetRemainingBytesSpan() + { + return new ReadOnlySpan(_data, _position, _dataSize - _position); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyMemory GetRemainingBytesMemory() + { + return new ReadOnlyMemory(_data, _position, _dataSize - _position); + } +#endif + + public byte[] GetRemainingBytes() + { + byte[] outgoingData = new byte[AvailableBytes]; + Buffer.BlockCopy(_data, _position, outgoingData, 0, AvailableBytes); + _position = _data.Length; + return outgoingData; + } + + public void GetBytes(byte[] destination, int start, int count) + { + Buffer.BlockCopy(_data, _position, destination, start, count); + _position += count; + } + + public void GetBytes(byte[] destination, int count) + { + Buffer.BlockCopy(_data, _position, destination, 0, count); + _position += count; + } + + public sbyte[] GetSBytesWithLength() + { + return GetArray(1); + } + + public byte[] GetBytesWithLength() + { + return GetArray(1); + } + #endregion + + #region PeekMethods + + public byte PeekByte() + { + return _data[_position]; + } + + public sbyte PeekSByte() + { + return (sbyte)_data[_position]; + } + + public bool PeekBool() + { + return _data[_position] == 1; + } + + public char PeekChar() + { + return (char)PeekUShort(); + } + + public ushort PeekUShort() + { + return BitConverter.ToUInt16(_data, _position); + } + + public short PeekShort() + { + return BitConverter.ToInt16(_data, _position); + } + + public long PeekLong() + { + return BitConverter.ToInt64(_data, _position); + } + + public ulong PeekULong() + { + return BitConverter.ToUInt64(_data, _position); + } + + public int PeekInt() + { + return BitConverter.ToInt32(_data, _position); + } + + public uint PeekUInt() + { + return BitConverter.ToUInt32(_data, _position); + } + + public float PeekFloat() + { + return BitConverter.ToSingle(_data, _position); + } + + public double PeekDouble() + { + return BitConverter.ToDouble(_data, _position); + } + + /// + /// Note that "maxLength" only limits the number of characters in a string, not its size in bytes. + /// + public string PeekString(int maxLength) + { + ushort size = PeekUShort(); + if (size == 0) + return string.Empty; + + int actualSize = size - 1; + return (maxLength > 0 && NetDataWriter.uTF8Encoding.Value.GetCharCount(_data, _position + 2, actualSize) > maxLength) ? + string.Empty : + NetDataWriter.uTF8Encoding.Value.GetString(_data, _position + 2, actualSize); + } + + public string PeekString() + { + ushort size = PeekUShort(); + if (size == 0) + return string.Empty; + + int actualSize = size - 1; + return NetDataWriter.uTF8Encoding.Value.GetString(_data, _position + 2, actualSize); + } + #endregion + + #region TryGetMethods + public bool TryGetByte(out byte result) + { + if (AvailableBytes >= 1) + { + result = GetByte(); + return true; + } + result = 0; + return false; + } + + public bool TryGetSByte(out sbyte result) + { + if (AvailableBytes >= 1) + { + result = GetSByte(); + return true; + } + result = 0; + return false; + } + + public bool TryGetBool(out bool result) + { + if (AvailableBytes >= 1) + { + result = GetBool(); + return true; + } + result = false; + return false; + } + + public bool TryGetChar(out char result) + { + if (!TryGetUShort(out ushort uShortValue)) + { + result = '\0'; + return false; + } + result = (char)uShortValue; + return true; + } + + public bool TryGetShort(out short result) + { + if (AvailableBytes >= 2) + { + result = GetShort(); + return true; + } + result = 0; + return false; + } + + public bool TryGetUShort(out ushort result) + { + if (AvailableBytes >= 2) + { + result = GetUShort(); + return true; + } + result = 0; + return false; + } + + public bool TryGetInt(out int result) + { + if (AvailableBytes >= 4) + { + result = GetInt(); + return true; + } + result = 0; + return false; + } + + public bool TryGetUInt(out uint result) + { + if (AvailableBytes >= 4) + { + result = GetUInt(); + return true; + } + result = 0; + return false; + } + + public bool TryGetLong(out long result) + { + if (AvailableBytes >= 8) + { + result = GetLong(); + return true; + } + result = 0; + return false; + } + + public bool TryGetULong(out ulong result) + { + if (AvailableBytes >= 8) + { + result = GetULong(); + return true; + } + result = 0; + return false; + } + + public bool TryGetFloat(out float result) + { + if (AvailableBytes >= 4) + { + result = GetFloat(); + return true; + } + result = 0; + return false; + } + + public bool TryGetDouble(out double result) + { + if (AvailableBytes >= 8) + { + result = GetDouble(); + return true; + } + result = 0; + return false; + } + + public bool TryGetString(out string result) + { + if (AvailableBytes >= 2) + { + ushort strSize = PeekUShort(); + if (AvailableBytes >= strSize + 1) + { + result = GetString(); + return true; + } + } + result = null; + return false; + } + + public bool TryGetStringArray(out string[] result) + { + if (!TryGetUShort(out ushort strArrayLength)) { + result = null; + return false; + } + + result = new string[strArrayLength]; + for (int i = 0; i < strArrayLength; i++) + { + if (!TryGetString(out result[i])) + { + result = null; + return false; + } + } + + return true; + } + + public bool TryGetBytesWithLength(out byte[] result) + { + if (AvailableBytes >= 2) + { + ushort length = PeekUShort(); + if (length >= 0 && AvailableBytes >= 2 + length) + { + result = GetBytesWithLength(); + return true; + } + } + result = null; + return false; + } + #endregion + + public void Clear() + { + _position = 0; + _dataSize = 0; + _data = null; + } + } +} diff --git a/Assets/Lib/LiteNetLib/Utils/NetDataReader.cs.meta b/Assets/Lib/LiteNetLib/Utils/NetDataReader.cs.meta new file mode 100644 index 0000000..3bd37d9 --- /dev/null +++ b/Assets/Lib/LiteNetLib/Utils/NetDataReader.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: efb23a5085c2451469f0d269ab6519d7 \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/Utils/NetDataWriter.cs b/Assets/Lib/LiteNetLib/Utils/NetDataWriter.cs new file mode 100644 index 0000000..21d7ceb --- /dev/null +++ b/Assets/Lib/LiteNetLib/Utils/NetDataWriter.cs @@ -0,0 +1,449 @@ +using System; +using System.Net; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; + +namespace LiteNetLib.Utils +{ + public class NetDataWriter + { + protected byte[] _data; + protected int _position; + private const int InitialSize = 64; + private readonly bool _autoResize; + + public int Capacity + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data.Length; + } + public byte[] Data + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data; + } + public int Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _position; + } + +#if LITENETLIB_SPANS || NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1 || NETCOREAPP3_1 || NET5_0 || NETSTANDARD2_1 + public ReadOnlySpan AsReadOnlySpan() + { + return new ReadOnlySpan(_data, 0, _position); + } +#endif + + public static readonly ThreadLocal uTF8Encoding = new ThreadLocal(() => new UTF8Encoding(false, true)); + + public NetDataWriter() : this(true, InitialSize) + { + } + + public NetDataWriter(bool autoResize) : this(autoResize, InitialSize) + { + } + + public NetDataWriter(bool autoResize, int initialSize) + { + _data = new byte[initialSize]; + _autoResize = autoResize; + } + + /// + /// Creates NetDataWriter from existing ByteArray + /// + /// Source byte array + /// Copy array to new location or use existing + public static NetDataWriter FromBytes(byte[] bytes, bool copy) + { + if (copy) + { + var netDataWriter = new NetDataWriter(true, bytes.Length); + netDataWriter.Put(bytes); + return netDataWriter; + } + return new NetDataWriter(true, 0) {_data = bytes, _position = bytes.Length}; + } + + /// + /// Creates NetDataWriter from existing ByteArray (always copied data) + /// + /// Source byte array + /// Offset of array + /// Length of array + public static NetDataWriter FromBytes(byte[] bytes, int offset, int length) + { + var netDataWriter = new NetDataWriter(true, bytes.Length); + netDataWriter.Put(bytes, offset, length); + return netDataWriter; + } + +#if LITENETLIB_SPANS || NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1 || NETCOREAPP3_1 || NET5_0 || NETSTANDARD2_1 + /// + /// Creates NetDataWriter from the given . + /// + public static NetDataWriter FromBytes(Span bytes) + { + var netDataWriter = new NetDataWriter(true, bytes.Length); + netDataWriter.Put(bytes); + return netDataWriter; + } +#endif + + public static NetDataWriter FromString(string value) + { + var netDataWriter = new NetDataWriter(); + netDataWriter.Put(value); + return netDataWriter; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ResizeIfNeed(int newSize) + { + if (_data.Length < newSize) + { + Array.Resize(ref _data, Math.Max(newSize, _data.Length * 2)); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void EnsureFit(int additionalSize) + { + if (_data.Length < _position + additionalSize) + { + Array.Resize(ref _data, Math.Max(_position + additionalSize, _data.Length * 2)); + } + } + + public void Reset(int size) + { + ResizeIfNeed(size); + _position = 0; + } + + public void Reset() + { + _position = 0; + } + + public byte[] CopyData() + { + byte[] resultData = new byte[_position]; + Buffer.BlockCopy(_data, 0, resultData, 0, _position); + return resultData; + } + + /// + /// Sets position of NetDataWriter to rewrite previous values + /// + /// new byte position + /// previous position of data writer + public int SetPosition(int position) + { + int prevPosition = _position; + _position = position; + return prevPosition; + } + + public void Put(float value) + { + if (_autoResize) + ResizeIfNeed(_position + 4); + FastBitConverter.GetBytes(_data, _position, value); + _position += 4; + } + + public void Put(double value) + { + if (_autoResize) + ResizeIfNeed(_position + 8); + FastBitConverter.GetBytes(_data, _position, value); + _position += 8; + } + + public void Put(long value) + { + if (_autoResize) + ResizeIfNeed(_position + 8); + FastBitConverter.GetBytes(_data, _position, value); + _position += 8; + } + + public void Put(ulong value) + { + if (_autoResize) + ResizeIfNeed(_position + 8); + FastBitConverter.GetBytes(_data, _position, value); + _position += 8; + } + + public void Put(int value) + { + if (_autoResize) + ResizeIfNeed(_position + 4); + FastBitConverter.GetBytes(_data, _position, value); + _position += 4; + } + + public void Put(uint value) + { + if (_autoResize) + ResizeIfNeed(_position + 4); + FastBitConverter.GetBytes(_data, _position, value); + _position += 4; + } + + public void Put(char value) + { + Put((ushort)value); + } + + public void Put(ushort value) + { + if (_autoResize) + ResizeIfNeed(_position + 2); + FastBitConverter.GetBytes(_data, _position, value); + _position += 2; + } + + public void Put(short value) + { + if (_autoResize) + ResizeIfNeed(_position + 2); + FastBitConverter.GetBytes(_data, _position, value); + _position += 2; + } + + public void Put(sbyte value) + { + if (_autoResize) + ResizeIfNeed(_position + 1); + _data[_position] = (byte)value; + _position++; + } + + public void Put(byte value) + { + if (_autoResize) + ResizeIfNeed(_position + 1); + _data[_position] = value; + _position++; + } + + public void Put(Guid value) + { +#if LITENETLIB_SPANS || NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1 || NETCOREAPP3_1 || NET5_0 || NETSTANDARD2_1 + if (_autoResize) + ResizeIfNeed(_position + 16); + value.TryWriteBytes(_data.AsSpan(_position)); + _position += 16; +#else + PutBytesWithLength(value.ToByteArray()); +#endif + } + + public void Put(byte[] data, int offset, int length) + { + if (_autoResize) + ResizeIfNeed(_position + length); + Buffer.BlockCopy(data, offset, _data, _position, length); + _position += length; + } + + public void Put(byte[] data) + { + if (_autoResize) + ResizeIfNeed(_position + data.Length); + Buffer.BlockCopy(data, 0, _data, _position, data.Length); + _position += data.Length; + } + +#if LITENETLIB_SPANS || NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1 || NETCOREAPP3_1 || NET5_0 || NETSTANDARD2_1 + public void Put(ReadOnlySpan data) + { + if (_autoResize) + ResizeIfNeed(_position + data.Length); + data.CopyTo(_data.AsSpan(_position)); + _position += data.Length; + } +#endif + + public void PutSBytesWithLength(sbyte[] data, int offset, ushort length) + { + if (_autoResize) + ResizeIfNeed(_position + 2 + length); + FastBitConverter.GetBytes(_data, _position, length); + Buffer.BlockCopy(data, offset, _data, _position + 2, length); + _position += 2 + length; + } + + public void PutSBytesWithLength(sbyte[] data) + { + PutArray(data, 1); + } + + public void PutBytesWithLength(byte[] data, int offset, ushort length) + { + if (_autoResize) + ResizeIfNeed(_position + 2 + length); + FastBitConverter.GetBytes(_data, _position, length); + Buffer.BlockCopy(data, offset, _data, _position + 2, length); + _position += 2 + length; + } + + public void PutBytesWithLength(byte[] data) + { + PutArray(data, 1); + } + + public void Put(bool value) + { + Put((byte)(value ? 1 : 0)); + } + + public void PutArray(Array arr, int sz) + { + ushort length = arr == null ? (ushort) 0 : (ushort)arr.Length; + sz *= length; + if (_autoResize) + ResizeIfNeed(_position + sz + 2); + FastBitConverter.GetBytes(_data, _position, length); + if (arr != null) + Buffer.BlockCopy(arr, 0, _data, _position + 2, sz); + _position += sz + 2; + } + + public void PutArray(float[] value) + { + PutArray(value, 4); + } + + public void PutArray(double[] value) + { + PutArray(value, 8); + } + + public void PutArray(long[] value) + { + PutArray(value, 8); + } + + public void PutArray(ulong[] value) + { + PutArray(value, 8); + } + + public void PutArray(int[] value) + { + PutArray(value, 4); + } + + public void PutArray(uint[] value) + { + PutArray(value, 4); + } + + public void PutArray(ushort[] value) + { + PutArray(value, 2); + } + + public void PutArray(short[] value) + { + PutArray(value, 2); + } + + public void PutArray(bool[] value) + { + PutArray(value, 1); + } + + public void PutArray(string[] value) + { + ushort strArrayLength = value == null ? (ushort)0 : (ushort)value.Length; + Put(strArrayLength); + for (int i = 0; i < strArrayLength; i++) + Put(value[i]); + } + + public void PutArray(string[] value, int strMaxLength) + { + ushort strArrayLength = value == null ? (ushort)0 : (ushort)value.Length; + Put(strArrayLength); + for (int i = 0; i < strArrayLength; i++) + Put(value[i], strMaxLength); + } + + public void PutArray(T[] value) where T : INetSerializable, new() + { + ushort strArrayLength = (ushort)(value?.Length ?? 0); + Put(strArrayLength); + for (int i = 0; i < strArrayLength; i++) + value[i].Serialize(this); + } + + public void Put(IPEndPoint endPoint) + { + Put(endPoint.Address.ToString()); + Put(endPoint.Port); + } + + public void PutLargeString(string value) + { + if (string.IsNullOrEmpty(value)) + { + Put(0); + return; + } + int size = uTF8Encoding.Value.GetByteCount(value); + if (size == 0) + { + Put(0); + return; + } + Put(size); + if (_autoResize) + ResizeIfNeed(_position + size); + uTF8Encoding.Value.GetBytes(value, 0, size, _data, _position); + _position += size; + } + + public void Put(string value) + { + Put(value, 0); + } + + /// + /// Note that "maxLength" only limits the number of characters in a string, not its size in bytes. + /// + public void Put(string value, int maxLength) + { + if (string.IsNullOrEmpty(value)) + { + Put((ushort)0); + return; + } + + int length = maxLength > 0 && value.Length > maxLength ? maxLength : value.Length; + int maxSize = uTF8Encoding.Value.GetMaxByteCount(length); + if (_autoResize) + ResizeIfNeed(_position + maxSize + sizeof(ushort)); + int size = uTF8Encoding.Value.GetBytes(value, 0, length, _data, _position + sizeof(ushort)); + if (size == 0) + { + Put((ushort)0); + return; + } + Put(checked((ushort)(size + 1))); + _position += size; + } + + public void Put(T obj) where T : INetSerializable + { + obj.Serialize(this); + } + } +} diff --git a/Assets/Lib/LiteNetLib/Utils/NetDataWriter.cs.meta b/Assets/Lib/LiteNetLib/Utils/NetDataWriter.cs.meta new file mode 100644 index 0000000..4445248 --- /dev/null +++ b/Assets/Lib/LiteNetLib/Utils/NetDataWriter.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1a3c7816846d281419feafc6413780a5 \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/Utils/NetPacketProcessor.cs b/Assets/Lib/LiteNetLib/Utils/NetPacketProcessor.cs new file mode 100644 index 0000000..c18683e --- /dev/null +++ b/Assets/Lib/LiteNetLib/Utils/NetPacketProcessor.cs @@ -0,0 +1,288 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace LiteNetLib.Utils +{ + public class NetPacketProcessor + { + private static class HashCache + { + public static readonly ulong Id; + + //FNV-1 64 bit hash + static HashCache() + { + ulong hash = 14695981039346656037UL; //offset + string typeName = typeof(T).ToString(); + for (var i = 0; i < typeName.Length; i++) + { + hash ^= typeName[i]; + hash *= 1099511628211UL; //prime + } + Id = hash; + } + } + + protected delegate void SubscribeDelegate(NetDataReader reader, object userData); + private readonly NetSerializer _netSerializer; + private readonly Dictionary _callbacks = new Dictionary(); + + public NetPacketProcessor() + { + _netSerializer = new NetSerializer(); + } + + public NetPacketProcessor(int maxStringLength) + { + _netSerializer = new NetSerializer(maxStringLength); + } + + protected virtual ulong GetHash() + { + return HashCache.Id; + } + + protected virtual SubscribeDelegate GetCallbackFromData(NetDataReader reader) + { + ulong hash = reader.GetULong(); + if (!_callbacks.TryGetValue(hash, out var action)) + { + throw new ParseException("Undefined packet in NetDataReader"); + } + return action; + } + + protected virtual void WriteHash(NetDataWriter writer) + { + writer.Put(GetHash()); + } + + /// + /// Register nested property type + /// + /// INetSerializable structure + public void RegisterNestedType() where T : struct, INetSerializable + { + _netSerializer.RegisterNestedType(); + } + + /// + /// Register nested property type + /// + /// + /// + public void RegisterNestedType(Action writeDelegate, Func readDelegate) + { + _netSerializer.RegisterNestedType(writeDelegate, readDelegate); + } + + /// + /// Register nested property type + /// + /// INetSerializable class + public void RegisterNestedType(Func constructor) where T : class, INetSerializable + { + _netSerializer.RegisterNestedType(constructor); + } + + /// + /// Reads all available data from NetDataReader and calls OnReceive delegates + /// + /// NetDataReader with packets data + public void ReadAllPackets(NetDataReader reader) + { + while (reader.AvailableBytes > 0) + ReadPacket(reader); + } + + /// + /// Reads all available data from NetDataReader and calls OnReceive delegates + /// + /// NetDataReader with packets data + /// Argument that passed to OnReceivedEvent + /// Malformed packet + public void ReadAllPackets(NetDataReader reader, object userData) + { + while (reader.AvailableBytes > 0) + ReadPacket(reader, userData); + } + + /// + /// Reads one packet from NetDataReader and calls OnReceive delegate + /// + /// NetDataReader with packet + /// Malformed packet + public void ReadPacket(NetDataReader reader) + { + ReadPacket(reader, null); + } + + public void Write< +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers(Trimming.SerializerMemberTypes)] +#endif + T>(NetDataWriter writer, T packet) where T : class, new() + { + WriteHash(writer); + _netSerializer.Serialize(writer, packet); + } + + public void WriteNetSerializable(NetDataWriter writer, ref T packet) where T : INetSerializable + { + WriteHash(writer); + packet.Serialize(writer); + } + + /// + /// Reads one packet from NetDataReader and calls OnReceive delegate + /// + /// NetDataReader with packet + /// Argument that passed to OnReceivedEvent + /// Malformed packet + public void ReadPacket(NetDataReader reader, object userData) + { + GetCallbackFromData(reader)(reader, userData); + } + + /// + /// Register and subscribe to packet receive event + /// + /// event that will be called when packet deserialized with ReadPacket method + /// Method that constructs packet instead of slow Activator.CreateInstance + /// 's fields are not supported, or it has no fields + public void Subscribe< +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers(Trimming.SerializerMemberTypes)] +#endif + T>(Action onReceive, Func packetConstructor) where T : class, new() + { + _netSerializer.Register(); + _callbacks[GetHash()] = (reader, userData) => + { + var reference = packetConstructor(); + _netSerializer.Deserialize(reader, reference); + onReceive(reference); + }; + } + + /// + /// Register and subscribe to packet receive event (with userData) + /// + /// event that will be called when packet deserialized with ReadPacket method + /// Method that constructs packet instead of slow Activator.CreateInstance + /// 's fields are not supported, or it has no fields + public void Subscribe< +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers(Trimming.SerializerMemberTypes)] +#endif + T, TUserData>(Action onReceive, Func packetConstructor) where T : class, new() + { + _netSerializer.Register(); + _callbacks[GetHash()] = (reader, userData) => + { + var reference = packetConstructor(); + _netSerializer.Deserialize(reader, reference); + onReceive(reference, (TUserData)userData); + }; + } + + /// + /// Register and subscribe to packet receive event + /// This method will overwrite last received packet class on receive (less garbage) + /// + /// event that will be called when packet deserialized with ReadPacket method + /// 's fields are not supported, or it has no fields + public void SubscribeReusable< +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers(Trimming.SerializerMemberTypes)] +#endif + T>(Action onReceive) where T : class, new() + { + _netSerializer.Register(); + var reference = new T(); + _callbacks[GetHash()] = (reader, userData) => + { + _netSerializer.Deserialize(reader, reference); + onReceive(reference); + }; + } + + /// + /// Register and subscribe to packet receive event + /// This method will overwrite last received packet class on receive (less garbage) + /// + /// event that will be called when packet deserialized with ReadPacket method + /// 's fields are not supported, or it has no fields + public void SubscribeReusable< +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers(Trimming.SerializerMemberTypes)] +#endif + T, TUserData>(Action onReceive) where T : class, new() + { + _netSerializer.Register(); + var reference = new T(); + _callbacks[GetHash()] = (reader, userData) => + { + _netSerializer.Deserialize(reader, reference); + onReceive(reference, (TUserData)userData); + }; + } + + public void SubscribeNetSerializable( + Action onReceive, + Func packetConstructor) where T : INetSerializable + { + _callbacks[GetHash()] = (reader, userData) => + { + var pkt = packetConstructor(); + pkt.Deserialize(reader); + onReceive(pkt, (TUserData)userData); + }; + } + + public void SubscribeNetSerializable( + Action onReceive, + Func packetConstructor) where T : INetSerializable + { + _callbacks[GetHash()] = (reader, userData) => + { + var pkt = packetConstructor(); + pkt.Deserialize(reader); + onReceive(pkt); + }; + } + + public void SubscribeNetSerializable( + Action onReceive) where T : INetSerializable, new() + { + var reference = new T(); + _callbacks[GetHash()] = (reader, userData) => + { + reference.Deserialize(reader); + onReceive(reference, (TUserData)userData); + }; + } + + public void SubscribeNetSerializable( + Action onReceive) where T : INetSerializable, new() + { + var reference = new T(); + _callbacks[GetHash()] = (reader, userData) => + { + reference.Deserialize(reader); + onReceive(reference); + }; + } + + /// + /// Remove any subscriptions by type + /// + /// Packet type + /// true if remove is success + public bool RemoveSubscription() + { + return _callbacks.Remove(GetHash()); + } + } +} diff --git a/Assets/Lib/LiteNetLib/Utils/NetPacketProcessor.cs.meta b/Assets/Lib/LiteNetLib/Utils/NetPacketProcessor.cs.meta new file mode 100644 index 0000000..dbe78e4 --- /dev/null +++ b/Assets/Lib/LiteNetLib/Utils/NetPacketProcessor.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a601680ccf1356f41bca24883ef2e2d5 \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/Utils/NetSerializer.cs b/Assets/Lib/LiteNetLib/Utils/NetSerializer.cs new file mode 100644 index 0000000..36f2bf3 --- /dev/null +++ b/Assets/Lib/LiteNetLib/Utils/NetSerializer.cs @@ -0,0 +1,770 @@ +using System; +using System.Reflection; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Net; +using System.Runtime.Serialization; + +namespace LiteNetLib.Utils +{ + public class InvalidTypeException : ArgumentException + { + public InvalidTypeException(string message) : base(message) { } + } + + public class ParseException : Exception + { + public ParseException(string message) : base(message) { } + } + + public class NetSerializer + { + private enum CallType + { + Basic, + Array, + List + } + + private abstract class FastCall + { + public CallType Type; + public virtual void Init(MethodInfo getMethod, MethodInfo setMethod, CallType type) { Type = type; } + public abstract void Read(T inf, NetDataReader r); + public abstract void Write(T inf, NetDataWriter w); + public abstract void ReadArray(T inf, NetDataReader r); + public abstract void WriteArray(T inf, NetDataWriter w); + public abstract void ReadList(T inf, NetDataReader r); + public abstract void WriteList(T inf, NetDataWriter w); + } + + private abstract class FastCallSpecific : FastCall + { + protected Func Getter; + protected Action Setter; + protected Func GetterArr; + protected Action SetterArr; + protected Func> GetterList; + protected Action> SetterList; + + public override void ReadArray(TClass inf, NetDataReader r) { throw new InvalidTypeException("Unsupported type: " + typeof(TProperty) + "[]"); } + public override void WriteArray(TClass inf, NetDataWriter w) { throw new InvalidTypeException("Unsupported type: " + typeof(TProperty) + "[]"); } + public override void ReadList(TClass inf, NetDataReader r) { throw new InvalidTypeException("Unsupported type: List<" + typeof(TProperty) + ">"); } + public override void WriteList(TClass inf, NetDataWriter w) { throw new InvalidTypeException("Unsupported type: List<" + typeof(TProperty) + ">"); } + + protected TProperty[] ReadArrayHelper(TClass inf, NetDataReader r) + { + ushort count = r.GetUShort(); + var arr = GetterArr(inf); + arr = arr == null || arr.Length != count ? new TProperty[count] : arr; + SetterArr(inf, arr); + return arr; + } + + protected TProperty[] WriteArrayHelper(TClass inf, NetDataWriter w) + { + var arr = GetterArr(inf); + w.Put((ushort)arr.Length); + return arr; + } + + protected List ReadListHelper(TClass inf, NetDataReader r, out int len) + { + len = r.GetUShort(); + var list = GetterList(inf); + if (list == null) + { + list = new List(len); + SetterList(inf, list); + } + return list; + } + + protected List WriteListHelper(TClass inf, NetDataWriter w, out int len) + { + var list = GetterList(inf); + if (list == null) + { + len = 0; + w.Put(0); + return null; + } + len = list.Count; + w.Put((ushort)len); + return list; + } + + public override void Init(MethodInfo getMethod, MethodInfo setMethod, CallType type) + { + base.Init(getMethod, setMethod, type); + switch (type) + { + case CallType.Array: + GetterArr = (Func)Delegate.CreateDelegate(typeof(Func), getMethod); + SetterArr = (Action)Delegate.CreateDelegate(typeof(Action), setMethod); + break; + case CallType.List: + GetterList = (Func>)Delegate.CreateDelegate(typeof(Func>), getMethod); + SetterList = (Action>)Delegate.CreateDelegate(typeof(Action>), setMethod); + break; + default: + Getter = (Func)Delegate.CreateDelegate(typeof(Func), getMethod); + Setter = (Action)Delegate.CreateDelegate(typeof(Action), setMethod); + break; + } + } + } + + private abstract class FastCallSpecificAuto : FastCallSpecific + { + protected abstract void ElementRead(NetDataReader r, out TProperty prop); + protected abstract void ElementWrite(NetDataWriter w, ref TProperty prop); + + public override void Read(TClass inf, NetDataReader r) + { + ElementRead(r, out var elem); + Setter(inf, elem); + } + + public override void Write(TClass inf, NetDataWriter w) + { + var elem = Getter(inf); + ElementWrite(w, ref elem); + } + + public override void ReadArray(TClass inf, NetDataReader r) + { + var arr = ReadArrayHelper(inf, r); + for (int i = 0; i < arr.Length; i++) + ElementRead(r, out arr[i]); + } + + public override void WriteArray(TClass inf, NetDataWriter w) + { + var arr = WriteArrayHelper(inf, w); + for (int i = 0; i < arr.Length; i++) + ElementWrite(w, ref arr[i]); + } + } + + private sealed class FastCallStatic : FastCallSpecific + { + private readonly Action _writer; + private readonly Func _reader; + + public FastCallStatic(Action write, Func read) + { + _writer = write; + _reader = read; + } + + public override void Read(TClass inf, NetDataReader r) { Setter(inf, _reader(r)); } + public override void Write(TClass inf, NetDataWriter w) { _writer(w, Getter(inf)); } + + public override void ReadList(TClass inf, NetDataReader r) + { + var list = ReadListHelper(inf, r, out int len); + int listCount = list.Count; + for (int i = 0; i < len; i++) + { + if (i < listCount) + list[i] = _reader(r); + else + list.Add(_reader(r)); + } + if (len < listCount) + list.RemoveRange(len, listCount - len); + } + + public override void WriteList(TClass inf, NetDataWriter w) + { + var list = WriteListHelper(inf, w, out int len); + for (int i = 0; i < len; i++) + _writer(w, list[i]); + } + + public override void ReadArray(TClass inf, NetDataReader r) + { + var arr = ReadArrayHelper(inf, r); + int len = arr.Length; + for (int i = 0; i < len; i++) + arr[i] = _reader(r); + } + + public override void WriteArray(TClass inf, NetDataWriter w) + { + var arr = WriteArrayHelper(inf, w); + int len = arr.Length; + for (int i = 0; i < len; i++) + _writer(w, arr[i]); + } + } + + private sealed class FastCallStruct : FastCallSpecific where TProperty : struct, INetSerializable + { + private TProperty _p; + + public override void Read(TClass inf, NetDataReader r) + { + _p.Deserialize(r); + Setter(inf, _p); + } + + public override void Write(TClass inf, NetDataWriter w) + { + _p = Getter(inf); + _p.Serialize(w); + } + + public override void ReadList(TClass inf, NetDataReader r) + { + var list = ReadListHelper(inf, r, out int len); + int listCount = list.Count; + for (int i = 0; i < len; i++) + { + var itm = default(TProperty); + itm.Deserialize(r); + if(i < listCount) + list[i] = itm; + else + list.Add(itm); + } + if (len < listCount) + list.RemoveRange(len, listCount - len); + } + + public override void WriteList(TClass inf, NetDataWriter w) + { + var list = WriteListHelper(inf, w, out int len); + for (int i = 0; i < len; i++) + list[i].Serialize(w); + } + + public override void ReadArray(TClass inf, NetDataReader r) + { + var arr = ReadArrayHelper(inf, r); + int len = arr.Length; + for (int i = 0; i < len; i++) + arr[i].Deserialize(r); + } + + public override void WriteArray(TClass inf, NetDataWriter w) + { + var arr = WriteArrayHelper(inf, w); + int len = arr.Length; + for (int i = 0; i < len; i++) + arr[i].Serialize(w); + } + } + + private sealed class FastCallClass : FastCallSpecific where TProperty : class, INetSerializable + { + private readonly Func _constructor; + public FastCallClass(Func constructor) { _constructor = constructor; } + + public override void Read(TClass inf, NetDataReader r) + { + var p = _constructor(); + p.Deserialize(r); + Setter(inf, p); + } + + public override void Write(TClass inf, NetDataWriter w) + { + var p = Getter(inf); + p?.Serialize(w); + } + + public override void ReadList(TClass inf, NetDataReader r) + { + var list = ReadListHelper(inf, r, out int len); + int listCount = list.Count; + for (int i = 0; i < len; i++) + { + if (i < listCount) + { + list[i].Deserialize(r); + } + else + { + var itm = _constructor(); + itm.Deserialize(r); + list.Add(itm); + } + } + if (len < listCount) + list.RemoveRange(len, listCount - len); + } + + public override void WriteList(TClass inf, NetDataWriter w) + { + var list = WriteListHelper(inf, w, out int len); + for (int i = 0; i < len; i++) + list[i].Serialize(w); + } + + public override void ReadArray(TClass inf, NetDataReader r) + { + var arr = ReadArrayHelper(inf, r); + int len = arr.Length; + for (int i = 0; i < len; i++) + { + arr[i] = _constructor(); + arr[i].Deserialize(r); + } + } + + public override void WriteArray(TClass inf, NetDataWriter w) + { + var arr = WriteArrayHelper(inf, w); + int len = arr.Length; + for (int i = 0; i < len; i++) + arr[i].Serialize(w); + } + } + + private class IntSerializer : FastCallSpecific + { + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetInt()); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetIntArray()); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } + } + + private class UIntSerializer : FastCallSpecific + { + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetUInt()); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetUIntArray()); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } + } + + private class ShortSerializer : FastCallSpecific + { + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetShort()); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetShortArray()); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } + } + + private class UShortSerializer : FastCallSpecific + { + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetUShort()); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetUShortArray()); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } + } + + private class LongSerializer : FastCallSpecific + { + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetLong()); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetLongArray()); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } + } + + private class ULongSerializer : FastCallSpecific + { + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetULong()); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetULongArray()); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } + } + + private class ByteSerializer : FastCallSpecific + { + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetByte()); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetBytesWithLength()); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutBytesWithLength(GetterArr(inf)); } + } + + private class SByteSerializer : FastCallSpecific + { + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetSByte()); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetSBytesWithLength()); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutSBytesWithLength(GetterArr(inf)); } + } + + private class FloatSerializer : FastCallSpecific + { + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetFloat()); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetFloatArray()); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } + } + + private class DoubleSerializer : FastCallSpecific + { + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetDouble()); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetDoubleArray()); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } + } + + private class BoolSerializer : FastCallSpecific + { + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetBool()); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetBoolArray()); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } + } + + private class CharSerializer : FastCallSpecificAuto + { + protected override void ElementWrite(NetDataWriter w, ref char prop) { w.Put(prop); } + protected override void ElementRead(NetDataReader r, out char prop) { prop = r.GetChar(); } + } + + private class IPEndPointSerializer : FastCallSpecificAuto + { + protected override void ElementWrite(NetDataWriter w, ref IPEndPoint prop) { w.Put(prop); } + protected override void ElementRead(NetDataReader r, out IPEndPoint prop) { prop = r.GetNetEndPoint(); } + } + + private class GuidSerializer : FastCallSpecificAuto + { + protected override void ElementWrite(NetDataWriter w, ref Guid guid) { w.Put(guid); } + protected override void ElementRead(NetDataReader r, out Guid guid) { guid = r.GetGuid(); } + } + + private class StringSerializer : FastCallSpecific + { + private readonly int _maxLength; + public StringSerializer(int maxLength) { _maxLength = maxLength > 0 ? maxLength : short.MaxValue; } + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetString(_maxLength)); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf), _maxLength); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetStringArray(_maxLength)); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf), _maxLength); } + } + + private class EnumByteSerializer : FastCall + { + protected readonly PropertyInfo Property; + protected readonly Type PropertyType; + public EnumByteSerializer(PropertyInfo property, Type propertyType) + { + Property = property; + PropertyType = propertyType; + } + public override void Read(T inf, NetDataReader r) { Property.SetValue(inf, Enum.ToObject(PropertyType, r.GetByte()), null); } + public override void Write(T inf, NetDataWriter w) { w.Put((byte)Property.GetValue(inf, null)); } + public override void ReadArray(T inf, NetDataReader r) { throw new InvalidTypeException("Unsupported type: Enum[]"); } + public override void WriteArray(T inf, NetDataWriter w) { throw new InvalidTypeException("Unsupported type: Enum[]"); } + public override void ReadList(T inf, NetDataReader r) { throw new InvalidTypeException("Unsupported type: List"); } + public override void WriteList(T inf, NetDataWriter w) { throw new InvalidTypeException("Unsupported type: List"); } + } + + private class EnumIntSerializer : EnumByteSerializer + { + public EnumIntSerializer(PropertyInfo property, Type propertyType) : base(property, propertyType) { } + public override void Read(T inf, NetDataReader r) { Property.SetValue(inf, Enum.ToObject(PropertyType, r.GetInt()), null); } + public override void Write(T inf, NetDataWriter w) { w.Put((int)Property.GetValue(inf, null)); } + } + + private sealed class ClassInfo + { + public static ClassInfo Instance; + private readonly FastCall[] _serializers; + private readonly int _membersCount; + + public ClassInfo(List> serializers) + { + _membersCount = serializers.Count; + _serializers = serializers.ToArray(); + } + + public void Write(T obj, NetDataWriter writer) + { + for (int i = 0; i < _membersCount; i++) + { + var s = _serializers[i]; + if (s.Type == CallType.Basic) + s.Write(obj, writer); + else if (s.Type == CallType.Array) + s.WriteArray(obj, writer); + else + s.WriteList(obj, writer); + } + } + + public void Read(T obj, NetDataReader reader) + { + for (int i = 0; i < _membersCount; i++) + { + var s = _serializers[i]; + if (s.Type == CallType.Basic) + s.Read(obj, reader); + else if(s.Type == CallType.Array) + s.ReadArray(obj, reader); + else + s.ReadList(obj, reader); + } + } + } + + private abstract class CustomType + { + public abstract FastCall Get(); + } + + private sealed class CustomTypeStruct : CustomType where TProperty : struct, INetSerializable + { + public override FastCall Get() { return new FastCallStruct(); } + } + + private sealed class CustomTypeClass : CustomType where TProperty : class, INetSerializable + { + private readonly Func _constructor; + public CustomTypeClass(Func constructor) { _constructor = constructor; } + public override FastCall Get() { return new FastCallClass(_constructor); } + } + + private sealed class CustomTypeStatic : CustomType + { + private readonly Action _writer; + private readonly Func _reader; + public CustomTypeStatic(Action writer, Func reader) + { + _writer = writer; + _reader = reader; + } + public override FastCall Get() { return new FastCallStatic(_writer, _reader); } + } + + /// + /// Register custom property type + /// + /// INetSerializable structure + public void RegisterNestedType() where T : struct, INetSerializable + { + _registeredTypes.Add(typeof(T), new CustomTypeStruct()); + } + + /// + /// Register custom property type + /// + /// INetSerializable class + public void RegisterNestedType(Func constructor) where T : class, INetSerializable + { + _registeredTypes.Add(typeof(T), new CustomTypeClass(constructor)); + } + + /// + /// Register custom property type + /// + /// Any packet + /// custom type writer + /// custom type reader + public void RegisterNestedType(Action writer, Func reader) + { + _registeredTypes.Add(typeof(T), new CustomTypeStatic(writer, reader)); + } + + private NetDataWriter _writer; + private readonly int _maxStringLength; + private readonly Dictionary _registeredTypes = new Dictionary(); + + public NetSerializer() : this(0) + { + } + + public NetSerializer(int maxStringLength) + { + _maxStringLength = maxStringLength; + } + + private ClassInfo RegisterInternal< +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers(Trimming.SerializerMemberTypes)] +#endif + T>() + { + if (ClassInfo.Instance != null) + return ClassInfo.Instance; + + var props = typeof(T).GetProperties( + BindingFlags.Instance | + BindingFlags.Public | + BindingFlags.GetProperty | + BindingFlags.SetProperty); + var serializers = new List>(); + for (int i = 0; i < props.Length; i++) + { + var property = props[i]; + var propertyType = property.PropertyType; + + var elementType = propertyType.IsArray ? propertyType.GetElementType() : propertyType; + var callType = propertyType.IsArray ? CallType.Array : CallType.Basic; + + if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(List<>)) + { + elementType = propertyType.GetGenericArguments()[0]; + callType = CallType.List; + } + + if (Attribute.IsDefined(property, typeof(IgnoreDataMemberAttribute))) + continue; + + var getMethod = property.GetGetMethod(); + var setMethod = property.GetSetMethod(); + if (getMethod == null || setMethod == null) + continue; + + FastCall serialzer = null; + if (propertyType.IsEnum) + { + var underlyingType = Enum.GetUnderlyingType(propertyType); + if (underlyingType == typeof(byte)) + serialzer = new EnumByteSerializer(property, propertyType); + else if (underlyingType == typeof(int)) + serialzer = new EnumIntSerializer(property, propertyType); + else + throw new InvalidTypeException("Not supported enum underlying type: " + underlyingType.Name); + } + else if (elementType == typeof(string)) + serialzer = new StringSerializer(_maxStringLength); + else if (elementType == typeof(bool)) + serialzer = new BoolSerializer(); + else if (elementType == typeof(byte)) + serialzer = new ByteSerializer(); + else if (elementType == typeof(sbyte)) + serialzer = new SByteSerializer(); + else if (elementType == typeof(short)) + serialzer = new ShortSerializer(); + else if (elementType == typeof(ushort)) + serialzer = new UShortSerializer(); + else if (elementType == typeof(int)) + serialzer = new IntSerializer(); + else if (elementType == typeof(uint)) + serialzer = new UIntSerializer(); + else if (elementType == typeof(long)) + serialzer = new LongSerializer(); + else if (elementType == typeof(ulong)) + serialzer = new ULongSerializer(); + else if (elementType == typeof(float)) + serialzer = new FloatSerializer(); + else if (elementType == typeof(double)) + serialzer = new DoubleSerializer(); + else if (elementType == typeof(char)) + serialzer = new CharSerializer(); + else if (elementType == typeof(IPEndPoint)) + serialzer = new IPEndPointSerializer(); + else if (elementType == typeof(Guid)) + serialzer = new GuidSerializer(); + else + { + _registeredTypes.TryGetValue(elementType, out var customType); + if (customType != null) + serialzer = customType.Get(); + } + + if (serialzer != null) + { + serialzer.Init(getMethod, setMethod, callType); + serializers.Add(serialzer); + } + else + { + throw new InvalidTypeException("Unknown property type: " + propertyType.FullName); + } + } + ClassInfo.Instance = new ClassInfo(serializers); + return ClassInfo.Instance; + } + + /// 's fields are not supported, or it has no fields + public void Register< +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers(Trimming.SerializerMemberTypes)] +#endif + T>() + { + RegisterInternal(); + } + + /// + /// Reads packet with known type + /// + /// NetDataReader with packet + /// Returns packet if packet in reader is matched type + /// 's fields are not supported, or it has no fields + public T Deserialize< +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers(Trimming.SerializerMemberTypes)] +#endif + T>(NetDataReader reader) where T : class, new() + { + var info = RegisterInternal(); + var result = new T(); + try + { + info.Read(result, reader); + } + catch + { + return null; + } + return result; + } + + /// + /// Reads packet with known type (non alloc variant) + /// + /// NetDataReader with packet + /// Deserialization target + /// Returns true if packet in reader is matched type + /// 's fields are not supported, or it has no fields + public bool Deserialize< +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers(Trimming.SerializerMemberTypes)] +#endif + T>(NetDataReader reader, T target) where T : class, new() + { + var info = RegisterInternal(); + try + { + info.Read(target, reader); + } + catch + { + return false; + } + return true; + } + + /// + /// Serialize object to NetDataWriter (fast) + /// + /// Serialization target NetDataWriter + /// Object to serialize + /// 's fields are not supported, or it has no fields + public void Serialize< +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers(Trimming.SerializerMemberTypes)] +#endif + T>(NetDataWriter writer, T obj) where T : class, new() + { + RegisterInternal().Write(obj, writer); + } + + /// + /// Serialize object to byte array + /// + /// Object to serialize + /// byte array with serialized data + public byte[] Serialize< +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers(Trimming.SerializerMemberTypes)] +#endif + T>(T obj) where T : class, new() + { + if (_writer == null) + _writer = new NetDataWriter(); + _writer.Reset(); + Serialize(_writer, obj); + return _writer.CopyData(); + } + } +} diff --git a/Assets/Lib/LiteNetLib/Utils/NetSerializer.cs.meta b/Assets/Lib/LiteNetLib/Utils/NetSerializer.cs.meta new file mode 100644 index 0000000..b091a15 --- /dev/null +++ b/Assets/Lib/LiteNetLib/Utils/NetSerializer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ae1099a6726ccff4bae23455849583d2 \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/Utils/NtpPacket.cs b/Assets/Lib/LiteNetLib/Utils/NtpPacket.cs new file mode 100644 index 0000000..1ba5210 --- /dev/null +++ b/Assets/Lib/LiteNetLib/Utils/NtpPacket.cs @@ -0,0 +1,423 @@ +using System; + +namespace LiteNetLib.Utils +{ + /// + /// Represents RFC4330 SNTP packet used for communication to and from a network time server. + /// + /// + /// + /// Most applications should just use the property. + /// + /// + /// The same data structure represents both request and reply packets. + /// Request and reply differ in which properties are set and to what values. + /// + /// + /// The only real property is . + /// All other properties read from and write to the underlying byte array + /// with the exception of , + /// which is not part of the packet on network and it is instead set locally after receiving the packet. + /// + /// + /// Copied from GuerrillaNtp project + /// with permission from Robert Vazan (@robertvazan) under MIT license, see https://github.com/RevenantX/LiteNetLib/pull/236 + /// + /// + public class NtpPacket + { + private static readonly DateTime Epoch = new DateTime(1900, 1, 1); + + /// + /// Gets RFC4330-encoded SNTP packet. + /// + /// + /// Byte array containing RFC4330-encoded SNTP packet. It is at least 48 bytes long. + /// + /// + /// This is the only real property. All other properties except + /// read from or write to this byte array. + /// + public byte[] Bytes { get; } + + /// + /// Gets the leap second indicator. + /// + /// + /// Leap second warning, if any. Special value + /// indicates unsynchronized server clock. + /// Default is . + /// + /// + /// Only servers fill in this property. Clients can consult this property for possible leap second warning. + /// + public NtpLeapIndicator LeapIndicator => (NtpLeapIndicator)((Bytes[0] & 0xC0) >> 6); + + /// + /// Gets or sets protocol version number. + /// + /// + /// SNTP protocol version. Default is 4, which is the latest version at the time of this writing. + /// + /// + /// In request packets, clients should leave this property at default value 4. + /// Servers usually reply with the same protocol version. + /// + public int VersionNumber + { + get => (Bytes[0] & 0x38) >> 3; + private set => Bytes[0] = (byte)((Bytes[0] & ~0x38) | value << 3); + } + + /// + /// Gets or sets SNTP packet mode, i.e. whether this is client or server packet. + /// + /// + /// SNTP packet mode. Default is in newly created packets. + /// Server reply should have this property set to . + /// + public NtpMode Mode + { + get => (NtpMode)(Bytes[0] & 0x07); + private set => Bytes[0] = (byte)((Bytes[0] & ~0x07) | (int)value); + } + + /// + /// Gets server's distance from the reference clock. + /// + /// + /// + /// Distance from the reference clock. This property is set only in server reply packets. + /// Servers connected directly to reference clock hardware set this property to 1. + /// Statum number is incremented by 1 on every hop down the NTP server hierarchy. + /// + /// + /// Special value 0 indicates that this packet is a Kiss-o'-Death message + /// with kiss code stored in . + /// + /// + public int Stratum => Bytes[1]; + + /// + /// Gets server's preferred polling interval. + /// + /// + /// Polling interval in log2 seconds, e.g. 4 stands for 16s and 17 means 131,072s. + /// + public int Poll => Bytes[2]; + + /// + /// Gets the precision of server clock. + /// + /// + /// Clock precision in log2 seconds, e.g. -20 for microsecond precision. + /// + public int Precision => (sbyte)Bytes[3]; + + /// + /// Gets the total round-trip delay from the server to the reference clock. + /// + /// + /// Round-trip delay to the reference clock. Normally a positive value smaller than one second. + /// + public TimeSpan RootDelay => GetTimeSpan32(4); + + /// + /// Gets the estimated error in time reported by the server. + /// + /// + /// Estimated error in time reported by the server. Normally a positive value smaller than one second. + /// + public TimeSpan RootDispersion => GetTimeSpan32(8); + + /// + /// Gets the ID of the time source used by the server or Kiss-o'-Death code sent by the server. + /// + /// + /// + /// ID of server's time source or Kiss-o'-Death code. + /// Purpose of this property depends on value of property. + /// + /// + /// Stratum 1 servers write here one of several special values that describe the kind of hardware clock they use. + /// + /// + /// Stratum 2 and lower servers set this property to IPv4 address of their upstream server. + /// If upstream server has IPv6 address, the address is hashed, because it doesn't fit in this property. + /// + /// + /// When server sets to special value 0, + /// this property contains so called kiss code that instructs the client to stop querying the server. + /// + /// + public uint ReferenceId => GetUInt32BE(12); + + /// + /// Gets or sets the time when the server clock was last set or corrected. + /// + /// + /// Time when the server clock was last set or corrected or null when not specified. + /// + /// + /// This Property is usually set only by servers. It usually lags server's current time by several minutes, + /// so don't use this property for time synchronization. + /// + public DateTime? ReferenceTimestamp => GetDateTime64(16); + + /// + /// Gets or sets the time when the client sent its request. + /// + /// + /// This property is null in request packets. + /// In reply packets, it is the time when the client sent its request. + /// Servers copy this value from + /// that they find in received request packet. + /// + /// + /// + public DateTime? OriginTimestamp => GetDateTime64(24); + + /// + /// Gets or sets the time when the request was received by the server. + /// + /// + /// This property is null in request packets. + /// In reply packets, it is the time when the server received client request. + /// + /// + /// + public DateTime? ReceiveTimestamp => GetDateTime64(32); + + /// + /// Gets or sets the time when the packet was sent. + /// + /// + /// Time when the packet was sent. It should never be null. + /// Default value is . + /// + /// + /// This property must be set by both clients and servers. + /// + /// + /// + public DateTime? TransmitTimestamp { get { return GetDateTime64(40); } private set { SetDateTime64(40, value); } } + + /// + /// Gets or sets the time of reception of response SNTP packet on the client. + /// + /// + /// Time of reception of response SNTP packet on the client. It is null in request packets. + /// + /// + /// This property is not part of the protocol and has to be set when reply packet is received. + /// + /// + /// + public DateTime? DestinationTimestamp { get; private set; } + + /// + /// Gets the round-trip time to the server. + /// + /// + /// Time the request spent traveling to the server plus the time the reply spent traveling back. + /// This is calculated from timestamps in the packet as (t1 - t0) + (t3 - t2) + /// where t0 is , + /// t1 is , + /// t2 is , + /// and t3 is . + /// This property throws an exception in request packets. + /// + public TimeSpan RoundTripTime + { + get + { + CheckTimestamps(); + return (ReceiveTimestamp.Value - OriginTimestamp.Value) + (DestinationTimestamp.Value - TransmitTimestamp.Value); + } + } + + /// + /// Gets the offset that should be added to local time to synchronize it with server time. + /// + /// + /// Time difference between server and client. It should be added to local time to get server time. + /// It is calculated from timestamps in the packet as 0.5 * ((t1 - t0) - (t3 - t2)) + /// where t0 is , + /// t1 is , + /// t2 is , + /// and t3 is . + /// This property throws an exception in request packets. + /// + public TimeSpan CorrectionOffset + { + get + { + CheckTimestamps(); + return TimeSpan.FromTicks(((ReceiveTimestamp.Value - OriginTimestamp.Value) - (DestinationTimestamp.Value - TransmitTimestamp.Value)).Ticks / 2); + } + } + + /// + /// Initializes default request packet. + /// + /// + /// Properties and + /// are set appropriately for request packet. Property + /// is set to . + /// + public NtpPacket() : this(new byte[48]) + { + Mode = NtpMode.Client; + VersionNumber = 4; + TransmitTimestamp = DateTime.UtcNow; + } + + /// + /// Initializes packet from received data. + /// + internal NtpPacket(byte[] bytes) + { + if (bytes.Length < 48) + throw new ArgumentException("SNTP reply packet must be at least 48 bytes long.", "bytes"); + Bytes = bytes; + } + + /// + /// Initializes packet from data received from a server. + /// + /// Data received from the server. + /// Utc time of reception of response SNTP packet on the client. + /// + public static NtpPacket FromServerResponse(byte[] bytes, DateTime destinationTimestamp) + { + return new NtpPacket(bytes) { DestinationTimestamp = destinationTimestamp }; + } + + internal void ValidateRequest() + { + if (Mode != NtpMode.Client) + throw new InvalidOperationException("This is not a request SNTP packet."); + if (VersionNumber == 0) + throw new InvalidOperationException("Protocol version of the request is not specified."); + if (TransmitTimestamp == null) + throw new InvalidOperationException("TransmitTimestamp must be set in request packet."); + } + + internal void ValidateReply() + { + if (Mode != NtpMode.Server) + throw new InvalidOperationException("This is not a reply SNTP packet."); + if (VersionNumber == 0) + throw new InvalidOperationException("Protocol version of the reply is not specified."); + if (Stratum == 0) + throw new InvalidOperationException(string.Format("Received Kiss-o'-Death SNTP packet with code 0x{0:x}.", ReferenceId)); + if (LeapIndicator == NtpLeapIndicator.AlarmCondition) + throw new InvalidOperationException("SNTP server has unsynchronized clock."); + CheckTimestamps(); + } + + private void CheckTimestamps() + { + if (OriginTimestamp == null) + throw new InvalidOperationException("Origin timestamp is missing."); + if (ReceiveTimestamp == null) + throw new InvalidOperationException("Receive timestamp is missing."); + if (TransmitTimestamp == null) + throw new InvalidOperationException("Transmit timestamp is missing."); + if (DestinationTimestamp == null) + throw new InvalidOperationException("Destination timestamp is missing."); + } + + private DateTime? GetDateTime64(int offset) + { + var field = GetUInt64BE(offset); + if (field == 0) + return null; + return new DateTime(Epoch.Ticks + Convert.ToInt64(field * (1.0 / (1L << 32) * 10000000.0))); + } + + private void SetDateTime64(int offset, DateTime? value) + { + SetUInt64BE(offset, value == null ? 0 : Convert.ToUInt64((value.Value.Ticks - Epoch.Ticks) * (0.0000001 * (1L << 32)))); + } + + private TimeSpan GetTimeSpan32(int offset) + { + return TimeSpan.FromSeconds(GetInt32BE(offset) / (double)(1 << 16)); + } + + private ulong GetUInt64BE(int offset) + { + return SwapEndianness(BitConverter.ToUInt64(Bytes, offset)); + } + + private void SetUInt64BE(int offset, ulong value) + { + FastBitConverter.GetBytes(Bytes, offset, SwapEndianness(value)); + } + + private int GetInt32BE(int offset) + { + return (int)GetUInt32BE(offset); + } + + private uint GetUInt32BE(int offset) + { + return SwapEndianness(BitConverter.ToUInt32(Bytes, offset)); + } + + private static uint SwapEndianness(uint x) + { + return ((x & 0xff) << 24) | ((x & 0xff00) << 8) | ((x & 0xff0000) >> 8) | ((x & 0xff000000) >> 24); + } + + private static ulong SwapEndianness(ulong x) + { + return ((ulong)SwapEndianness((uint)x) << 32) | SwapEndianness((uint)(x >> 32)); + } + } + + /// + /// Represents leap second warning from the server that instructs the client to add or remove leap second. + /// + /// + public enum NtpLeapIndicator + { + /// + /// No leap second warning. No action required. + /// + NoWarning, + + /// + /// Warns the client that the last minute of the current day has 61 seconds. + /// + LastMinuteHas61Seconds, + + /// + /// Warns the client that the last minute of the current day has 59 seconds. + /// + LastMinuteHas59Seconds, + + /// + /// Special value indicating that the server clock is unsynchronized and the returned time is unreliable. + /// + AlarmCondition + } + + /// + /// Describes SNTP packet mode, i.e. client or server. + /// + /// + public enum NtpMode + { + /// + /// Identifies client-to-server SNTP packet. + /// + Client = 3, + + /// + /// Identifies server-to-client SNTP packet. + /// + Server = 4, + } +} diff --git a/Assets/Lib/LiteNetLib/Utils/NtpPacket.cs.meta b/Assets/Lib/LiteNetLib/Utils/NtpPacket.cs.meta new file mode 100644 index 0000000..34fc949 --- /dev/null +++ b/Assets/Lib/LiteNetLib/Utils/NtpPacket.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 574565bf5535d8c4483b9a10ea514486 \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/Utils/NtpRequest.cs b/Assets/Lib/LiteNetLib/Utils/NtpRequest.cs new file mode 100644 index 0000000..ca83c8d --- /dev/null +++ b/Assets/Lib/LiteNetLib/Utils/NtpRequest.cs @@ -0,0 +1,42 @@ +using System.Net; +using System.Net.Sockets; + +namespace LiteNetLib.Utils +{ + internal sealed class NtpRequest + { + private const int ResendTimer = 1000; + private const int KillTimer = 10000; + public const int DefaultPort = 123; + private readonly IPEndPoint _ntpEndPoint; + private float _resendTime = ResendTimer; + private float _killTime = 0; + + public NtpRequest(IPEndPoint endPoint) + { + _ntpEndPoint = endPoint; + } + + public bool NeedToKill => _killTime >= KillTimer; + + public bool Send(Socket socket, float time) + { + _resendTime += time; + _killTime += time; + if (_resendTime < ResendTimer) + { + return false; + } + var packet = new NtpPacket(); + try + { + int sendCount = socket.SendTo(packet.Bytes, 0, packet.Bytes.Length, SocketFlags.None, _ntpEndPoint); + return sendCount == packet.Bytes.Length; + } + catch + { + return false; + } + } + } +} diff --git a/Assets/Lib/LiteNetLib/Utils/NtpRequest.cs.meta b/Assets/Lib/LiteNetLib/Utils/NtpRequest.cs.meta new file mode 100644 index 0000000..c0cc101 --- /dev/null +++ b/Assets/Lib/LiteNetLib/Utils/NtpRequest.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3fe96c13ada00824ca87a25ca4ba596f \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/Utils/Preserve.cs b/Assets/Lib/LiteNetLib/Utils/Preserve.cs new file mode 100644 index 0000000..b73e1b9 --- /dev/null +++ b/Assets/Lib/LiteNetLib/Utils/Preserve.cs @@ -0,0 +1,12 @@ +using System; + +namespace LiteNetLib.Utils +{ + /// + /// PreserveAttribute prevents byte code stripping from removing a class, method, field, or property. + /// + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, Inherited = false)] + public class PreserveAttribute : Attribute + { + } +} diff --git a/Assets/Lib/LiteNetLib/Utils/Preserve.cs.meta b/Assets/Lib/LiteNetLib/Utils/Preserve.cs.meta new file mode 100644 index 0000000..5edb6c3 --- /dev/null +++ b/Assets/Lib/LiteNetLib/Utils/Preserve.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9c3f1f6ce10e2d346b8c85e12145c72d \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/package.json b/Assets/Lib/LiteNetLib/package.json new file mode 100644 index 0000000..e501145 --- /dev/null +++ b/Assets/Lib/LiteNetLib/package.json @@ -0,0 +1,11 @@ +{ + "name": "com.revenantx.litenetlib", + "version": "1.0.1-1", + "displayName": "LiteNetLib", + "description": "Lite reliable UDP library for .NET Standard 2.0 (Mono, .NET Core, .NET Framework)", + "unity": "2018.3", + "author": { + "name": "RevenantX", + "url": "https://github.com/RevenantX" + } +} \ No newline at end of file diff --git a/Assets/Lib/LiteNetLib/package.json.meta b/Assets/Lib/LiteNetLib/package.json.meta new file mode 100644 index 0000000..b48b2d2 --- /dev/null +++ b/Assets/Lib/LiteNetLib/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: eaddb9e208716fa45a13d98875102563 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index 8aaca93..e18f914 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -70,6 +70,7 @@ PlayerSettings: androidStartInFullscreen: 1 androidRenderOutsideSafeArea: 1 androidUseSwappy: 0 + androidDisplayOptions: 1 androidBlitType: 0 androidResizeableActivity: 1 androidDefaultWindowWidth: 1920 @@ -821,7 +822,8 @@ PlayerSettings: webGLWebAssemblyBigInt: 0 webGLCloseOnQuit: 0 webWasm2023: 0 - scriptingDefineSymbols: {} + scriptingDefineSymbols: + Android: MOREMOUNTAINS_NICEVIBRATIONS_INSTALLED additionalCompilerArguments: {} platformArchitecture: {} scriptingBackend: