﻿// Decompiled with JetBrains decompiler
// Type: InvokedCommon.Networking.Client
// Assembly: InvokedCommon, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
// MVID: A935EE8A-7FFF-4BAB-94FD-31AEC4036FB9
// Assembly location: C:\Users\ian\Downloads\Telegram Desktop\fein\InvokedCommon.dll

using InvokedCommon.Extensions;
using InvokedCommon.Messages;
using InvokedCommon.Messages.ReverseProxy;
using InvokedCommon.ReverseProxy;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Threading;


namespace InvokedCommon.Networking
{
	public class Client : ISender
	{
		private SslStream _stream;
		private readonly X509Certificate2 _serverCertificate;
		private List<ReverseProxyClient> _proxyClients = new List<ReverseProxyClient>();
		private int _typeIndex;
		private readonly object _proxyClientsLock = new object();
		private byte[] _readBuffer;
		private byte[] _payloadBuffer;
		private readonly Queue<IMessage> _sendBuffers = new Queue<IMessage>();
		private bool _sendingMessages;
		private readonly object _sendingMessagesLock = new object();
		private readonly Queue<byte[]> _readBuffers = new Queue<byte[]>();
		private bool _readingMessages;
		private readonly object _readingMessagesLock = new object();
		private int _readOffset;
		private int _writeOffset;
		private int _readableDataLen;
		private int _payloadLen;
		private Client.ReceiveType _receiveState;
		private readonly Mutex _singleWriteMutex = new Mutex();

		public event Client.ClientFailEventHandler ClientFail;

		private void OnClientFail(Exception ex)
		{
			Client.ClientFailEventHandler clientFail = this.ClientFail;
			if (clientFail == null)
				return;
			clientFail(this, ex);
		}

		public event Client.ClientStateEventHandler ClientState;

		private void OnClientState(bool connected)
		{
			if (this.Connected == connected)
				return;
			this.Connected = connected;
			Client.ClientStateEventHandler clientState = this.ClientState;
			if (clientState == null)
				return;
			clientState(this, connected);
		}

		public event Client.ClientReadEventHandler ClientRead;

		private void OnClientRead(IMessage message, int messageLength)
		{
			Client.ClientReadEventHandler clientRead = this.ClientRead;
			if (clientRead == null)
				return;
			clientRead(this, message, messageLength);
		}

		public event Client.ClientWriteEventHandler ClientWrite;

		private void OnClientWrite(IMessage message, int messageLength)
		{
			Client.ClientWriteEventHandler clientWrite = this.ClientWrite;
			if (clientWrite == null)
				return;
			clientWrite(this, message, messageLength);
		}

		public int BUFFER_SIZE => 16384;

		public uint KEEP_ALIVE_TIME => 25000;

		public uint KEEP_ALIVE_INTERVAL => 25000;

		public int HEADER_SIZE => 4;

		public int MAX_MESSAGE_SIZE => 5242880;

		public ReverseProxyClient[] ProxyClients
		{
			get
			{
				lock (this._proxyClientsLock)
					return this._proxyClients.ToArray();
			}
		}

		public bool Connected { get; private set; }

		protected Client(X509Certificate2 serverCertificate)
		{
			this._serverCertificate = serverCertificate;
			this._readBuffer = new byte[this.BUFFER_SIZE];
			TypeRegistry.AddTypesToSerializer(typeof(IMessage), TypeRegistry.GetPacketTypes(typeof(IMessage)).ToArray<Type>());
		}

		protected void Connect(IPAddress ip, ushort port)
		{
			Socket socket = (Socket)null;
			try
			{
				this.Disconnect();
				socket = new Socket(ip.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
				socket.SetKeepAliveEx(this.KEEP_ALIVE_INTERVAL, this.KEEP_ALIVE_TIME);
				socket.Connect(ip, (int)port);
				if (socket.Connected)
				{
					this._stream = new SslStream((Stream)new NetworkStream(socket, true), false, new RemoteCertificateValidationCallback(this.ValidateServerCertificate));
					this._stream.AuthenticateAsClient(ip.ToString(), (X509CertificateCollection)null, SslProtocols.Tls12, false);
					this._stream.BeginRead(this._readBuffer, 0, this._readBuffer.Length, new AsyncCallback(this.AsyncReceive), (object)null);
					this.OnClientState(true);
				}
				else
					socket.Dispose();
			}
			catch (Exception ex)
			{
				socket?.Dispose();
				this.OnClientFail(ex);
			}
		}

		private bool ValidateServerCertificate(
		    object sender,
		    X509Certificate certificate,
		    X509Chain chain,
		    SslPolicyErrors sslPolicyErrors)
		{
			RSACryptoServiceProvider key1 = (RSACryptoServiceProvider)this._serverCertificate.PublicKey.Key;
			RSACryptoServiceProvider key2 = (RSACryptoServiceProvider)new X509Certificate2(certificate).PublicKey.Key;
			return this._serverCertificate.Equals(certificate);
		}

		private void AsyncReceive(IAsyncResult result)
		{
			int length;
			try
			{
				length = this._stream.EndRead(result);
				if (length <= 0)
					throw new Exception("no bytes transferred");
			}
			catch (NullReferenceException ex)
			{
				return;
			}
			catch (ObjectDisposedException ex)
			{
				return;
			}
			catch (Exception ex)
			{
				this.Disconnect();
				return;
			}
			byte[] destinationArray = new byte[length];
			try
			{
				Array.Copy((Array)this._readBuffer, (Array)destinationArray, destinationArray.Length);
			}
			catch (Exception ex)
			{
				this.OnClientFail(ex);
				return;
			}
			lock (this._readBuffers)
				this._readBuffers.Enqueue(destinationArray);
			lock (this._readingMessagesLock)
			{
				if (!this._readingMessages)
				{
					this._readingMessages = true;
					ThreadPool.QueueUserWorkItem(new WaitCallback(this.AsyncReceive));
				}
			}
			try
			{
				this._stream.BeginRead(this._readBuffer, 0, this._readBuffer.Length, new AsyncCallback(this.AsyncReceive), (object)null);
			}
			catch (ObjectDisposedException ex)
			{
			}
			catch (Exception ex)
			{
				this.OnClientFail(ex);
			}
		}

		private void AsyncReceive(object state)
		{
			while (true)
			{
				byte[] sourceArray;
				lock (this._readBuffers)
				{
					if (this._readBuffers.Count == 0)
					{
						lock (this._readingMessagesLock)
						{
							this._readingMessages = false;
							break;
						}
					}
					else
						sourceArray = this._readBuffers.Dequeue();
				}
				this._readableDataLen += sourceArray.Length;
				bool flag = true;
				while (flag)
				{
					switch (this._receiveState)
					{
						case Client.ReceiveType.Header:
							if (this._payloadBuffer == null)
								this._payloadBuffer = new byte[this.HEADER_SIZE];
							if (this._readableDataLen + this._writeOffset >= this.HEADER_SIZE)
							{
								int length = this.HEADER_SIZE - this._writeOffset;
								try
								{
									Array.Copy((Array)sourceArray, this._readOffset, (Array)this._payloadBuffer, this._writeOffset, length);
									this._payloadLen = BitConverter.ToInt32(this._payloadBuffer, this._readOffset);
									if (this._payloadLen <= 0 || this._payloadLen > this.MAX_MESSAGE_SIZE)
										throw new Exception("invalid header");
									if (this._payloadBuffer.Length <= this._payloadLen + this.HEADER_SIZE)
										Array.Resize<byte>(ref this._payloadBuffer, this._payloadLen + this.HEADER_SIZE);
								}
								catch (Exception ex)
								{
									flag = false;
									this.Disconnect();
									continue;
								}
								this._readableDataLen -= length;
								this._writeOffset += length;
								this._readOffset += length;
								this._receiveState = Client.ReceiveType.Payload;
								continue;
							}
							try
							{
								Array.Copy((Array)sourceArray, this._readOffset, (Array)this._payloadBuffer, this._writeOffset, this._readableDataLen);
							}
							catch (Exception ex)
							{
								flag = false;
								this.Disconnect();
								continue;
							}
							this._readOffset += this._readableDataLen;
							this._writeOffset += this._readableDataLen;
							flag = false;
							continue;
						case Client.ReceiveType.Payload:
							int length1 = this._writeOffset - this.HEADER_SIZE + this._readableDataLen >= this._payloadLen ? this._payloadLen - (this._writeOffset - this.HEADER_SIZE) : this._readableDataLen;
							try
							{
								Array.Copy((Array)sourceArray, this._readOffset, (Array)this._payloadBuffer, this._writeOffset, length1);
							}
							catch (Exception ex)
							{
								flag = false;
								this.Disconnect();
								continue;
							}
							this._writeOffset += length1;
							this._readOffset += length1;
							this._readableDataLen -= length1;
							if (this._writeOffset - this.HEADER_SIZE == this._payloadLen)
							{
								try
								{
									using (PayloadReader payloadReader = new PayloadReader(this._payloadBuffer, this._payloadLen + this.HEADER_SIZE, false))
										this.OnClientRead(payloadReader.ReadMessage(), this._payloadBuffer.Length);
								}
								catch (Exception ex)
								{
									flag = false;
									this.Disconnect();
									continue;
								}
								this._receiveState = Client.ReceiveType.Header;
								this._payloadLen = 0;
								this._writeOffset = 0;
							}
							if (this._readableDataLen == 0)
							{
								flag = false;
								continue;
							}
							continue;
						default:
							continue;
					}
				}
				this._readOffset = 0;
				this._readableDataLen = 0;
			}
		}

		public void Send<T>(T message) where T : IMessage
		{
			if (!this.Connected || (object)message == null)
				return;
			lock (this._sendBuffers)
			{
				this._sendBuffers.Enqueue((IMessage)message);
				lock (this._sendingMessagesLock)
				{
					if (this._sendingMessages)
						return;
					this._sendingMessages = true;
					ThreadPool.QueueUserWorkItem(new WaitCallback(this.ProcessSendBuffers));
				}
			}
		}

		public void SendBlocking<T>(T message) where T : IMessage
		{
			if (!this.Connected || (object)message == null)
				return;
			this.SafeSendMessage((IMessage)message);
		}

		private void SafeSendMessage(IMessage message)
		{
			try
			{
				this._singleWriteMutex.WaitOne();
				using (PayloadWriter payloadWriter = new PayloadWriter((Stream)this._stream, true))
					this.OnClientWrite(message, payloadWriter.WriteMessage(message));
			}
			catch (Exception ex)
			{
				this.Disconnect();
				this.SendCleanup(true);
			}
			finally
			{
				this._singleWriteMutex.ReleaseMutex();
			}
		}

		private void ProcessSendBuffers(object state)
		{
			while (this.Connected)
			{
				IMessage message;
				lock (this._sendBuffers)
				{
					if (this._sendBuffers.Count == 0)
					{
						this.SendCleanup();
						return;
					}
					message = this._sendBuffers.Dequeue();
				}
				this.SafeSendMessage(message);
			}
			this.SendCleanup(true);
		}

		private void SendCleanup(bool clear = false)
		{
			lock (this._sendingMessagesLock)
				this._sendingMessages = false;
			if (!clear)
				return;
			lock (this._sendBuffers)
				this._sendBuffers.Clear();
		}

		public void Disconnect()
		{
			if (this._stream != null)
			{
				this._stream.Close();
				this._readOffset = 0;
				this._writeOffset = 0;
				this._readableDataLen = 0;
				this._payloadLen = 0;
				this._payloadBuffer = (byte[])null;
				this._receiveState = Client.ReceiveType.Header;
				if (this._proxyClients != null)
				{
					lock (this._proxyClientsLock)
					{
						try
						{
							foreach (ReverseProxyClient proxyClient in this._proxyClients)
								proxyClient.Disconnect();
						}
						catch (Exception ex)
						{
						}
					}
				}
			}
			this.OnClientState(false);
		}

		public void ConnectReverseProxy(ReverseProxyConnect command)
		{
			lock (this._proxyClientsLock)
				this._proxyClients.Add(new ReverseProxyClient(command, this));
		}

		public ReverseProxyClient GetReverseProxyByConnectionId(int connectionId)
		{
			lock (this._proxyClientsLock)
				return this._proxyClients.FirstOrDefault<ReverseProxyClient>((Func<ReverseProxyClient, bool>)(t => t.ConnectionId == connectionId));
		}

		public void RemoveProxyClient(int connectionId)
		{
			try
			{
				lock (this._proxyClientsLock)
				{
					for (int index = 0; index < this._proxyClients.Count; ++index)
					{
						if (this._proxyClients[index].ConnectionId == connectionId)
						{
							this._proxyClients.RemoveAt(index);
							break;
						}
					}
				}
			}
			catch
			{
			}
		}

		public delegate void ClientFailEventHandler(Client s, Exception ex);

		public delegate void ClientStateEventHandler(Client s, bool connected);

		public delegate void ClientReadEventHandler(Client s, IMessage message, int messageLength);

		public delegate void ClientWriteEventHandler(Client s, IMessage message, int messageLength);

		public enum ReceiveType
		{
			Header,
			Payload,
		}
	}
}