using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using Renci.SshNet.Channels;
using Renci.SshNet.Common;
using Renci.SshNet.Compression;
using Renci.SshNet.Messages;
using Renci.SshNet.Messages.Authentication;
using Renci.SshNet.Messages.Connection;
using Renci.SshNet.Messages.Transport;
using Renci.SshNet.Security;
using System.Globalization;
using Renci.SshNet.Security.Cryptography.Ciphers;
using Renci.SshNet.Security.Cryptography;
namespace Renci.SshNet
{
    /// 
    /// Provides functionality to connect and interact with SSH server.
    /// 
    public partial class Session : IDisposable
    {
        /// 
        /// Specifies maximum packet size defined by the protocol.
        /// 
        protected const int MAXIMUM_PACKET_SIZE = 35000;
        /// 
        /// Specifies maximum payload size defined by the protocol.
        /// 
        protected const int MAXIMUM_PAYLOAD_SIZE = 1024 * 32;
        private static RNGCryptoServiceProvider _randomizer = new System.Security.Cryptography.RNGCryptoServiceProvider();
#if SILVERLIGHT
        private static Regex _serverVersionRe = new Regex("^SSH-(?[^-]+)-(?.+)( SP.+)?$");
#else
        private static Regex _serverVersionRe = new Regex("^SSH-(?[^-]+)-(?.+)( SP.+)?$", RegexOptions.Compiled);
#endif
        /// 
        /// Controls how many authentication attempts can take place at the same time.
        /// 
        /// 
        /// Some server may restrict number to prevent authentication attacks
        /// 
        private static SemaphoreLight _authenticationConnection = new SemaphoreLight(3);
        /// 
        /// Holds metada about session messages
        /// 
        private IEnumerable _messagesMetadata;
        /// 
        /// Holds connection socket.
        /// 
        private Socket _socket;
        /// 
        /// Holds reference to task that listens for incoming messages
        /// 
        private EventWaitHandle _messageListenerCompleted;
        /// 
        /// Specifies outbound packet number
        /// 
        private volatile UInt32 _outboundPacketSequence = 0;
        /// 
        /// Specifies incoming packet number
        /// 
        private UInt32 _inboundPacketSequence = 0;
        /// 
        /// WaitHandle to signal that last service request was accepted
        /// 
        private EventWaitHandle _serviceAccepted = new AutoResetEvent(false);
        /// 
        /// WaitHandle to signal that exception was thrown by another thread.
        /// 
        private EventWaitHandle _exceptionWaitHandle = new AutoResetEvent(false);
        /// 
        /// WaitHandle to signal that key exchange was completed.
        /// 
        private EventWaitHandle _keyExchangeCompletedWaitHandle = new ManualResetEvent(false);
        /// 
        /// Exception that need to be thrown by waiting thread
        /// 
        private Exception _exception;
        /// 
        /// Specifies whether connection is authenticated
        /// 
        private bool _isAuthenticated;
        /// 
        /// Specifies whether user issued Disconnect command or not
        /// 
        private bool _isDisconnecting;
        private KeyExchange _keyExchange;
        private HashAlgorithm _serverMac;
        private HashAlgorithm _clientMac;
        private BlockCipher _clientCipher;
        private BlockCipher _serverCipher;
        private Compressor _serverDecompression;
        private Compressor _clientCompression;
        private SemaphoreLight _sessionSemaphore;
        /// 
        /// Gets the session semaphore that controls session channels.
        /// 
        /// The session semaphore.
        public SemaphoreLight SessionSemaphore
        {
            get
            {
                if (this._sessionSemaphore == null)
                {
                    lock (this)
                    {
                        if (this._sessionSemaphore == null)
                        {
                            this._sessionSemaphore = new SemaphoreLight(this.ConnectionInfo.MaxSessions);
                        }
                    }
                }
                return this._sessionSemaphore;
            }
        }
        private bool _isDisconnectMessageSent;
        private uint _nextChannelNumber;
        /// 
        /// Gets the next channel number.
        /// 
        /// The next channel number.
        internal uint NextChannelNumber
        {
            get
            {
                uint result;
                lock (this)
                {
                    result = this._nextChannelNumber++;
                }
                return result;
            }
        }
        /// 
        /// Gets a value indicating whether socket connected.
        /// 
        /// 
        /// 	true if socket connected; otherwise, false.
        /// 
        public bool IsConnected
        {
            get
            {
                return this._socket != null && this._socket.Connected && this._isAuthenticated && this._messageListenerCompleted != null;
            }
        }
        /// 
        /// Gets or sets the session id.
        /// 
        /// The session id.
        public byte[] SessionId { get; private set; }
        private Message _clientInitMessage;
        /// 
        /// Gets the client init message.
        /// 
        /// The client init message.
        public Message ClientInitMessage
        {
            get
            {
                if (this._clientInitMessage == null)
                {
                    this._clientInitMessage = new KeyExchangeInitMessage()
                    {
                        KeyExchangeAlgorithms = this.ConnectionInfo.KeyExchangeAlgorithms.Keys.ToArray(),
                        ServerHostKeyAlgorithms = this.ConnectionInfo.HostKeyAlgorithms.Keys.ToArray(),
                        EncryptionAlgorithmsClientToServer = this.ConnectionInfo.Encryptions.Keys.ToArray(),
                        EncryptionAlgorithmsServerToClient = this.ConnectionInfo.Encryptions.Keys.ToArray(),
                        MacAlgorithmsClientToServer = this.ConnectionInfo.HmacAlgorithms.Keys.ToArray(),
                        MacAlgorithmsServerToClient = this.ConnectionInfo.HmacAlgorithms.Keys.ToArray(),
                        CompressionAlgorithmsClientToServer = this.ConnectionInfo.CompressionAlgorithms.Keys.ToArray(),
                        CompressionAlgorithmsServerToClient = this.ConnectionInfo.CompressionAlgorithms.Keys.ToArray(),
                        LanguagesClientToServer = new string[] { string.Empty },
                        LanguagesServerToClient = new string[] { string.Empty },
                        FirstKexPacketFollows = false,
                        Reserved = 0,
                    };
                }
                return this._clientInitMessage;
            }
        }
        /// 
        /// Gets or sets the server version string.
        /// 
        /// The server version.
        public string ServerVersion { get; private set; }
        /// 
        /// Gets or sets the client version string.
        /// 
        /// The client version.
        public string ClientVersion { get; private set; }
        /// 
        /// Gets or sets the connection info.
        /// 
        /// The connection info.
        public ConnectionInfo ConnectionInfo { get; private set; }
        /// 
        /// Occurs when an error occurred.
        /// 
        public event EventHandler ErrorOccured;
        /// 
        /// Occurs when session has been disconnected form the server.
        /// 
        public event EventHandler Disconnected;
        #region Message events
        /// 
        /// Occurs when  message received
        /// 
        internal event EventHandler> DisconnectReceived;
        /// 
        /// Occurs when  message received
        /// 
        internal event EventHandler> IgnoreReceived;
        /// 
        /// Occurs when  message received
        /// 
        internal event EventHandler> UnimplementedReceived;
        /// 
        /// Occurs when  message received
        /// 
        internal event EventHandler> DebugReceived;
        /// 
        /// Occurs when  message received
        /// 
        internal event EventHandler> ServiceRequestReceived;
        /// 
        /// Occurs when  message received
        /// 
        internal event EventHandler> ServiceAcceptReceived;
        /// 
        /// Occurs when  message received
        /// 
        internal event EventHandler> KeyExchangeInitReceived;
        /// 
        /// Occurs when  message received
        /// 
        internal event EventHandler> NewKeysReceived;
        /// 
        /// Occurs when  message received
        /// 
        internal event EventHandler> UserAuthenticationRequestReceived;
        /// 
        /// Occurs when  message received
        /// 
        internal event EventHandler> UserAuthenticationFailureReceived;
        /// 
        /// Occurs when  message received
        /// 
        internal event EventHandler> UserAuthenticationSuccessReceived;
        /// 
        /// Occurs when  message received
        /// 
        internal event EventHandler> UserAuthenticationBannerReceived;
        /// 
        /// Occurs when  message received
        ///         
        internal event EventHandler> GlobalRequestReceived;
        /// 
        /// Occurs when  message received
        /// 
        internal event EventHandler> RequestSuccessReceived;
        /// 
        /// Occurs when  message received
        /// 
        internal event EventHandler> RequestFailureReceived;
        /// 
        /// Occurs when  message received
        /// 
        internal event EventHandler> ChannelOpenReceived;
        /// 
        /// Occurs when  message received
        /// 
        internal event EventHandler> ChannelOpenConfirmationReceived;
        /// 
        /// Occurs when  message received
        /// 
        internal event EventHandler> ChannelOpenFailureReceived;
        /// 
        /// Occurs when  message received
        /// 
        internal event EventHandler> ChannelWindowAdjustReceived;
        /// 
        /// Occurs when  message received
        /// 
        internal event EventHandler> ChannelDataReceived;
        /// 
        /// Occurs when  message received
        /// 
        internal event EventHandler> ChannelExtendedDataReceived;
        /// 
        /// Occurs when  message received
        /// 
        internal event EventHandler> ChannelEofReceived;
        /// 
        /// Occurs when  message received
        /// 
        internal event EventHandler> ChannelCloseReceived;
        /// 
        /// Occurs when  message received
        /// 
        internal event EventHandler> ChannelRequestReceived;
        /// 
        /// Occurs when  message received
        /// 
        internal event EventHandler> ChannelSuccessReceived;
        /// 
        /// Occurs when  message received
        /// 
        internal event EventHandler> ChannelFailureReceived;
        /// 
        /// Occurs when message received and is not handled by any of the event handlers
        /// 
        internal event EventHandler> MessageReceived;
        #endregion
        /// 
        /// Initializes a new instance of the  class.
        /// 
        /// The connection info.
        internal Session(ConnectionInfo connectionInfo)
        {
            this.ConnectionInfo = connectionInfo;
            this.ClientVersion = string.Format(CultureInfo.CurrentCulture, "SSH-2.0-Renci.SshNet.SshClient.{0}", this.GetType().Assembly.GetName().Version);
        }
        /// 
        /// Connects to the server.
        /// 
        public void Connect()
        {
            if (this.ConnectionInfo == null)
            {
                throw new ArgumentNullException("connectionInfo");
            }
            if (this.IsConnected)
                return;
            try
            {
                _authenticationConnection.Wait();
                if (this.IsConnected)
                    return;
                lock (this)
                {
                    //  If connected don't connect again
                    if (this.IsConnected)
                        return;
                    this.OpenSocket();
                    Match versionMatch = null;
                    //  Get server version from the server,
                    //  ignore text lines which are sent before if any
                    using (var ns = new NetworkStream(this._socket))
                    {
                        using (var sr = new StreamReader(ns))
                        {
                            while (true)
                            {
                                this.ServerVersion = sr.ReadLine();
                                if (string.IsNullOrEmpty(this.ServerVersion))
                                {
                                    throw new InvalidOperationException("Server string is null or empty.");
                                }
                                versionMatch = _serverVersionRe.Match(this.ServerVersion);
                                if (versionMatch.Success)
                                {
                                    break;
                                }
                            }
                        }
                    }
                    //  Get server SSH version
                    var version = versionMatch.Result("${protoversion}");
                    if (!(version.Equals("2.0") || version.Equals("1.99")))
                    {
                        throw new SshConnectionException(string.Format(CultureInfo.CurrentCulture, "Server version '{0}' is not supported.", version), DisconnectReason.ProtocolVersionNotSupported);
                    }
                    this.Write(Renci.SshNet.Common.ASCIIEncoding.Current.GetBytes(string.Format(CultureInfo.InvariantCulture, "{0}\x0D\x0A", this.ClientVersion)));
                    //  Register Transport response messages
                    this.RegisterMessage("SSH_MSG_DISCONNECT");
                    this.RegisterMessage("SSH_MSG_IGNORE");
                    this.RegisterMessage("SSH_MSG_UNIMPLEMENTED");
                    this.RegisterMessage("SSH_MSG_DEBUG");
                    this.RegisterMessage("SSH_MSG_SERVICE_ACCEPT");
                    this.RegisterMessage("SSH_MSG_KEXINIT");
                    this.RegisterMessage("SSH_MSG_NEWKEYS");
                    //  Some server implementations might sent this message first, prior establishing encryption algorithm
                    this.RegisterMessage("SSH_MSG_USERAUTH_BANNER");
                    //  Start incoming request listener
                    this._messageListenerCompleted = new ManualResetEvent(false);
                    this.ExecuteThread(() =>
                    {
                        try
                        {
                            this.MessageListener();
                        }
                        finally
                        {
                            this._messageListenerCompleted.Set();
                        }
                    });
                    //  Wait for key exchange to be completed
                    this.WaitHandle(this._keyExchangeCompletedWaitHandle);
                    //  If sessionId is not set then its not connected
                    if (this.SessionId == null)
                    {
                        this.Disconnect();
                        return;
                    }
                    //  Request user authorization service
                    this.SendMessage(new ServiceRequestMessage(ServiceName.UserAuthentication));
                    //  Wait for service to be accepted
                    this.WaitHandle(this._serviceAccepted);
                    if (string.IsNullOrEmpty(this.ConnectionInfo.Username))
                    {
                        throw new SshException("Username is not specified.");
                    }
                    //  Try authenticate using none method
                    using (var noneConnectionInfo = new NoneConnectionInfo(this.ConnectionInfo.Host, this.ConnectionInfo.Port, this.ConnectionInfo.Username))
                    {
                        noneConnectionInfo.Authenticate(this);
                        this._isAuthenticated = noneConnectionInfo.IsAuthenticated;
                        if (!this._isAuthenticated)
                        {
                            //  Ensure that authentication method is allowed
                            if (!noneConnectionInfo.AllowedAuthentications.Contains(this.ConnectionInfo.Name))
                            {
                                throw new SshAuthenticationException("User authentication method is not supported.");
                            }
                            //  In future, if more then one authentication methods are supported perform the check here.
                            //  Authenticate using provided connection info object
                            this.ConnectionInfo.Authenticate(this);
                            this._isAuthenticated = this.ConnectionInfo.IsAuthenticated;
                        }
                    }
                    if (!this._isAuthenticated)
                    {
                        throw new SshAuthenticationException("User cannot be authenticated.");
                    }
                    //  Register Connection messages
                    this.RegisterMessage("SSH_MSG_GLOBAL_REQUEST");
                    this.RegisterMessage("SSH_MSG_REQUEST_SUCCESS");
                    this.RegisterMessage("SSH_MSG_REQUEST_FAILURE");
                    this.RegisterMessage("SSH_MSG_CHANNEL_OPEN_CONFIRMATION");
                    this.RegisterMessage("SSH_MSG_CHANNEL_OPEN_FAILURE");
                    this.RegisterMessage("SSH_MSG_CHANNEL_WINDOW_ADJUST");
                    this.RegisterMessage("SSH_MSG_CHANNEL_EXTENDED_DATA");
                    this.RegisterMessage("SSH_MSG_CHANNEL_REQUEST");
                    this.RegisterMessage("SSH_MSG_CHANNEL_SUCCESS");
                    this.RegisterMessage("SSH_MSG_CHANNEL_FAILURE");
                    this.RegisterMessage("SSH_MSG_CHANNEL_DATA");
                    this.RegisterMessage("SSH_MSG_CHANNEL_EOF");
                    this.RegisterMessage("SSH_MSG_CHANNEL_CLOSE");
                    Monitor.Pulse(this);
                }
            }
            finally
            {
                _authenticationConnection.Release();
            }
        }
        /// 
        /// Disconnects from the server
        /// 
        public void Disconnect()
        {
            this.Dispose();
        }
        internal T CreateChannel() where T : Channel, new()
        {
            return CreateChannel(0, 0x100000, 0x8000);
        }
        internal T CreateChannel(uint serverChannelNumber, uint windowSize, uint packetSize) where T : Channel, new()
        {
            T channel = new T();
            lock (this)
            {
                channel.Initialize(this, serverChannelNumber, windowSize, packetSize);
            }
            return channel;
        }
        /// 
        /// Sends "keep alive" message to keep connection alive.
        /// 
        internal void SendKeepAlive()
        {
            this.SendMessage(new IgnoreMessage());
        }
        /// 
        /// Waits for handle to signal while checking other handles as well including timeout check to prevent waiting for ever
        /// 
        /// The wait handle.
        internal void WaitHandle(WaitHandle waitHandle)
        {
            var waitHandles = new WaitHandle[]
                {
                    this._exceptionWaitHandle,
                    waitHandle,
                };
            var index = EventWaitHandle.WaitAny(waitHandles, this.ConnectionInfo.Timeout);
            if (index < 1)
            {
                throw this._exception;
            }
            else if (index > 1)
            {
                this.SendDisconnect(DisconnectReason.ByApplication, "Operation timeout");
                throw new SshOperationTimeoutException("Session operation has timed out");
            }
        }
        /// 
        /// Sends packet message to the server.
        /// 
        /// The message.
        internal void SendMessage(Message message)
        {
            if (this._socket == null || !this._socket.Connected)
                return;
            //  Messages can be sent by different thread so we need to synchronize it            
            var paddingMultiplier = this._clientCipher == null ? (byte)8 : (byte)this._clientCipher.BlockSize;    //    Should be recalculate base on cipher min length if cipher specified
            var messageData = message.GetBytes();
            if (messageData.Length > Session.MAXIMUM_PAYLOAD_SIZE)
            {
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Payload cannot be more then {0} bytes.", Session.MAXIMUM_PAYLOAD_SIZE));
            }
            if (this._clientCompression != null)
            {
                messageData = this._clientCompression.Compress(messageData);
            }
            var packetLength = messageData.Length + 4 + 1; //  add length bytes and padding byte
            byte paddingLength = (byte)((-packetLength) & (paddingMultiplier - 1));
            if (paddingLength < paddingMultiplier)
            {
                paddingLength += paddingMultiplier;
            }
            //  Build Packet data
            var packetData = new byte[4 + 1 + messageData.Length + paddingLength];
            //  Add packet length
            ((uint)packetData.Length - 4).GetBytes().CopyTo(packetData, 0);
            //  Add packet padding length
            packetData[4] = paddingLength;
            //  Add packet payload
            messageData.CopyTo(packetData, 4 + 1);
            //  Add random padding
            var paddingBytes = new byte[paddingLength];
            _randomizer.GetBytes(paddingBytes);
            paddingBytes.CopyTo(packetData, 4 + 1 + messageData.Length);
            //  Lock handling of _outboundPacketSequence since it must be sent sequently to server
            lock (this._socket)
            {
                //  Calculate packet hash
                var hashData = new byte[4 + packetData.Length];
                this._outboundPacketSequence.GetBytes().CopyTo(hashData, 0);
                packetData.CopyTo(hashData, 4);
                //  Encrypt packet data
                if (this._clientCipher != null)
                {
                    packetData = this._clientCipher.Encrypt(packetData);
                }
                if (packetData.Length > Session.MAXIMUM_PACKET_SIZE)
                {
                    throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Packet is too big. Maximum packet size is {0} bytes.", Session.MAXIMUM_PACKET_SIZE));
                }
                if (this._clientMac == null)
                {
                    this.Write(packetData);
                }
                else
                {
                    var hash = this._clientMac.ComputeHash(hashData.ToArray());
                    var data = new byte[packetData.Length + this._clientMac.HashSize / 8];
                    packetData.CopyTo(data, 0);
                    hash.CopyTo(data, packetData.Length);
                    this.Write(data);
                }
                this._outboundPacketSequence++;
                Monitor.Pulse(this._socket);
            }
        }
        /// 
        /// Receives the message from the server.
        /// 
        /// 
        private Message ReceiveMessage()
        {
            if (!this._socket.Connected)
                return null;
            //  No lock needed since all messages read by only one thread
            var blockSize = this._serverCipher == null ? (byte)8 : (byte)this._serverCipher.BlockSize;
            //  Read packet length first
            var firstBlock = this.Read(blockSize);
            if (this._serverCipher != null)
            {
                firstBlock = this._serverCipher.Decrypt(firstBlock);
            }
            var packetLength = (uint)(firstBlock[0] << 24 | firstBlock[1] << 16 | firstBlock[2] << 8 | firstBlock[3]);
            //  Test packet minimum and maximum boundaries
            if (packetLength < Math.Max((byte)16, blockSize) - 4 || packetLength > Session.MAXIMUM_PACKET_SIZE - 4)
                throw new SshConnectionException(string.Format(CultureInfo.CurrentCulture, "Bad packet length {0}", packetLength), DisconnectReason.ProtocolError);
            //  Read rest of the packet data
            int bytesToRead = (int)(packetLength - (blockSize - 4));
            var data = new byte[bytesToRead + blockSize];
            firstBlock.CopyTo(data, 0);
            byte[] serverHash = null;
            if (this._serverMac != null)
            {
                serverHash = new byte[this._serverMac.HashSize / 8];
                bytesToRead += serverHash.Length;
            }
            if (bytesToRead > 0)
            {
                var nextBlocks = this.Read(bytesToRead);
                if (serverHash != null)
                {
                    Array.Copy(nextBlocks, nextBlocks.Length - serverHash.Length, serverHash, 0, serverHash.Length);
                    nextBlocks = nextBlocks.Take(nextBlocks.Length - serverHash.Length).ToArray();
                }
                if (nextBlocks.Length > 0)
                {
                    if (this._serverCipher != null)
                    {
                        nextBlocks = this._serverCipher.Decrypt(nextBlocks);
                    }
                    nextBlocks.CopyTo(data, blockSize);
                }
            }
            var paddingLength = data[4];
            var messagePayload = new byte[packetLength - paddingLength - 1];
            Array.Copy(data, 5, messagePayload, 0, messagePayload.Length);
            if (this._serverDecompression != null)
            {
                messagePayload = this._serverDecompression.Decompress(messagePayload);
            }
            //  Validate message against MAC            
            if (this._serverMac != null)
            {
                var clientHashData = new byte[4 + data.Length];
                var lengthBytes = this._inboundPacketSequence.GetBytes();
                lengthBytes.CopyTo(clientHashData, 0);
                data.CopyTo(clientHashData, 4);
                //  Calculate packet hash
                var clientHash = this._serverMac.ComputeHash(clientHashData);
                if (!serverHash.SequenceEqual(clientHash))
                {
                    throw new SshConnectionException("MAC error", DisconnectReason.MacError);
                }
            }
            this._inboundPacketSequence++;
            return this.LoadMessage(messagePayload);
        }
        private void SendDisconnect(DisconnectReason reasonCode, string message)
        {
            //  If disconnect message was sent already dont send it again
            if (this._isDisconnectMessageSent)
                return;
            var disconnectMessage = new DisconnectMessage(reasonCode, message);
            this.SendMessage(disconnectMessage);
            this._isDisconnectMessageSent = true;
        }
        /// 
        /// Handles the message.
        /// 
        /// 
        /// The message.
        private void HandleMessage(T message) where T : Message
        {
            this.OnMessageReceived(message);
        }
        #region Handle transport messages
        private void HandleMessage(DisconnectMessage message)
        {
            this.OnDisconnectReceived(message);
            //  Shutdown and disconnect from the socket
            if (this._socket != null)
            {
                lock (this._socket)
                {
                    if (this._socket != null)
                    {
                        this._socket.Disconnect(true);
                        //  When socket is disconnected wait for listener to finish
                        if (this._messageListenerCompleted != null)
                        {
                            //  Wait for listener task to finish
                            this._messageListenerCompleted.WaitOne();
                            this._messageListenerCompleted.Dispose();
                            this._messageListenerCompleted = null;
                        }
                        this._socket.Dispose();
                        this._socket = null;
                    }
                }
            }
        }
        private void HandleMessage(IgnoreMessage message)
        {
            this.OnIgnoreReceived(message);
        }
        private void HandleMessage(UnimplementedMessage message)
        {
            this.OnUnimplementedReceived(message);
        }
        private void HandleMessage(DebugMessage message)
        {
            this.OnDebugReceived(message);
        }
        private void HandleMessage(ServiceRequestMessage message)
        {
            this.OnServiceRequestReceived(message);
        }
        private void HandleMessage(ServiceAcceptMessage message)
        {
            //  TODO:   Refactor to avoid this method here
            this.OnServiceAcceptReceived(message);
            this._serviceAccepted.Set();
        }
        private void HandleMessage(KeyExchangeInitMessage message)
        {
            this.OnKeyExchangeInitReceived(message);
        }
        private void HandleMessage(NewKeysMessage message)
        {
            this.OnNewKeysReceived(message);
        }
        #endregion
        #region Handle User Authentication messages
        private void HandleMessage(RequestMessage message)
        {
            this.OnUserAuthenticationRequestReceived(message);
        }
        private void HandleMessage(FailureMessage message)
        {
            this.OnUserAuthenticationFailureReceived(message);
        }
        private void HandleMessage(SuccessMessage message)
        {
            this.OnUserAuthenticationSuccessReceived(message);
        }
        private void HandleMessage(BannerMessage message)
        {
            this.OnUserAuthenticationBannerReceived(message);
        }
        #endregion
        #region Handle connection messages
        private void HandleMessage(GlobalRequestMessage message)
        {
            this.OnGlobalRequestReceived(message);
        }
        private void HandleMessage(RequestSuccessMessage message)
        {
            this.OnRequestSuccessReceived(message);
        }
        private void HandleMessage(RequestFailureMessage message)
        {
            this.OnRequestFailureReceived(message);
        }
        private void HandleMessage(ChannelOpenMessage message)
        {
            this.OnChannelOpenReceived(message);
        }
        private void HandleMessage(ChannelOpenConfirmationMessage message)
        {
            this.OnChannelOpenConfirmationReceived(message);
        }
        private void HandleMessage(ChannelOpenFailureMessage message)
        {
            this.OnChannelOpenFailureReceived(message);
        }
        private void HandleMessage(ChannelWindowAdjustMessage message)
        {
            this.OnChannelWindowAdjustReceived(message);
        }
        private void HandleMessage(ChannelDataMessage message)
        {
            this.OnChannelDataReceived(message);
        }
        private void HandleMessage(ChannelExtendedDataMessage message)
        {
            this.OnChannelExtendedDataReceived(message);
        }
        private void HandleMessage(ChannelEofMessage message)
        {
            this.OnChannelEofReceived(message);
        }
        private void HandleMessage(ChannelCloseMessage message)
        {
            this.OnChannelCloseReceived(message);
        }
        private void HandleMessage(ChannelRequestMessage message)
        {
            this.OnChannelRequestReceived(message);
        }
        private void HandleMessage(ChannelSuccessMessage message)
        {
            this.OnChannelSuccessReceived(message);
        }
        private void HandleMessage(ChannelFailureMessage message)
        {
            this.OnChannelFailureReceived(message);
        }
        #endregion
        #region Handle received message events
        /// 
        /// Called when  received.
        /// 
        ///  message.
        protected virtual void OnDisconnectReceived(DisconnectMessage message)
        {
            if (this.DisconnectReceived != null)
            {
                this.DisconnectReceived(this, new MessageEventArgs(message));
            }
            if (this.Disconnected != null)
            {
                this.Disconnected(this, new EventArgs());
            }
        }
        /// 
        /// Called when  received.
        /// 
        ///  message.
        protected virtual void OnIgnoreReceived(IgnoreMessage message)
        {
            if (this.IgnoreReceived != null)
            {
                this.IgnoreReceived(this, new MessageEventArgs(message));
            }
        }
        /// 
        /// Called when  message received.
        /// 
        ///  message.
        protected virtual void OnUnimplementedReceived(UnimplementedMessage message)
        {
            if (this.UnimplementedReceived != null)
            {
                this.UnimplementedReceived(this, new MessageEventArgs(message));
            }
        }
        /// 
        /// Called when  message received.
        /// 
        ///  message.
        protected virtual void OnDebugReceived(DebugMessage message)
        {
            if (this.DebugReceived != null)
            {
                this.DebugReceived(this, new MessageEventArgs(message));
            }
        }
        /// 
        /// Called when  message received.
        /// 
        ///  message.
        protected virtual void OnServiceRequestReceived(ServiceRequestMessage message)
        {
            if (this.ServiceRequestReceived != null)
            {
                this.ServiceRequestReceived(this, new MessageEventArgs(message));
            }
        }
        /// 
        /// Called when  message received.
        /// 
        ///  message.
        protected virtual void OnServiceAcceptReceived(ServiceAcceptMessage message)
        {
            if (this.ServiceAcceptReceived != null)
            {
                this.ServiceAcceptReceived(this, new MessageEventArgs(message));
            }
        }
        /// 
        /// Called when  message received.
        /// 
        ///  message.
        protected virtual void OnKeyExchangeInitReceived(KeyExchangeInitMessage message)
        {
            this._keyExchangeCompletedWaitHandle.Reset();
            //  Disable all registered messages except key exchange related
            foreach (var messageMetadata in this._messagesMetadata)
            {
                if (messageMetadata.Activated == true && messageMetadata.Number > 2 && (messageMetadata.Number < 20 || messageMetadata.Number > 30))
                    messageMetadata.Enabled = false;
            }
            var keyExchangeAlgorithmName = (from c in this.ConnectionInfo.KeyExchangeAlgorithms.Keys
                                            from s in message.KeyExchangeAlgorithms
                                            where s == c
                                            select c).FirstOrDefault();
            if (keyExchangeAlgorithmName == null)
            {
                throw new SshConnectionException("Failed to negotiate key exchange algorithm.", DisconnectReason.KeyExchangeFailed);
            }
            //  Create instance of key exchange algorithm that will be used
            this._keyExchange = this.ConnectionInfo.KeyExchangeAlgorithms[keyExchangeAlgorithmName].CreateInstance();
            //  Start the algorithm implementation
            this._keyExchange.Start(this, message);
            if (this.KeyExchangeInitReceived != null)
            {
                this.KeyExchangeInitReceived(this, new MessageEventArgs(message));
            }
        }
        /// 
        /// Called when  message received.
        /// 
        ///  message.
        protected virtual void OnNewKeysReceived(NewKeysMessage message)
        {
            //  Update sessionId
            if (this.SessionId == null)
            {
                this.SessionId = this._keyExchange.ExchangeHash;
            }
            //  Dispose of old ciphers and hash algorithms
            if (this._serverMac != null)
            {
                this._serverMac.Dispose();
                this._serverMac = null;
            }
            if (this._clientMac != null)
            {
                this._clientMac.Dispose();
                this._clientMac = null;
            }
            //  Update negotiated algorithms
            this._serverCipher = this._keyExchange.CreateServerCipher();
            this._clientCipher = this._keyExchange.CreateClientCipher();
            this._serverMac = this._keyExchange.CreateServerHash();
            this._clientMac = this._keyExchange.CreateClientHash();
            this._clientCompression = this._keyExchange.CreateCompressor();
            this._serverDecompression = this._keyExchange.CreateDecompressor();
            //  Dispose of old KeyExchange object as it is no longer needed.
            if (this._keyExchange != null)
            {
                this._keyExchange.Dispose();
                this._keyExchange = null;
            }
            //  Enable all active registered messages
            foreach (var messageMetadata in this._messagesMetadata)
            {
                if (messageMetadata.Activated == true)
                    messageMetadata.Enabled = true;
            }
            if (this.NewKeysReceived != null)
            {
                this.NewKeysReceived(this, new MessageEventArgs(message));
            }
            //  Signal that key exchange completed
            this._keyExchangeCompletedWaitHandle.Set();
        }
        /// 
        /// Called when  message received.
        /// 
        ///  message.
        protected virtual void OnUserAuthenticationRequestReceived(RequestMessage message)
        {
            if (this.UserAuthenticationRequestReceived != null)
            {
                this.UserAuthenticationRequestReceived(this, new MessageEventArgs(message));
            }
        }
        /// 
        /// Called when  message received.
        /// 
        ///  message.
        protected virtual void OnUserAuthenticationFailureReceived(FailureMessage message)
        {
            if (this.UserAuthenticationFailureReceived != null)
            {
                this.UserAuthenticationFailureReceived(this, new MessageEventArgs(message));
            }
        }
        /// 
        /// Called when  message received.
        /// 
        ///  message.
        protected virtual void OnUserAuthenticationSuccessReceived(SuccessMessage message)
        {
            if (this.UserAuthenticationSuccessReceived != null)
            {
                this.UserAuthenticationSuccessReceived(this, new MessageEventArgs(message));
            }
        }
        /// 
        /// Called when  message received.
        /// 
        ///  message.
        protected virtual void OnUserAuthenticationBannerReceived(BannerMessage message)
        {
            if (this.UserAuthenticationBannerReceived != null)
            {
                this.UserAuthenticationBannerReceived(this, new MessageEventArgs(message));
            }
        }
        /// 
        /// Called when  message received.
        /// 
        ///  message.
        protected virtual void OnGlobalRequestReceived(GlobalRequestMessage message)
        {
            if (this.GlobalRequestReceived != null)
            {
                this.GlobalRequestReceived(this, new MessageEventArgs(message));
            }
        }
        /// 
        /// Called when  message received.
        /// 
        ///  message.
        protected virtual void OnRequestSuccessReceived(RequestSuccessMessage message)
        {
            if (this.RequestSuccessReceived != null)
            {
                this.RequestSuccessReceived(this, new MessageEventArgs(message));
            }
        }
        /// 
        /// Called when  message received.
        /// 
        ///  message.
        protected virtual void OnRequestFailureReceived(RequestFailureMessage message)
        {
            if (this.RequestFailureReceived != null)
            {
                this.RequestFailureReceived(this, new MessageEventArgs(message));
            }
        }
        /// 
        /// Called when  message received.
        /// 
        ///  message.
        protected virtual void OnChannelOpenReceived(ChannelOpenMessage message)
        {
            if (this.ChannelOpenReceived != null)
            {
                this.ChannelOpenReceived(this, new MessageEventArgs(message));
            }
        }
        /// 
        /// Called when  message received.
        /// 
        ///  message.
        protected virtual void OnChannelOpenConfirmationReceived(ChannelOpenConfirmationMessage message)
        {
            if (this.ChannelOpenConfirmationReceived != null)
            {
                this.ChannelOpenConfirmationReceived(this, new MessageEventArgs(message));
            }
        }
        /// 
        /// Called when  message received.
        /// 
        ///  message.
        protected virtual void OnChannelOpenFailureReceived(ChannelOpenFailureMessage message)
        {
            if (this.ChannelOpenFailureReceived != null)
            {
                this.ChannelOpenFailureReceived(this, new MessageEventArgs(message));
            }
        }
        /// 
        /// Called when  message received.
        /// 
        ///  message.
        protected virtual void OnChannelWindowAdjustReceived(ChannelWindowAdjustMessage message)
        {
            if (this.ChannelWindowAdjustReceived != null)
            {
                this.ChannelWindowAdjustReceived(this, new MessageEventArgs(message));
            }
        }
        /// 
        /// Called when  message received.
        /// 
        ///  message.
        protected virtual void OnChannelDataReceived(ChannelDataMessage message)
        {
            if (this.ChannelDataReceived != null)
            {
                this.ChannelDataReceived(this, new MessageEventArgs(message));
            }
        }
        /// 
        /// Called when  message received.
        /// 
        ///  message.
        protected virtual void OnChannelExtendedDataReceived(ChannelExtendedDataMessage message)
        {
            if (this.ChannelExtendedDataReceived != null)
            {
                this.ChannelExtendedDataReceived(this, new MessageEventArgs(message));
            }
        }
        /// 
        /// Called when  message received.
        /// 
        ///  message.
        protected virtual void OnChannelEofReceived(ChannelEofMessage message)
        {
            if (this.ChannelEofReceived != null)
            {
                this.ChannelEofReceived(this, new MessageEventArgs(message));
            }
        }
        /// 
        /// Called when  message received.
        /// 
        ///  message.
        protected virtual void OnChannelCloseReceived(ChannelCloseMessage message)
        {
            if (this.ChannelCloseReceived != null)
            {
                this.ChannelCloseReceived(this, new MessageEventArgs(message));
            }
        }
        /// 
        /// Called when  message received.
        /// 
        ///  message.
        protected virtual void OnChannelRequestReceived(ChannelRequestMessage message)
        {
            if (this.ChannelRequestReceived != null)
            {
                this.ChannelRequestReceived(this, new MessageEventArgs(message));
            }
        }
        /// 
        /// Called when  message received.
        /// 
        ///  message.
        protected virtual void OnChannelSuccessReceived(ChannelSuccessMessage message)
        {
            if (this.ChannelSuccessReceived != null)
            {
                this.ChannelSuccessReceived(this, new MessageEventArgs(message));
            }
        }
        /// 
        /// Called when  message received.
        /// 
        ///  message.
        protected virtual void OnChannelFailureReceived(ChannelFailureMessage message)
        {
            if (this.ChannelFailureReceived != null)
            {
                this.ChannelFailureReceived(this, new MessageEventArgs(message));
            }
        }
        /// 
        /// Called when  message received.
        /// 
        ///  message.
        protected virtual void OnMessageReceived(Message message)
        {
            if (this.MessageReceived != null)
            {
                this.MessageReceived(this, new MessageEventArgs(message));
            }
        }
        #endregion
        #region Read & Write operations
        /// 
        /// Reads the specified length of bytes from the server
        /// 
        /// The length.
        /// 
        private byte[] Read(int length)
        {
            byte[] buffer = new byte[length];
            this.InternalRead(length, ref buffer);
            return buffer;
        }
        /// 
        /// Writes the specified data to the server.
        /// 
        /// The data.
        partial void Write(byte[] data);
        #endregion
        #region Message loading functions
        /// 
        /// Registers SSH Message with the session.
        /// 
        /// Name of the message.
        public void RegisterMessage(string messageName)
        {
            this.InternalRegisterMessage(messageName);
        }
        /// 
        /// Removes SSH message from the session
        /// 
        /// Name of the message.
        public void UnRegisterMessage(string messageName)
        {
            this.InternalUnRegisterMessage(messageName);
        }
        /// 
        /// Loads the message.
        /// 
        /// Message data.
        /// New message
        private Message LoadMessage(byte[] data)
        {
            var messageType = data[0];
            var messageMetadata = (from m in this._messagesMetadata where m.Number == messageType && m.Enabled && m.Activated select m).SingleOrDefault();
            if (messageMetadata == null)
                throw new SshException(string.Format(CultureInfo.CurrentCulture, "Message type {0} is not valid.", messageType));
            var message = messageMetadata.Type.CreateInstance();
            message.Load(data);
            return message;
        }
        partial void InternalRegisterMessage(string messageName);
        partial void InternalUnRegisterMessage(string messageName);
        #endregion
        partial void ExecuteThread(Action action);
        partial void OpenSocket();
        partial void InternalRead(int length, ref byte[] buffer);
        /// 
        /// Listens for incoming message from the server and handles them. This method run as a task on separate thread.
        /// 
        private void MessageListener()
        {
            try
            {
                while (this._socket.Connected)
                {
                    var message = this.ReceiveMessage();
                    if (message == null)
                    {
                        throw new NullReferenceException("The 'message' variable cannot be null");
                    }
                    else
                    {
                        this.HandleMessage((dynamic)message);
                    }
                }
            }
            catch (Exception exp)
            {
                this.RaiseError(exp);
            }
        }
        /// 
        /// Raises the  event.
        /// 
        /// The exp.
        private void RaiseError(Exception exp)
        {
            var connectionException = exp as SshConnectionException;
            //  If connection exception was raised while isDisconnecting is true then this is expected
            //  case and should be ignore
            if (connectionException != null && this._isDisconnecting)
                return;
            this._exception = exp;
            this._exceptionWaitHandle.Set();
            if (this.ErrorOccured != null)
            {
                this.ErrorOccured(this, new ExceptionEventArgs(exp));
            }
            if (connectionException != null && connectionException.DisconnectReason != DisconnectReason.ConnectionLost)
            {
                this.SendDisconnect(connectionException.DisconnectReason, exp.ToString());
            }
        }
        #region IDisposable Members
        private bool _disposed = false;
        /// 
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged ResourceMessages.
        /// 
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        /// 
        /// Releases unmanaged and - optionally - managed resources
        /// 
        /// true to release both managed and unmanaged resources; false to release only unmanaged ResourceMessages.
        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called.
            if (!this._disposed)
            {
                // If disposing equals true, dispose all managed
                // and unmanaged ResourceMessages.
                if (disposing)
                {
                    this._isDisconnecting = true;
                    if (this._socket != null)
                    {
                        //  If socket still open try to send disconnect message to the server
                        this.SendDisconnect(DisconnectReason.ByApplication, "Connection terminated by the client.");
                        this._socket.Dispose();
                        this._socket = null;
                    }
                    if (this._messageListenerCompleted != null)
                    {
                        //  Wait for socket to be closed and for task to complete before disposing a task
                        this._messageListenerCompleted.WaitOne();
                        this._messageListenerCompleted.Dispose();
                        this._messageListenerCompleted = null;
                    }
                    if (this._serviceAccepted != null)
                    {
                        this._serviceAccepted.Dispose();
                        this._serviceAccepted = null;
                    }
                    if (this._exceptionWaitHandle != null)
                    {
                        this._exceptionWaitHandle.Dispose();
                        this._exceptionWaitHandle = null;
                    }
                    if (this._keyExchangeCompletedWaitHandle != null)
                    {
                        this._keyExchangeCompletedWaitHandle.Dispose();
                        this._keyExchangeCompletedWaitHandle = null;
                    }
                    if (this._serverMac != null)
                    {
                        this._serverMac.Dispose();
                        this._serverMac = null;
                    }
                    if (this._clientMac != null)
                    {
                        this._clientMac.Dispose();
                        this._clientMac = null;
                    }
                    if (this._keyExchange != null)
                    {
                        this._keyExchange.Dispose();
                        this._keyExchange = null;
                    }
                }
                // Note disposing has been done.
                this._disposed = true;
            }
        }
        /// 
        /// Releases unmanaged resources and performs other cleanup operations before the
        ///  is reclaimed by garbage collection.
        /// 
        ~Session()
        {
            // Do not re-create Dispose clean-up code here.
            // Calling Dispose(false) is optimal in terms of
            // readability and maintainability.
            Dispose(false);
        }
        #endregion
        private class MessageMetadata
        {
            public string Name { get; set; }
            public byte Number { get; set; }
            public bool Enabled { get; set; }
            public bool Activated { get; set; }
            public Type Type { get; set; }
        }
    }
}