<PackageReference Include="SSH.NET" Version="2023.0.0" />

SubsystemSession

Base class for SSH subsystem implementations.
using Renci.SshNet.Channels; using Renci.SshNet.Common; using System; using System.Globalization; using System.Runtime.ExceptionServices; using System.Threading; namespace Renci.SshNet { internal abstract class SubsystemSession : ISubsystemSession, IDisposable { private const int SystemWaitHandleCount = 3; private readonly string _subsystemName; private ISession _session; private IChannelSession _channel; private Exception _exception; private EventWaitHandle _errorOccuredWaitHandle = new ManualResetEvent(false); private EventWaitHandle _sessionDisconnectedWaitHandle = new ManualResetEvent(false); private EventWaitHandle _channelClosedWaitHandle = new ManualResetEvent(false); private bool _isDisposed; public int OperationTimeout { get; set; } internal IChannelSession Channel { get { EnsureNotDisposed(); return _channel; } } public bool IsOpen { get { if (_channel != null) return _channel.IsOpen; return false; } } public event EventHandler<ExceptionEventArgs> ErrorOccurred; public event EventHandler<EventArgs> Disconnected; protected SubsystemSession(ISession session, string subsystemName, int operationTimeout) { if (session == null) throw new ArgumentNullException("session"); if (subsystemName == null) throw new ArgumentNullException("subsystemName"); _session = session; _subsystemName = subsystemName; OperationTimeout = operationTimeout; } public void Connect() { EnsureNotDisposed(); if (IsOpen) throw new InvalidOperationException("The session is already connected."); _errorOccuredWaitHandle.Reset(); _sessionDisconnectedWaitHandle.Reset(); _sessionDisconnectedWaitHandle.Reset(); _channelClosedWaitHandle.Reset(); _session.ErrorOccured += Session_ErrorOccured; _session.Disconnected += Session_Disconnected; _channel = _session.CreateChannelSession(); _channel.DataReceived += Channel_DataReceived; _channel.Exception += Channel_Exception; _channel.Closed += Channel_Closed; _channel.Open(); if (!_channel.SendSubsystemRequest(_subsystemName)) { Disconnect(); throw new SshException(string.Format(CultureInfo.InvariantCulture, "Subsystem '{0}' could not be executed.", _subsystemName)); } OnChannelOpen(); } public void Disconnect() { UnsubscribeFromSessionEvents(_session); IChannelSession channel = _channel; if (channel != null) { _channel = null; channel.DataReceived -= Channel_DataReceived; channel.Exception -= Channel_Exception; channel.Closed -= Channel_Closed; channel.Dispose(); } } public void SendData(byte[] data) { EnsureNotDisposed(); EnsureSessionIsOpen(); _channel.SendData(data); } protected abstract void OnChannelOpen(); protected abstract void OnDataReceived(byte[] data); protected void RaiseError(Exception error) { _exception = error; _errorOccuredWaitHandle?.Set(); SignalErrorOccurred(error); } private void Channel_DataReceived(object sender, ChannelDataEventArgs e) { try { OnDataReceived(e.Data); } catch (Exception error) { RaiseError(error); } } private void Channel_Exception(object sender, ExceptionEventArgs e) { RaiseError(e.Exception); } private void Channel_Closed(object sender, ChannelEventArgs e) { _channelClosedWaitHandle?.Set(); } public void WaitOnHandle(WaitHandle waitHandle, int millisecondsTimeout) { int num = WaitHandle.WaitAny(new WaitHandle[4] { _errorOccuredWaitHandle, _sessionDisconnectedWaitHandle, _channelClosedWaitHandle, waitHandle }, millisecondsTimeout); switch (num) { case 3: break; case 0: ExceptionDispatchInfo.Capture(_exception).Throw(); break; case 1: throw new SshException("Connection was closed by the server."); case 2: throw new SshException("Channel was closed."); case 258: throw new SshOperationTimeoutException("Operation has timed out."); default: throw new NotImplementedException(string.Format(CultureInfo.InvariantCulture, "WaitAny return value '{0}' is not implemented.", num)); } } public bool WaitOne(WaitHandle waitHandle, int millisecondsTimeout) { int num = WaitHandle.WaitAny(new WaitHandle[4] { _errorOccuredWaitHandle, _sessionDisconnectedWaitHandle, _channelClosedWaitHandle, waitHandle }, millisecondsTimeout); switch (num) { case 0: ExceptionDispatchInfo.Capture(_exception).Throw(); return false; case 1: throw new SshException("Connection was closed by the server."); case 2: throw new SshException("Channel was closed."); case 3: return true; case 258: return false; default: throw new NotImplementedException(string.Format(CultureInfo.InvariantCulture, "WaitAny return value '{0}' is not implemented.", num)); } } public int WaitAny(WaitHandle waitHandle1, WaitHandle waitHandle2, int millisecondsTimeout) { int num = WaitHandle.WaitAny(new WaitHandle[5] { _errorOccuredWaitHandle, _sessionDisconnectedWaitHandle, _channelClosedWaitHandle, waitHandle1, waitHandle2 }, millisecondsTimeout); switch (num) { case 0: ExceptionDispatchInfo.Capture(_exception).Throw(); return -1; case 1: throw new SshException("Connection was closed by the server."); case 2: throw new SshException("Channel was closed."); case 3: return 0; case 4: return 1; case 258: throw new SshOperationTimeoutException("Operation has timed out."); default: throw new NotImplementedException(string.Format(CultureInfo.InvariantCulture, "WaitAny return value '{0}' is not implemented.", num)); } } public int WaitAny(WaitHandle[] waitHandles, int millisecondsTimeout) { int num = WaitHandle.WaitAny(waitHandles, millisecondsTimeout); switch (num) { case 0: ExceptionDispatchInfo.Capture(_exception).Throw(); return -1; case 1: throw new SshException("Connection was closed by the server."); case 2: throw new SshException("Channel was closed."); case 258: throw new SshOperationTimeoutException("Operation has timed out."); default: return num - 3; } } public WaitHandle[] CreateWaitHandleArray(WaitHandle waitHandle1, WaitHandle waitHandle2) { return new WaitHandle[5] { _errorOccuredWaitHandle, _sessionDisconnectedWaitHandle, _channelClosedWaitHandle, waitHandle1, waitHandle2 }; } public WaitHandle[] CreateWaitHandleArray(params WaitHandle[] waitHandles) { WaitHandle[] array = new WaitHandle[waitHandles.Length + 3]; array[0] = _errorOccuredWaitHandle; array[1] = _sessionDisconnectedWaitHandle; array[2] = _channelClosedWaitHandle; for (int i = 0; i < waitHandles.Length; i++) { array[i + 3] = waitHandles[i]; } return array; } private void Session_Disconnected(object sender, EventArgs e) { _sessionDisconnectedWaitHandle?.Set(); SignalDisconnected(); } private void Session_ErrorOccured(object sender, ExceptionEventArgs e) { RaiseError(e.Exception); } private void SignalErrorOccurred(Exception error) { this.ErrorOccurred?.Invoke(this, new ExceptionEventArgs(error)); } private void SignalDisconnected() { this.Disconnected?.Invoke(this, EventArgs.Empty); } private void EnsureSessionIsOpen() { if (!IsOpen) throw new InvalidOperationException("The session is not open."); } private void UnsubscribeFromSessionEvents(ISession session) { if (session != null) { session.Disconnected -= Session_Disconnected; session.ErrorOccured -= Session_ErrorOccured; } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!_isDisposed && disposing) { Disconnect(); _session = null; EventWaitHandle errorOccuredWaitHandle = _errorOccuredWaitHandle; if (errorOccuredWaitHandle != null) { _errorOccuredWaitHandle = null; errorOccuredWaitHandle.Dispose(); } EventWaitHandle sessionDisconnectedWaitHandle = _sessionDisconnectedWaitHandle; if (sessionDisconnectedWaitHandle != null) { _sessionDisconnectedWaitHandle = null; sessionDisconnectedWaitHandle.Dispose(); } EventWaitHandle channelClosedWaitHandle = _channelClosedWaitHandle; if (channelClosedWaitHandle != null) { _channelClosedWaitHandle = null; channelClosedWaitHandle.Dispose(); } _isDisposed = true; } } ~SubsystemSession() { Dispose(false); } private void EnsureNotDisposed() { if (_isDisposed) throw new ObjectDisposedException(GetType().FullName); } } }