using Org.Mentalis.Network.ProxySocket.Authentication;
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace Org.Mentalis.Network.ProxySocket
{
	internal sealed class Socks5Handler : SocksHandler
	{
		private string m_Password;

		private byte[] m_HandShake;

		private string Password
		{
			get
			{
				return this.m_Password;
			}
			set
			{
				if (value == null)
				{
					throw new ArgumentNullException();
				}
				this.m_Password = value;
			}
		}

		private byte[] HandShake
		{
			get
			{
				return this.m_HandShake;
			}
			set
			{
				this.m_HandShake = value;
			}
		}

		public Socks5Handler(Socket server) : this(server, "")
		{
		}

		public Socks5Handler(Socket server, string user) : this(server, user, "")
		{
		}

		public Socks5Handler(Socket server, string user, string pass) : base(server, user)
		{
			this.Password = pass;
		}

		private void Authenticate()
		{
			base.Server.Send(new byte[]
			{
				5,
				2,
				0,
				2
			});
			byte[] array = base.ReadBytes(2);
			if (array[1] == 255)
			{
				throw new ProxyException("No authentication method accepted.");
			}
			AuthMethod authMethod;
			switch (array[1])
			{
			case 0:
				authMethod = new AuthNone(base.Server);
				goto IL_94;
			case 2:
				authMethod = new AuthUserPass(base.Server, base.Username, this.Password);
				goto IL_94;
			}
			throw new ProtocolViolationException();
			IL_94:
			authMethod.Authenticate();
		}

		private byte[] GetHostPortBytes(string host, int port)
		{
			if (host == null)
			{
				throw new ArgumentNullException();
			}
			if (port <= 0 || port > 65535 || host.Length > 255)
			{
				throw new ArgumentException();
			}
			byte[] array = new byte[7 + host.Length];
			array[0] = 5;
			array[1] = 1;
			array[2] = 0;
			array[3] = 3;
			array[4] = (byte)host.Length;
			Array.Copy(Encoding.ASCII.GetBytes(host), 0, array, 5, host.Length);
			Array.Copy(base.PortToBytes(port), 0, array, host.Length + 5, 2);
			return array;
		}

		private byte[] GetEndPointBytes(IPEndPoint remoteEP)
		{
			if (remoteEP == null)
			{
				throw new ArgumentNullException();
			}
			byte[] array = new byte[10];
			array[0] = 5;
			array[1] = 1;
			array[2] = 0;
			array[3] = 1;
			Array.Copy(base.AddressToBytes(remoteEP.Address.Address), 0, array, 4, 4);
			Array.Copy(base.PortToBytes(remoteEP.Port), 0, array, 8, 2);
			return array;
		}

		public override void Negotiate(string host, int port)
		{
			this.Negotiate(this.GetHostPortBytes(host, port));
		}

		public override void Negotiate(IPEndPoint remoteEP)
		{
			this.Negotiate(this.GetEndPointBytes(remoteEP));
		}

		private void Negotiate(byte[] connect)
		{
			this.Authenticate();
			base.Server.Send(connect);
			byte[] array = base.ReadBytes(4);
			if (array[1] != 0)
			{
				base.Server.Close();
				throw new ProxyException((int)array[1]);
			}
			switch (array[3])
			{
			case 1:
				array = base.ReadBytes(6);
				return;
			case 3:
				array = base.ReadBytes(1);
				array = base.ReadBytes((int)(array[0] + 2));
				return;
			case 4:
				array = base.ReadBytes(18);
				return;
			}
			base.Server.Close();
			throw new ProtocolViolationException();
		}

		public override IAsyncProxyResult BeginNegotiate(string host, int port, HandShakeComplete callback, IPEndPoint proxyEndPoint)
		{
			this.ProtocolComplete = callback;
			this.HandShake = this.GetHostPortBytes(host, port);
			base.Server.BeginConnect(proxyEndPoint, new AsyncCallback(this.OnConnect), base.Server);
			base.AsyncResult = new IAsyncProxyResult();
			return base.AsyncResult;
		}

		public override IAsyncProxyResult BeginNegotiate(IPEndPoint remoteEP, HandShakeComplete callback, IPEndPoint proxyEndPoint)
		{
			this.ProtocolComplete = callback;
			this.HandShake = this.GetEndPointBytes(remoteEP);
			base.Server.BeginConnect(proxyEndPoint, new AsyncCallback(this.OnConnect), base.Server);
			base.AsyncResult = new IAsyncProxyResult();
			return base.AsyncResult;
		}

		private void OnConnect(IAsyncResult ar)
		{
			try
			{
				base.Server.EndConnect(ar);
			}
			catch (Exception error)
			{
				this.ProtocolComplete(error);
				return;
			}
			try
			{
				base.Server.BeginSend(new byte[]
				{
					5,
					2,
					0,
					2
				}, 0, 4, SocketFlags.None, new AsyncCallback(this.OnAuthSent), base.Server);
			}
			catch (Exception error)
			{
				this.ProtocolComplete(error);
			}
		}

		private void OnAuthSent(IAsyncResult ar)
		{
			try
			{
				base.Server.EndSend(ar);
			}
			catch (Exception error)
			{
				this.ProtocolComplete(error);
				return;
			}
			try
			{
				base.Buffer = new byte[1024];
				base.Received = 0;
				base.Server.BeginReceive(base.Buffer, 0, base.Buffer.Length, SocketFlags.None, new AsyncCallback(this.OnAuthReceive), base.Server);
			}
			catch (Exception error)
			{
				this.ProtocolComplete(error);
			}
		}

		private void OnAuthReceive(IAsyncResult ar)
		{
			try
			{
				base.Received += base.Server.EndReceive(ar);
				if (base.Received <= 0)
				{
					throw new SocketException();
				}
			}
			catch (Exception error)
			{
				this.ProtocolComplete(error);
				return;
			}
			try
			{
				if (base.Received < 2)
				{
					base.Server.BeginReceive(base.Buffer, base.Received, base.Buffer.Length - base.Received, SocketFlags.None, new AsyncCallback(this.OnAuthReceive), base.Server);
				}
				else
				{
					AuthMethod authMethod;
					switch (base.Buffer[1])
					{
					case 0:
						authMethod = new AuthNone(base.Server);
						goto IL_107;
					case 2:
						authMethod = new AuthUserPass(base.Server, base.Username, this.Password);
						goto IL_107;
					}
					this.ProtocolComplete(new SocketException());
					return;
					IL_107:
					authMethod.BeginAuthenticate(new HandShakeComplete(this.OnAuthenticated));
				}
			}
			catch (Exception error)
			{
				this.ProtocolComplete(error);
			}
		}

		private void OnAuthenticated(Exception e)
		{
			if (e != null)
			{
				this.ProtocolComplete(e);
			}
			else
			{
				try
				{
					base.Server.BeginSend(this.HandShake, 0, this.HandShake.Length, SocketFlags.None, new AsyncCallback(this.OnSent), base.Server);
				}
				catch (Exception error)
				{
					this.ProtocolComplete(error);
				}
			}
		}

		private void OnSent(IAsyncResult ar)
		{
			try
			{
				base.Server.EndSend(ar);
			}
			catch (Exception error)
			{
				this.ProtocolComplete(error);
				return;
			}
			try
			{
				base.Buffer = new byte[5];
				base.Received = 0;
				base.Server.BeginReceive(base.Buffer, 0, base.Buffer.Length, SocketFlags.None, new AsyncCallback(this.OnReceive), base.Server);
			}
			catch (Exception error)
			{
				this.ProtocolComplete(error);
			}
		}

		private void OnReceive(IAsyncResult ar)
		{
			try
			{
				base.Received += base.Server.EndReceive(ar);
			}
			catch (Exception error)
			{
				this.ProtocolComplete(error);
				return;
			}
			try
			{
				if (base.Received == base.Buffer.Length)
				{
					this.ProcessReply(base.Buffer);
				}
				else
				{
					base.Server.BeginReceive(base.Buffer, base.Received, base.Buffer.Length - base.Received, SocketFlags.None, new AsyncCallback(this.OnReceive), base.Server);
				}
			}
			catch (Exception error)
			{
				this.ProtocolComplete(error);
			}
		}

		private void ProcessReply(byte[] buffer)
		{
			switch (buffer[3])
			{
			case 1:
				base.Buffer = new byte[5];
				goto IL_5E;
			case 3:
				base.Buffer = new byte[(int)(buffer[4] + 2)];
				goto IL_5E;
			case 4:
				buffer = new byte[17];
				goto IL_5E;
			}
			throw new ProtocolViolationException();
			IL_5E:
			base.Received = 0;
			base.Server.BeginReceive(base.Buffer, 0, base.Buffer.Length, SocketFlags.None, new AsyncCallback(this.OnReadLast), base.Server);
		}

		private void OnReadLast(IAsyncResult ar)
		{
			try
			{
				base.Received += base.Server.EndReceive(ar);
			}
			catch (Exception error)
			{
				this.ProtocolComplete(error);
				return;
			}
			try
			{
				if (base.Received == base.Buffer.Length)
				{
					this.ProtocolComplete(null);
				}
				else
				{
					base.Server.BeginReceive(base.Buffer, base.Received, base.Buffer.Length - base.Received, SocketFlags.None, new AsyncCallback(this.OnReadLast), base.Server);
				}
			}
			catch (Exception error)
			{
				this.ProtocolComplete(error);
			}
		}
	}
}
