// // Licensed under the MIT License. See LICENSE file in the project root for full license information. // namespace MUnique.OpenMU.ConnectServer; using System.Buffers; using System.Net; using System.Threading; using Microsoft.Extensions.Logging; using MUnique.OpenMU.ConnectServer.PacketHandler; using MUnique.OpenMU.Network; using MUnique.OpenMU.Network.Packets.ConnectServer; using Nito.AsyncEx.Synchronous; /// /// The client which connected to the connect server. /// internal sealed class Client : IDisposable { private readonly ILogger _logger; private readonly byte[] _receiveBuffer; private readonly Timer _onlineTimer; private readonly IPacketHandler _packetHandler; private bool _disposed; private DateTime _lastReceive; /// /// Initializes a new instance of the class. /// /// The connection. /// The timeout. /// The packet handler. /// Maximum size of the packet. This value is also used to initialize the receive buffer. /// The logger. public Client(IConnection connection, TimeSpan timeout, IPacketHandler packetHandler, byte maxPacketSize, ILogger logger) { this.Connection = connection; this.Connection.PacketReceived += this.OnPacketReceivedAsync; this.Timeout = timeout; this._packetHandler = packetHandler; this._logger = logger; this._lastReceive = DateTime.Now; var checkInterval = new TimeSpan(0, 0, 20); this._onlineTimer = new Timer(this.OnOnlineTimerElapsed, null, checkInterval, checkInterval); this._receiveBuffer = new byte[maxPacketSize]; } /// /// Gets or sets the timeout after which the client gets disconnected if he is inactive. /// public TimeSpan Timeout { get; set; } /// /// Gets or sets the server information request count. /// /// Used for DOS protection. public int ServerInfoRequestCount { get; set; } /// /// Gets or sets the FTP request count. /// /// Used for DOS protection. public int FtpRequestCount { get; set; } /// /// Gets or sets the server list request count. /// /// Used for DOS protection. public int ServerListRequestCount { get; set; } /// /// Gets or sets the ip from which the client is connecting. /// public IPAddress Address { get; set; } = IPAddress.None; /// /// Gets or sets the port from which the client is connecting. /// public int Port { get; set; } /// /// Gets the connection from/to the client. /// internal IConnection Connection { get; } /// public void Dispose() { if (!this._disposed) { this._disposed = true; this._onlineTimer.Dispose(); this.Connection.Dispose(); } } /// /// Sends the hello packet. /// internal ValueTask SendHelloAsync() { return this.Connection.SendHelloAsync(); } private void OnOnlineTimerElapsed(object? state) { try { if (this.Connection.Connected && DateTime.Now.Subtract(this._lastReceive) > this.Timeout) { this._logger.LogDebug("Connection Timeout ({0}): Address {1}:{2} will be disconnected.", this.Timeout, this.Address, this.Port); this.Connection.DisconnectAsync().AsTask().WaitAndUnwrapException(); } } catch (Exception ex) { this._logger.LogError(ex, "Error when disconnecting client. Address {1}:{2}", this.Address, this.Port); } } private async ValueTask OnPacketReceivedAsync(ReadOnlySequence sequence) { this._lastReceive = DateTime.Now; if (sequence.Length > this._receiveBuffer.Length) { this._logger.LogInformation($"Client {this.Address}:{this.Port} will be disconnected because it sent a packet which was too big (size of {sequence.Length}"); await this.Connection.DisconnectAsync().ConfigureAwait(false); } sequence.CopyTo(this._receiveBuffer); await this._packetHandler .HandlePacketAsync(this, this._receiveBuffer.AsMemory(0, this._receiveBuffer.GetPacketSize())) .ConfigureAwait(false); } }