BaseClient
Serves as base class for client implementations, provides common client functionality.
            
                using Microsoft.Extensions.Logging;
using Renci.SshNet.Common;
using Renci.SshNet.Messages.Transport;
using System;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
namespace Renci.SshNet
{
    [NullableContext(1)]
    [Nullable(0)]
    public abstract class BaseClient : IBaseClient, IDisposable
    {
        private readonly bool _ownsConnectionInfo;
        private readonly ILogger _logger;
        private readonly IServiceFactory _serviceFactory;
        private readonly object _keepAliveLock = new object();
        private TimeSpan _keepAliveInterval;
        [Nullable(2)]
        private Timer _keepAliveTimer;
        private ConnectionInfo _connectionInfo;
        private bool _isDisposed;
        [Nullable(2)]
        [field: Nullable(2)]
        internal ISession Session {
            [NullableContext(2)]
            get;
            [NullableContext(2)]
            private set;
        }
        internal IServiceFactory ServiceFactory => _serviceFactory;
        public ConnectionInfo ConnectionInfo {
            get {
                CheckDisposed();
                return _connectionInfo;
            }
            private set {
                _connectionInfo = value;
            }
        }
        public virtual bool IsConnected {
            get {
                CheckDisposed();
                return IsSessionConnected();
            }
        }
        public TimeSpan KeepAliveInterval {
            get {
                CheckDisposed();
                return _keepAliveInterval;
            }
            set {
                CheckDisposed();
                value.EnsureValidTimeout("KeepAliveInterval");
                if (!(value == _keepAliveInterval)) {
                    if (value == Timeout.InfiniteTimeSpan)
                        StopKeepAliveTimer();
                    else if (_keepAliveTimer != null) {
                        _keepAliveTimer.Change(value, value);
                    } else if (IsSessionConnected()) {
                        _keepAliveTimer = CreateKeepAliveTimer(value, value);
                    }
                    _keepAliveInterval = value;
                }
            }
        }
        [Nullable(new byte[] {
            2,
            1
        })]
        [method: Nullable(new byte[] {
            2,
            1
        })]
        [field: Nullable(new byte[] {
            2,
            1
        })]
        public event EventHandler<ExceptionEventArgs> ErrorOccurred;
        [Nullable(new byte[] {
            2,
            1
        })]
        [method: Nullable(new byte[] {
            2,
            1
        })]
        [field: Nullable(new byte[] {
            2,
            1
        })]
        public event EventHandler<HostKeyEventArgs> HostKeyReceived;
        [Nullable(new byte[] {
            2,
            1
        })]
        [method: Nullable(new byte[] {
            2,
            1
        })]
        [field: Nullable(new byte[] {
            2,
            1
        })]
        public event EventHandler<SshIdentificationEventArgs> ServerIdentificationReceived;
        protected BaseClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo)
            : this(connectionInfo, ownsConnectionInfo, new ServiceFactory())
        {
        }
        private protected BaseClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo, IServiceFactory serviceFactory)
        {
            ThrowHelper.ThrowIfNull(connectionInfo, "connectionInfo");
            ThrowHelper.ThrowIfNull(serviceFactory, "serviceFactory");
            _connectionInfo = connectionInfo;
            _ownsConnectionInfo = ownsConnectionInfo;
            _serviceFactory = serviceFactory;
            _logger = (connectionInfo.LoggerFactory ?? SshNetLoggingConfiguration.LoggerFactory).CreateLogger(GetType());
            _keepAliveInterval = Timeout.InfiniteTimeSpan;
        }
        public void Connect()
        {
            CheckDisposed();
            if (IsConnected)
                throw new InvalidOperationException("The client is already connected.");
            OnConnecting();
            ISession session = Session;
            if (session == null || !session.IsConnected) {
                if (session != null)
                    DisposeSession(session);
                Session = CreateAndConnectSession();
            }
            try {
                OnConnected();
            } catch {
                DisposeSession();
                throw;
            }
            StartKeepAliveTimer();
        }
        [AsyncStateMachine(typeof(<ConnectAsync>d__34))]
        public Task ConnectAsync(CancellationToken cancellationToken)
        {
            <ConnectAsync>d__34 stateMachine = default(<ConnectAsync>d__34);
            stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create();
            stateMachine.<>4__this = this;
            stateMachine.cancellationToken = cancellationToken;
            stateMachine.<>1__state = -1;
            stateMachine.<>t__builder.Start(ref stateMachine);
            return stateMachine.<>t__builder.Task;
        }
        public void Disconnect()
        {
            _logger.LogInformation("Disconnecting client.", Array.Empty<object>());
            CheckDisposed();
            OnDisconnecting();
            StopKeepAliveTimer();
            DisposeSession();
            OnDisconnected();
        }
        [Obsolete("Use KeepAliveInterval to send a keep-alive message at regular intervals.")]
        public void SendKeepAlive()
        {
            CheckDisposed();
            SendKeepAliveMessage();
        }
        protected virtual void OnConnecting()
        {
        }
        protected virtual void OnConnected()
        {
        }
        protected virtual void OnDisconnecting()
        {
            Session?.OnDisconnecting();
        }
        protected virtual void OnDisconnected()
        {
        }
        private void Session_ErrorOccurred([Nullable(2)] object sender, ExceptionEventArgs e)
        {
            this.ErrorOccurred?.Invoke(this, e);
        }
        private void Session_HostKeyReceived([Nullable(2)] object sender, HostKeyEventArgs e)
        {
            this.HostKeyReceived?.Invoke(this, e);
        }
        private void Session_ServerIdentificationReceived([Nullable(2)] object sender, SshIdentificationEventArgs e)
        {
            this.ServerIdentificationReceived?.Invoke(this, e);
        }
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        protected virtual void Dispose(bool disposing)
        {
            if (!_isDisposed && disposing) {
                _logger.LogDebug("Disposing client.", Array.Empty<object>());
                Disconnect();
                if (_ownsConnectionInfo)
                    (_connectionInfo as IDisposable)?.Dispose();
                _isDisposed = true;
            }
        }
        protected void CheckDisposed()
        {
            ThrowHelper.ThrowObjectDisposedIf(_isDisposed, this);
        }
        private void StopKeepAliveTimer()
        {
            if (_keepAliveTimer != null) {
                _keepAliveTimer.Dispose();
                _keepAliveTimer = null;
            }
        }
        private void SendKeepAliveMessage()
        {
            ISession session = Session;
            if (session != null && Monitor.TryEnter(_keepAliveLock))
                try {
                    session.TrySendMessage(new IgnoreMessage());
                } catch (ObjectDisposedException) {
                } catch (Exception exception) {
                    _logger.LogError(exception, "Error sending keepalive message", Array.Empty<object>());
                } finally {
                    Monitor.Exit(_keepAliveLock);
                }
        }
        private void StartKeepAliveTimer()
        {
            if (!(_keepAliveInterval == Timeout.InfiniteTimeSpan) && _keepAliveTimer == null)
                _keepAliveTimer = CreateKeepAliveTimer(_keepAliveInterval, _keepAliveInterval);
        }
        private Timer CreateKeepAliveTimer(TimeSpan dueTime, TimeSpan period)
        {
            return new Timer(delegate {
                SendKeepAliveMessage();
            }, Session, dueTime, period);
        }
        private ISession CreateAndConnectSession()
        {
            ISession session = _serviceFactory.CreateSession(ConnectionInfo, _serviceFactory.CreateSocketFactory());
            session.ServerIdentificationReceived += Session_ServerIdentificationReceived;
            session.HostKeyReceived += Session_HostKeyReceived;
            session.ErrorOccured += Session_ErrorOccurred;
            try {
                session.Connect();
                return session;
            } catch {
                DisposeSession(session);
                throw;
            }
        }
        [AsyncStateMachine(typeof(<CreateAndConnectSessionAsync>d__52))]
        private Task<ISession> CreateAndConnectSessionAsync(CancellationToken cancellationToken)
        {
            <CreateAndConnectSessionAsync>d__52 stateMachine = default(<CreateAndConnectSessionAsync>d__52);
            stateMachine.<>t__builder = AsyncTaskMethodBuilder<ISession>.Create();
            stateMachine.<>4__this = this;
            stateMachine.cancellationToken = cancellationToken;
            stateMachine.<>1__state = -1;
            stateMachine.<>t__builder.Start(ref stateMachine);
            return stateMachine.<>t__builder.Task;
        }
        private void DisposeSession(ISession session)
        {
            session.ErrorOccured -= Session_ErrorOccurred;
            session.HostKeyReceived -= Session_HostKeyReceived;
            session.ServerIdentificationReceived -= Session_ServerIdentificationReceived;
            session.Dispose();
        }
        private void DisposeSession()
        {
            ISession session = Session;
            if (session != null) {
                Session = null;
                DisposeSession(session);
            }
        }
        private bool IsSessionConnected()
        {
            return Session?.IsConnected ?? false;
        }
    }
}