using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using ProtoBuf; using ProtoBuf.Meta; using xServer.Core.Compression; using xServer.Core.Encryption; using xServer.Core.Extensions; using xServer.Core.Packets; using xServer.Settings; namespace xServer.Core.Networking { public class Client { /// /// Occurs when the state of the client changes. /// public event ClientStateEventHandler ClientState; /// /// Represents the method that will handle a change in a client's state. /// /// The client which changed its state. /// The new connection state of the client. public delegate void ClientStateEventHandler(Client s, bool connected); /// /// Fires an event that informs subscribers that the state of the client has changed. /// /// The new connection state of the client. private void OnClientState(bool connected) { if (Connected == connected) return; Connected = connected; if (ClientState != null) { ClientState(this, connected); } if (!connected && !_parentServer.Processing) _parentServer.RemoveClient(this); } /// /// Occurs when a packet is received from the client. /// public event ClientReadEventHandler ClientRead; /// /// Represents the method that will handle a packet received from the client. /// /// The client that has received the packet. /// The packet that received by the client. public delegate void ClientReadEventHandler(Client s, IPacket packet); /// /// Fires an event that informs subscribers that a packet has been /// received from the client. /// /// The packet that received by the client. private void OnClientRead(IPacket packet) { if (ClientRead != null) { ClientRead(this, packet); } } /// /// Occurs when a packet is sent by the client. /// public event ClientWriteEventHandler ClientWrite; /// /// Represents the method that will handle the sent packet. /// /// The client that has sent the packet. /// The packet that has been sent by the client. /// The length of the packet. /// The packet in raw bytes. public delegate void ClientWriteEventHandler(Client s, IPacket packet, long length, byte[] rawData); /// /// Fires an event that informs subscribers that the client has sent a packet. /// /// The packet that has been sent by the client. /// The length of the packet. /// The packet in raw bytes. private void OnClientWrite(IPacket packet, long length, byte[] rawData) { if (ClientWrite != null) { ClientWrite(this, packet, length, rawData); } } /// /// Checks whether the clients are equal. /// /// Client to compare with. /// public bool Equals(Client c) { return this.EndPoint.Port == c.EndPoint.Port; // this port is always unique for each client } /// /// The type of the packet received. /// public enum ReceiveType { Header, Payload } /// /// Handle of the Client Socket. /// private readonly Socket _handle; /// /// The internal index of the packet type. /// private int _typeIndex; /// /// The Queue which holds buffers to send. /// private readonly Queue _sendBuffers = new Queue(); /// /// Determines if the client is currently sending packets. /// private bool _sendingPackets; /// /// Lock object for the sending packets boolean. /// private readonly object _sendingPacketsLock = new object(); /// /// The Queue which holds buffers to read. /// private readonly Queue _readBuffers = new Queue(); /// /// Determines if the client is currently reading packets. /// private bool _readingPackets; /// /// Lock object for the reading packets boolean. /// private readonly object _readingPacketsLock = new object(); // receive info private int _readOffset; private int _writeOffset; private int _tempHeaderOffset; private int _readableDataLen; private int _payloadLen; private ReceiveType _receiveState = ReceiveType.Header; /// /// The time when the client connected. /// public DateTime ConnectedTime { get; private set; } /// /// The connection state of the client. /// public bool Connected { get; private set; } /// /// Stores values of the user. /// public UserState Value { get; set; } /// /// The Endpoint which the client is connected to. /// public IPEndPoint EndPoint { get; private set; } /// /// The parent server of the client. /// private readonly Server _parentServer; /// /// The buffer for the client's incoming packets. /// private byte[] _readBuffer; /// /// The buffer for the client's incoming payload. /// private byte[] _payloadBuffer; /// /// The temporary header to store parts of the header. /// /// /// This temporary header is used when we have i.e. /// only 2 bytes left to read from the buffer but need more /// which can only be read in the next Receive callback /// private byte[] _tempHeader; /// /// Decides if we need to append bytes to the header. /// private bool _appendHeader; private const bool encryptionEnabled = true; private const bool compressionEnabled = true; public Client() { } internal Client(Server server, Socket sock, Type[] packets) { try { _parentServer = server; AddTypesToSerializer(typeof(IPacket), packets); Initialize(); _handle = sock; _handle.SetKeepAliveEx(_parentServer.KEEP_ALIVE_INTERVAL, _parentServer.KEEP_ALIVE_TIME); _handle.NoDelay = true; EndPoint = (IPEndPoint)_handle.RemoteEndPoint; ConnectedTime = DateTime.UtcNow; _readBuffer = Server.BufferManager.GetBuffer(); _tempHeader = new byte[_parentServer.HEADER_SIZE]; _handle.BeginReceive(_readBuffer, 0, _readBuffer.Length, SocketFlags.None, AsyncReceive, null); OnClientState(true); } catch { Disconnect(); } } private void Initialize() { AddTypeToSerializer(typeof(IPacket), typeof(UnknownPacket)); Value = new UserState(); } private void AsyncReceive(IAsyncResult result) { try { int bytesTransferred; try { bytesTransferred = _handle.EndReceive(result); if (bytesTransferred <= 0) { OnClientState(false); return; } } catch (Exception) { OnClientState(false); return; } _parentServer.BytesReceived += bytesTransferred; byte[] received = new byte[bytesTransferred]; Array.Copy(_readBuffer, received, received.Length); lock (_readBuffers) { _readBuffers.Enqueue(received); } lock (_readingPacketsLock) { if (!_readingPackets) { _readingPackets = true; ThreadPool.QueueUserWorkItem(AsyncReceive); } } } catch { } try { _handle.BeginReceive(_readBuffer, 0, _readBuffer.Length, SocketFlags.None, AsyncReceive, null); } catch (ObjectDisposedException) { } catch { Disconnect(); } } private void AsyncReceive(object state) { while (true) { byte[] readBuffer; lock (_readBuffers) { if (_readBuffers.Count == 0) { lock (_readingPacketsLock) { _readingPackets = false; } return; } readBuffer = _readBuffers.Dequeue(); } _readableDataLen += readBuffer.Length; bool process = true; while (process) { switch (_receiveState) { case ReceiveType.Header: { if (_readableDataLen >= _parentServer.HEADER_SIZE) { // we can read the header int size = (_appendHeader) ? _parentServer.HEADER_SIZE - _tempHeaderOffset : _parentServer.HEADER_SIZE; try { if (_appendHeader) { Array.Copy(readBuffer, _readOffset, _tempHeader, _tempHeaderOffset, size); _payloadLen = (int)_tempHeader[0] | _tempHeader[1] << 8 | _tempHeader[2] << 16; _tempHeaderOffset = 0; _appendHeader = false; } else { _payloadLen = (int)readBuffer[_readOffset] | readBuffer[_readOffset + 1] << 8 | readBuffer[_readOffset + 2] << 16; } if (_payloadLen <= 0) throw new Exception("invalid header"); } catch (Exception) { process = false; break; } _readableDataLen -= size; _readOffset += size; _receiveState = ReceiveType.Payload; } else // _parentServer.HEADER_SIZE < _readableDataLen { _appendHeader = true; Array.Copy(readBuffer, _readOffset, _tempHeader, _tempHeaderOffset, _readableDataLen); _tempHeaderOffset += _readableDataLen; process = false; } break; } case ReceiveType.Payload: { if (_payloadBuffer == null || _payloadBuffer.Length != _payloadLen) _payloadBuffer = new byte[_payloadLen]; int length = _readableDataLen; if (_writeOffset + _readableDataLen >= _payloadLen) { length = _payloadLen - _writeOffset; } try { Array.Copy(readBuffer, _readOffset, _payloadBuffer, _writeOffset, length); } catch { Disconnect(); } _writeOffset += length; _readOffset += length; _readableDataLen -= length; if (_writeOffset == _payloadLen) { if (encryptionEnabled) _payloadBuffer = AES.Decrypt(_payloadBuffer, Encoding.UTF8.GetBytes(XMLSettings.Password)); if (_payloadBuffer.Length > 0) { if (compressionEnabled) _payloadBuffer = new SafeQuickLZ().Decompress(_payloadBuffer, 0, _payloadBuffer.Length); using (MemoryStream deserialized = new MemoryStream(_payloadBuffer)) { IPacket packet = Serializer.DeserializeWithLengthPrefix(deserialized, PrefixStyle.Fixed32); OnClientRead(packet); } } else // payload decryption failed process = false; _receiveState = ReceiveType.Header; _payloadBuffer = null; _payloadLen = 0; _writeOffset = 0; } if (_readableDataLen == 0) process = false; break; } } } if (_receiveState == ReceiveType.Header) { _writeOffset = 0; // prepare for next packet } _readOffset = 0; _readableDataLen = 0; } } /// /// Sends a packet to the connected client. /// /// The type of the packet. /// The packet to be send. public void Send(T packet) where T : IPacket { if (!Connected) return; lock (_sendBuffers) { try { using (MemoryStream ms = new MemoryStream()) { Serializer.SerializeWithLengthPrefix(ms, packet, PrefixStyle.Fixed32); byte[] payload = ms.ToArray(); _sendBuffers.Enqueue(payload); OnClientWrite(packet, payload.LongLength, payload); lock (_sendingPacketsLock) { if (_sendingPackets) return; _sendingPackets = true; } ThreadPool.QueueUserWorkItem(Send); } } catch { } } } /// /// Sends a packet to the connected client. /// Blocks the thread until all packets have been sent. /// /// The type of the packet. /// The packet to be send. public void SendBlocking(T packet) where T : IPacket { Send(packet); while (_sendingPackets) { Thread.Sleep(10); } } private void Send(object state) { while (true) { if (!Connected) { SendCleanup(true); return; } byte[] payload; lock (_sendBuffers) { if (_sendBuffers.Count == 0) { SendCleanup(); return; } payload = _sendBuffers.Dequeue(); } if (compressionEnabled) payload = new SafeQuickLZ().Compress(payload, 0, payload.Length, 3); if (encryptionEnabled) payload = AES.Encrypt(payload, Encoding.UTF8.GetBytes(XMLSettings.Password)); byte[] header = new byte[] { (byte)payload.Length, (byte)(payload.Length >> 8), (byte)(payload.Length >> 16) }; byte[] data = new byte[payload.Length + _parentServer.HEADER_SIZE]; Array.Copy(header, data, header.Length); Array.Copy(payload, 0, data, _parentServer.HEADER_SIZE, payload.Length); _parentServer.BytesSent += data.Length; try { _handle.Send(data); } catch (Exception) { Disconnect(); SendCleanup(true); return; } } } private void SendCleanup(bool clear = false) { lock (_sendingPacketsLock) { _sendingPackets = false; } if (!clear) return; lock (_sendBuffers) { _sendBuffers.Clear(); } } /// /// Disconnect the client from the server and dispose of /// resources associated with the client. /// public void Disconnect() { OnClientState(false); if (_handle != null) { _handle.Close(); _readOffset = 0; _writeOffset = 0; _readableDataLen = 0; _payloadLen = 0; _payloadBuffer = null; if (Server.BufferManager != null) Server.BufferManager.ReturnBuffer(_readBuffer); } } /// /// Adds a Type to the serializer so a message can be properly serialized. /// /// The parent type, i.e.: IPacket /// Type to be added public void AddTypeToSerializer(Type parent, Type type) { if (type == null || parent == null) throw new ArgumentNullException(); bool isAlreadyAdded = RuntimeTypeModel.Default[parent].GetSubtypes().Any(subType => subType.DerivedType.Type == type); if (!isAlreadyAdded) RuntimeTypeModel.Default[parent].AddSubType(_typeIndex += 1, type); } /// /// Adds Types to the serializer. /// /// The parent type, i.e.: IPacket /// Types to add. public void AddTypesToSerializer(Type parent, params Type[] types) { foreach (Type type in types) AddTypeToSerializer(parent, type); } } }