using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Security.Authentication.ExtendedProtection; using System.Threading; using System.Threading.Tasks; using Vision.Core.Collections; using Vision.Core.Extensions; using Vision.Core.Logging.Loggers; using Vision.Core.Networking.Crypto; using Vision.Core.Networking.Packet; using Vision.Core.Utils; namespace Vision.Core.Networking { public abstract class NetConnectionBase : VisionObject where T : NetConnectionBase { private readonly SocketLog Logger; /// /// The maximum buffer size allowed. /// private const int ReceiveBufferSize = ushort.MaxValue; /// /// The crypto to use for this connection /// public INetCrypto Crypto { get; } /// /// The connection's unique identifier. /// public string Guid { get; protected set; } /// /// The client's handle. /// public ushort Handle { get; protected set; } /// /// Returns true if the connection exists. /// public bool IsConnected => GetConnectionState(); /// /// Returns true if the handshake has been completed. /// public bool IsEstablished { get; } /// /// Returns true if the connection is currently being sent data in chunks. /// public bool SendChunk { get => _sendChunk; set { _sendChunk = value; if (!value) { SendAwaitingBuffers(); } } } /// /// The destination type of the connection. /// public NetConnectionDestination TransmitDestinationType { get; } /// /// The sender type of the connection. /// public NetConnectionDestination ReceiveDestinationType { get; } /// /// The region of the connection. /// public GameRegion Region { get; } /// /// Time of the connection's last heartbeat. /// public DateTime LastPing { get; set; } /// /// List of buffers waiting to be sent. /// private volatile List _awaitingBuffers; /// /// The buffer used for incoming data. /// private byte[] _receiveBuffer; /// /// The stream used to read from the incoming data buffer. /// private MemoryStream _receiveStream; /// /// Returns true if the connection is currently being sent data in chunks. /// private bool _sendChunk; /// /// The socket to use for data transferring. /// private Socket _socket; /// /// Returns the remote endpoint of the connection. /// public IPEndPoint RemoteEndPoint => _socket.RemoteEndPoint as IPEndPoint; /// /// Gets the socket's connection state. /// /// True if the socket is connected. private bool GetConnectionState() { // If the socket is already null, we've already called // Disconnect(). if (_socket == null || !_socket.Connected) { return false; } var blocking = _socket.Blocking; try { var tempBuffer = new byte[1]; _socket.Blocking = false; _socket.Send(tempBuffer, 0, 0); // If the send fails, this line won't be reached because an exception // will be thrown. return true; } catch (SocketException e) { // 10035 == WSAEWOULDBLOCK var ret = e.NativeErrorCode == 10035; if (!ret) { Disconnect(); } return ret; } finally { if (_socket != null) { _socket.Blocking = blocking; } } } protected NetConnectionBase(NetConnectionDestination txDest, NetConnectionDestination rxDest, GameRegion region = GameRegion.GR_NA, INetCrypto crypto = null) { Logger = new SocketLog(GetType()); _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); TransmitDestinationType = txDest; ReceiveDestinationType = rxDest; Region = region; IsEstablished = true; Guid = System.Guid.NewGuid().ToString().Replace("-", ""); Handle = (ushort)MathUtils.Random(ushort.MaxValue); // TODO: Create handles in a range based on their destination/source _receiveStream = new MemoryStream(); _awaitingBuffers = new List(); // default to 2020 NA crypto TODO: handle region cryptos separate Crypto = Crypto == null ? new NetCryptoNa2020() : crypto; } // TODO: FiestaNetworkServer ctor /* public FiestaNetworkConnection(FiestaNetworkClient client, Socket socket, FiestaNetworkConnectionType type) { Client = client; this.socket = socket; Type = type; Guid = System.Guid.NewGuid().ToString().Replace("-", ""); Handle = (ushort)MathUtils.Random(ushort.MaxValue); receiveStream = new MemoryStream(); awaitingBuffers = new List(); // wait on seed ack to do `seed`, `setSeed`, and `IsEstablished` // somehow? or is this not needed at all, since we only Connect? } */ public delegate void OnConnectCallback(NetConnectionDestination dest, IPEndPoint endPoint); public delegate void OnDisconnectCallback(NetConnectionDestination dest, IPEndPoint endPoint); private readonly FastList _connectCallbacks = new FastList(); private readonly FastList _disconnectCallbacks = new FastList(); public void AddConnectCallback(OnConnectCallback callback) => _connectCallbacks.Add(callback); public void AddDisconnectCallback(OnDisconnectCallback callback) => _disconnectCallbacks.Add(callback); /// /// Destroys the connection. /// public void Disconnect() { foreach (var dc in _disconnectCallbacks) { dc.Invoke(TransmitDestinationType, RemoteEndPoint); } Logger.Info($"Disconnected from target: {TransmitDestinationType.ToMessage()}, Endpoint: {RemoteEndPoint.ToSimpleString()}"); _socket?.Close(); // Close() will call Dispose() automatically for us. _socket = null; } /// /// Sends data to the connection. /// /// The data to send. public void SendData(byte[] buffer) { if (!IsConnected) { return; } if (_sendChunk) { _awaitingBuffers.Add(buffer); return; } var bytesToSend = buffer.Length; var bytesSent = 0; if (bytesToSend >= ReceiveBufferSize) { Logger.Debug("Exceeded max message size while sending data to a connection."); } while (bytesSent < bytesToSend) { bytesSent += _socket.Send(buffer, bytesSent, bytesToSend - bytesSent, SocketFlags.None); if (bytesSent <= bytesToSend) continue; Logger.Warning($"BUFFER OVERFLOW OCCURRED - Sent {bytesSent - bytesToSend} bytes more than expected."); break; } } public void SendDataAsync(byte[] buffer, EventHandler completedCallback) { if (!IsConnected) { return; } if (_sendChunk) { _awaitingBuffers.Add(buffer); return; } var bytesToSend = buffer.Length; if (bytesToSend >= ReceiveBufferSize) { Logger.Debug("Exceeded max message size while sending data to a connection."); return; } var e = new SocketAsyncEventArgs(); e.SetBuffer(buffer); e.Completed += completedCallback; bool completedAsync; try { completedAsync = _socket.SendAsync(e); // await _socket.SendAsync(buffer, SocketFlags.None); } catch (SocketException ex) { Logger.Info($"Socket not prepared for send! {ex.Message}"); return; } if (!completedAsync) { completedCallback.Invoke(this, e); } } /// /// Begins to accept data from the connection. /// private void BeginReceivingData() { if (!IsConnected) { return; } _receiveBuffer = new byte[ReceiveBufferSize]; _socket.BeginReceive(_receiveBuffer, 0, _receiveBuffer.Length, SocketFlags.None, ReceivedData, null); } /// /// Attempts to connect to the specified target endpoint. /// /// The target endpoint. public void Connect(IPEndPoint endPoint) { _socket.BeginConnect(endPoint, ConnectedToTarget, endPoint); } /// /// Attempts to connect to the specified target endpoint. /// /// The target IP Address. /// The target port. public void Connect(string targetIP, ushort targetPort) { Connect(new IPEndPoint(IPAddress.Parse(targetIP), targetPort)); } /// /// This callback is called when we have connected to the requested /// target endpoint. /// /// The result object. private void ConnectedToTarget(IAsyncResult e) { try { _socket.EndConnect(e); foreach (var c in _connectCallbacks) { c.Invoke(TransmitDestinationType, RemoteEndPoint); } Logger.Info($"Connected to target: {TransmitDestinationType.ToMessage()}, Endpoint: {RemoteEndPoint.ToSimpleString()}"); BeginReceivingData(); } catch { Logger.Warning("Remote socket connection attempt failed. Trying again..."); Thread.Sleep(3000); // 3 seconds. Connect((IPEndPoint)e.AsyncState); } } /// /// Destroys the instance. /// protected override void Destroy() { _socket?.Close(); } /// /// Gets all messages from the buffer. /// /// The buffer to get message from. private void GetMessagesFromBuffer(byte[] buffer) { if (!IsConnected) { return; } _receiveStream.Write(buffer, 0, buffer.Length); while (TryParseMessage()) { } } /// /// Called when data was received from the connection. /// /// The status of the operation. private void ReceivedData(IAsyncResult e) { if (!IsConnected) { return; } var count = _socket.EndReceive(e); var buffer = new byte[count]; if (count <= 0) { Disconnect(); return; } Array.Copy(_receiveBuffer, 0, buffer, 0, count); GetMessagesFromBuffer(buffer); BeginReceivingData(); } /// /// Sends the awaiting buffers as one big chunk. /// private void SendAwaitingBuffers() { if (!IsConnected) { return; } _awaitingBuffers.Copy(out var bufferList); _awaitingBuffers.Clear(); var size = bufferList.Sum(b => b.Length); var chunk = new byte[size]; var pointer = 0; for (var i = 0; i < bufferList.Count; i++) { var buffer = bufferList[i]; Array.Copy(buffer, 0, chunk, pointer, buffer.Length); pointer += buffer.Length; } SendData(chunk); } /// /// Tries to parse a message from the receive stream. /// private bool TryParseMessage() { if (!IsConnected) { return false; } _receiveStream.Position = 0; if (_receiveStream.Length < 1) { return false; } ushort messageSize; var sizeBuffer = new byte[1]; _receiveStream.ReadAsync(sizeBuffer, 0, 1); if (sizeBuffer[0] != 0) { messageSize = sizeBuffer[0]; } else { if (_receiveStream.Length - _receiveStream.Position < 2) { return false; } sizeBuffer = new byte[2]; _receiveStream.Read(sizeBuffer, 0, 2); messageSize = BitConverter.ToUInt16(sizeBuffer, 0); } if (_receiveStream.Length - _receiveStream.Position < messageSize) { return false; } var messageBuffer = new byte[messageSize]; _receiveStream.Read(messageBuffer, 0, messageSize); var isFromClient = ReceiveDestinationType.IsClient(); var isToServer = TransmitDestinationType.IsServer(); if (!isFromClient && !isToServer && Crypto.WasSeedSet()) { Crypto.XorBuffer(messageBuffer, 0, messageBuffer.Length); } var packet = new NetPacket(messageBuffer); GetAndRunHandler(packet, this as T); // Trims the receive stream. var remainingByteCount = new byte[_receiveStream.Length - _receiveStream.Position]; _receiveStream.Read(remainingByteCount, 0, remainingByteCount.Length); _receiveStream = new MemoryStream(); _receiveStream.Write(remainingByteCount, 0, remainingByteCount.Length); return true; } protected void GetAndRunHandler(NetPacket packet, T connection) { var hasHandler = NetPacketHandlerLoader.TryGetHandler(packet.Command, out var handler, out var destinations); var isForThisDest = destinations?.Contains(connection.ReceiveDestinationType) ?? false; if (hasHandler && isForThisDest) { if (!NetPacket.DebugSkipCommands.Contains(packet.Command)) { var watch = Stopwatch.StartNew(); handler(packet, connection); watch.Stop(); var millisString = $"{watch.Elapsed.TotalMilliseconds:N2}"; Logger.Debug($"Handler for {packet.Command} took {millisString}ms to complete."); } else { // quiet handle handler(packet, connection); } } else if (hasHandler) // not this dest { Logger.Warning($"Got handled command from {connection.TransmitDestinationType} NOT FOR {connection.ReceiveDestinationType} : {packet.Command}"); } else { var commandHex = $"0x{packet.Command:x}"; var commandName = Enum.GetName(typeof(NetCommand), packet.Command); if (string.IsNullOrEmpty(commandName)) commandName = "Unknown"; Logger.Unhandled($"Got unhandled command from {connection.TransmitDestinationType}: {commandHex} | {commandName}"); } } } }