using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using IgniteEngine.Protocols;
namespace IgniteEngine.Networking
{
///
/// Class that sends and receives data to and from a server.
///
public class NetworkConnection : Object
{
///
/// The maximum buffer size allowed.
///
private const int RECEIVE_BUFFER_SIZE = ushort.MaxValue;
///
/// Array of xor values.
///
private static readonly byte[] XorTable = {7, 89, 105, 74, 148, 17, 148, 133, 140, 136, 5, 203, 160, 158, 205, 88, 58, 54, 91, 26, 106, 22, 254, 189, 223, 148, 2, 248, 33, 150, 200, 233, 158, 247, 191, 189, 207, 205, 178, 122, 0, 159, 64, 34, 252, 17, 249, 12, 46, 18, 251, 167, 116, 10, 125, 120, 64, 30, 44, 160, 45, 6, 203, 168, 185, 126, 239, 222, 73, 234, 78, 19, 22, 22, 128, 244, 61, 194, 154, 212, 134, 215, 148, 36, 23, 244, 214, 101, 189, 63, 219, 228, 225, 15, 80, 246, 236, 122, 154, 12, 39, 61, 36, 102, 211, 34, 104, 156, 154, 82, 11, 224, 249, 165, 11, 37, 218, 128, 73, 13, 253, 62, 119, 209, 86, 168, 183, 244, 15, 155, 232, 15, 82, 71, 245, 111, 131, 32, 34, 219, 15, 11, 177, 67, 133, 193, 203, 164, 11, 2, 25, 223, 240, 139, 236, 219, 108, 109, 102, 173, 69, 190, 137, 20, 126, 47, 137, 16, 184, 147, 96, 216, 96, 222, 246, 254, 110, 155, 202, 6, 193, 117, 149, 51, 207, 192, 178, 224, 204, 165, 206, 18, 246, 229, 181, 180, 38, 197, 178, 24, 79, 42, 93, 38, 27, 101, 77, 245, 69, 201, 132, 20, 220, 124, 18, 75, 24, 156, 199, 36, 231, 60, 100, 255, 214, 58, 44, 238, 140, 129, 73, 57, 108, 183, 220, 189, 148, 226, 50, 247, 221, 10, 252, 2, 1, 100, 236, 76, 148, 10, 177, 86, 245, 201, 169, 52, 222, 15, 56, 39, 188, 129, 48, 15, 123, 56, 37, 254, 232, 62, 41, 186, 85, 67, 191, 107, 159, 31, 138, 73, 82, 24, 127, 138, 248, 136, 36, 92, 79, 225, 168, 48, 135, 142, 80, 31, 47, 209, 12, 180, 253, 10, 188, 220, 18, 133, 226, 82, 238, 74, 88, 56, 171, 255, 198, 61, 185, 96, 100, 10, 180, 80, 213, 64, 137, 23, 154, 213, 133, 207, 236, 13, 126, 129, 127, 227, 195, 4, 1, 34, 236, 39, 204, 250, 62, 33, 166, 84, 200, 222, 0, 182, 223, 39, 159, 246, 37, 52, 7, 133, 191, 167, 165, 165, 224, 131, 12, 61, 93, 32, 64, 175, 96, 163, 100, 86, 243, 5, 196, 28, 125, 55, 152, 195, 232, 90, 110, 88, 133, 164, 154, 107, 106, 244, 163, 123, 97, 155, 9, 64, 30, 96, 75, 50, 217, 81, 164, 254, 249, 93, 78, 74, 251, 74, 212, 124, 51, 2, 51, 213, 157, 206, 91, 170, 90, 124, 216, 248, 5, 250, 31, 43, 140, 114, 87, 80, 174, 108, 25, 137, 202, 1, 252, 252, 41, 155, 97, 18, 104, 99, 101, 70, 38, 196, 91, 80, 170, 43, 190, 239, 154, 121, 2, 35, 117, 44, 32, 19, 253, 217, 90, 118, 35, 241, 11, 181, 184, 89, 249, 159, 122, 230, 6, 233, 165, 58, 180, 80, 191, 22, 88, 152, 179, 154, 110, 54, 238, 141, 235};
///
/// The connection's account.
///
public Account Account { get; set; }
///
/// The connection's unique dentifier.
///
public string GUID { get; set; }
///
/// The client's handle.
///
public ushort Handle { get; set; }
///
/// Returns true if the connection exists.
///
public bool IsConnected => GetConnectionState();
///
/// Returns true if the handshake has been completed.
///
public bool IsEstablished { get; set; }
///
/// Returns true if the connection is currently being sent data in chunks.
///
public bool SendChunk
{
get => sendChunk;
set
{
sendChunk = value;
if (!value)
{
SendAwaitingBuffers();
}
}
}
///
/// The server that accepted the connection
///
public NetworkServer Server { get; }
///
/// The connection's type.
///
public NetworkConnectionType Type { get; }
///
/// 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 connection's current index in the xor table.
///
private ushort seed;
///
/// Returns true if the seed was set.
///
private bool seedSet;
///
/// Creates a new instance of the class.
///
public NetworkConnection(NetworkConnectionType type)
{
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Type = type;
IsEstablished = true;
GUID = Guid.NewGuid().ToString().Replace("-", "");
Handle = (ushort)Mathd.Random(ushort.MaxValue);
receiveStream = new MemoryStream();
awaitingBuffers = new List();
}
///
/// Creates a new instance of the class.
///
/// The server that accepted the connection.
/// The socket to use for data transferring.
/// The connection's type.
public NetworkConnection(NetworkServer server, Socket socket, NetworkConnectionType type)
{
Server = server;
this.socket = socket;
Type = type;
GUID = Guid.NewGuid().ToString().Replace("-", "");
Handle = (ushort) Mathd.Random(ushort.MaxValue);
receiveStream = new MemoryStream();
awaitingBuffers = new List();
SetSeed((ushort) Mathd.Random(498));
BeginReceivingData();
new PROTO_NC_MISC_SEED_ACK(seed).Send(this);
IsEstablished = true;
if (Type != NetworkConnectionType.NCT_CLIENT)
{
new PROTO_NC_MISC_S2SCONNECTION_RDY().Send(this);
}
}
///
/// The socket to use for data transferring.
///
private Socket socket;
///
/// Gets the sockets remote endpoint IP address
///
/// Remote endpoint IP address
public string GetRemoteIP => (socket.RemoteEndPoint as IPEndPoint)?.Address.ToString();
///
/// 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;
}
}
}
///
/// Decrypts a buffer.
///
/// The buffer to decrypt.
/// The offset to start decrypting.
/// The length to decrypt.
public void DecryptBuffer(byte[] buffer, int offset, int length)
{
for (var i = 0; i < length; i++)
{
buffer[offset + i] ^= XorTable[seed];
seed++;
if (seed >= 499)
{
seed = 0;
}
}
}
///
/// Destroys the connection.
///
public void Disconnect()
{
// May not want to remove here, might want to handle disconnections
// individually per server by checking the list for disconnected
// clients.
Server?.Connections.Remove(this);
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 >= RECEIVE_BUFFER_SIZE)
{
Debug.LogAssert("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)
{
Debug.LogWarning($"BUFFER OVERFLOW OCCURRED - Sent {bytesSent - bytesToSend} bytes more than expected.");
break;
}
}
}
///
/// Sets the client's current seed.
///
/// The value to set the seed to.
public void SetSeed(ushort value)
{
seed = value;
seedSet = true;
}
///
/// Begins to accept data from the connection.
///
private void BeginReceivingData()
{
if (!IsConnected)
{
return;
}
receiveBuffer = new byte[RECEIVE_BUFFER_SIZE];
socket.BeginReceive(receiveBuffer, 0, receiveBuffer.Length, SocketFlags.None, ReceivedData, null);
}
///
/// Attempts to connect to the specified target endpoint.
///
/// The target IP Address.
/// The target port.
public void Connect(string targetIP, ushort targetPort)
{
socket.BeginConnect(new IPEndPoint(IPAddress.Parse(targetIP), targetPort), ConnectedToTarget, new object[] { 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);
BeginReceivingData();
}
catch
{
Debug.LogWarning("Remote socket connection attempt failed. Trying again...");
Thread.Sleep(3000); // 3 seconds.
Connect((string) ((object[]) e.AsyncState)[0], (ushort) ((object[]) e.AsyncState)[1]);
}
}
///
/// 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.Read(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);
if (seedSet)
{
DecryptBuffer(messageBuffer, 0, messageBuffer.Length);
}
var message = new NetworkMessage(messageBuffer);
if (!NetworkMessageHandler.TryFetch(message.Command, out var handler) || handler == null)
{
Debug.LogAssert("Unhandled message", message);
}
NetworkMessageHandler.Invoke(handler, message, this);
// 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;
}
}
}