< Summary

Information
Class: Renci.SshNet.SubsystemSession
Assembly: Renci.SshNet
File(s): \home\appveyor\projects\ssh-net\src\Renci.SshNet\SubsystemSession.cs
Line coverage
71%
Covered lines: 170
Uncovered lines: 67
Coverable lines: 237
Total lines: 560
Line coverage: 71.7%
Branch coverage
48%
Covered branches: 34
Total branches: 70
Branch coverage: 48.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

\home\appveyor\projects\ssh-net\src\Renci.SshNet\SubsystemSession.cs

#LineLine coverage
 1using System;
 2using System.Globalization;
 3using System.Runtime.ExceptionServices;
 4using System.Threading;
 5
 6using Renci.SshNet.Abstractions;
 7using Renci.SshNet.Channels;
 8using Renci.SshNet.Common;
 9
 10namespace Renci.SshNet
 11{
 12    /// <summary>
 13    /// Base class for SSH subsystem implementations.
 14    /// </summary>
 15    internal abstract class SubsystemSession : ISubsystemSession
 16    {
 17        /// <summary>
 18        /// Holds the number of system wait handles that are returned as the leading entries in the array returned
 19        /// in <see cref="CreateWaitHandleArray(WaitHandle[])"/>.
 20        /// </summary>
 21        private const int SystemWaitHandleCount = 3;
 22
 23        private readonly string _subsystemName;
 24        private ISession _session;
 25        private IChannelSession _channel;
 26        private Exception _exception;
 94527        private EventWaitHandle _errorOccuredWaitHandle = new ManualResetEvent(initialState: false);
 94528        private EventWaitHandle _sessionDisconnectedWaitHandle = new ManualResetEvent(initialState: false);
 94529        private EventWaitHandle _channelClosedWaitHandle = new ManualResetEvent(initialState: false);
 30        private bool _isDisposed;
 31
 32        /// <summary>
 33        /// Gets or set the number of seconds to wait for an operation to complete.
 34        /// </summary>
 35        /// <value>
 36        /// The number of seconds to wait for an operation to complete, or -1 to wait indefinitely.
 37        /// </value>
 2933938        public int OperationTimeout { get; private set; }
 39
 40        /// <summary>
 41        /// Occurs when an error occurred.
 42        /// </summary>
 43        public event EventHandler<ExceptionEventArgs> ErrorOccurred;
 44
 45        /// <summary>
 46        /// Occurs when the server has disconnected from the session.
 47        /// </summary>
 48        public event EventHandler<EventArgs> Disconnected;
 49
 50        /// <summary>
 51        /// Gets the channel associated with this session.
 52        /// </summary>
 53        /// <value>
 54        /// The channel associated with this session.
 55        /// </value>
 56        internal IChannelSession Channel
 57        {
 58            get
 62759            {
 62760                EnsureNotDisposed();
 61
 62762                return _channel;
 62763            }
 64        }
 65
 66        /// <summary>
 67        /// Gets a value indicating whether this session is open.
 68        /// </summary>
 69        /// <value>
 70        /// <see langword="true"/> if this session is open; otherwise, <see langword="false"/>.
 71        /// </value>
 72        public bool IsOpen
 73        {
 10263374            get { return _channel is not null && _channel.IsOpen; }
 75        }
 76
 77        /// <summary>
 78        /// Initializes a new instance of the <see cref="SubsystemSession"/> class.
 79        /// </summary>
 80        /// <param name="session">The session.</param>
 81        /// <param name="subsystemName">Name of the subsystem.</param>
 82        /// <param name="operationTimeout">The number of milliseconds to wait for a given operation to complete, or -1 t
 83        /// <exception cref="ArgumentNullException"><paramref name="session" /> or <paramref name="subsystemName" /> is 
 94584        protected SubsystemSession(ISession session, string subsystemName, int operationTimeout)
 94585        {
 94586            if (session is null)
 087            {
 088                throw new ArgumentNullException(nameof(session));
 89            }
 90
 94591            if (subsystemName is null)
 092            {
 093                throw new ArgumentNullException(nameof(subsystemName));
 94            }
 95
 94596            _session = session;
 94597            _subsystemName = subsystemName;
 94598            OperationTimeout = operationTimeout;
 94599        }
 100
 101        /// <summary>
 102        /// Connects the subsystem using a new SSH channel session.
 103        /// </summary>
 104        /// <exception cref="InvalidOperationException">The session is already connected.</exception>
 105        /// <exception cref="ObjectDisposedException">The method was called after the session was disposed.</exception>
 106        /// <exception cref="SshException">The channel session could not be opened, or the subsystem could not be execut
 107        public void Connect()
 948108        {
 948109            EnsureNotDisposed();
 110
 936111            if (IsOpen)
 18112            {
 18113                throw new InvalidOperationException("The session is already connected.");
 114            }
 115
 116            // reset waithandles in case we're reconnecting
 918117            _ = _errorOccuredWaitHandle.Reset();
 918118            _ = _sessionDisconnectedWaitHandle.Reset();
 918119            _ = _sessionDisconnectedWaitHandle.Reset();
 918120            _ = _channelClosedWaitHandle.Reset();
 121
 918122            _session.ErrorOccured += Session_ErrorOccured;
 918123            _session.Disconnected += Session_Disconnected;
 124
 918125            _channel = _session.CreateChannelSession();
 918126            _channel.DataReceived += Channel_DataReceived;
 918127            _channel.Exception += Channel_Exception;
 918128            _channel.Closed += Channel_Closed;
 918129            _channel.Open();
 130
 918131            if (!_channel.SendSubsystemRequest(_subsystemName))
 25132            {
 133                // close channel session
 25134                Disconnect();
 135
 136                // signal subsystem failure
 25137                throw new SshException(string.Format(CultureInfo.InvariantCulture,
 25138                                                     "Subsystem '{0}' could not be executed.",
 25139                                                     _subsystemName));
 140            }
 141
 893142            OnChannelOpen();
 893143        }
 144
 145        /// <summary>
 146        /// Disconnects the subsystem channel.
 147        /// </summary>
 148        public void Disconnect()
 826149        {
 826150            UnsubscribeFromSessionEvents(_session);
 151
 826152            var channel = _channel;
 826153            if (channel is not null)
 771154            {
 771155                _channel = null;
 771156                channel.DataReceived -= Channel_DataReceived;
 771157                channel.Exception -= Channel_Exception;
 771158                channel.Closed -= Channel_Closed;
 771159                channel.Dispose();
 771160            }
 826161        }
 162
 163        /// <summary>
 164        /// Sends data to the subsystem.
 165        /// </summary>
 166        /// <param name="data">The data to be sent.</param>
 167        public void SendData(byte[] data)
 32204168        {
 32204169            EnsureNotDisposed();
 32192170            EnsureSessionIsOpen();
 171
 32177172            _channel.SendData(data);
 32175173        }
 174
 175        /// <summary>
 176        /// Called when channel is open.
 177        /// </summary>
 178        protected abstract void OnChannelOpen();
 179
 180        /// <summary>
 181        /// Called when data is received.
 182        /// </summary>
 183        /// <param name="data">The data.</param>
 184        protected abstract void OnDataReceived(byte[] data);
 185
 186        /// <summary>
 187        /// Raises the error.
 188        /// </summary>
 189        /// <param name="error">The error.</param>
 190        protected void RaiseError(Exception error)
 45191        {
 45192            _exception = error;
 193
 45194            DiagnosticAbstraction.Log("Raised exception: " + error);
 195
 45196            _ = _errorOccuredWaitHandle?.Set();
 197
 45198            SignalErrorOccurred(error);
 45199        }
 200
 201        private void Channel_DataReceived(object sender, ChannelDataEventArgs e)
 31914202        {
 203            try
 31914204            {
 31914205                OnDataReceived(e.Data);
 31905206            }
 9207            catch (Exception ex)
 9208            {
 9209                RaiseError(ex);
 9210            }
 31914211        }
 212
 213        private void Channel_Exception(object sender, ExceptionEventArgs e)
 15214        {
 15215            RaiseError(e.Exception);
 15216        }
 217
 218        private void Channel_Closed(object sender, ChannelEventArgs e)
 0219        {
 0220            _ = _channelClosedWaitHandle?.Set();
 0221        }
 222
 223        /// <summary>
 224        /// Waits a specified time for a given <see cref="WaitHandle"/> to get signaled.
 225        /// </summary>
 226        /// <param name="waitHandle">The handle to wait for.</param>
 227        /// <param name="millisecondsTimeout">To number of milliseconds to wait for <paramref name="waitHandle"/> to get
 228        /// <exception cref="SshException">The connection was closed by the server.</exception>
 229        /// <exception cref="SshException">The channel was closed.</exception>
 230        /// <exception cref="SshOperationTimeoutException">The handle did not get signaled within the specified timeout.
 231        public void WaitOnHandle(WaitHandle waitHandle, int millisecondsTimeout)
 24774232        {
 24774233            var waitHandles = new[]
 24774234                {
 24774235                    _errorOccuredWaitHandle,
 24774236                    _sessionDisconnectedWaitHandle,
 24774237                    _channelClosedWaitHandle,
 24774238                    waitHandle
 24774239                };
 240
 24774241            var result = WaitHandle.WaitAny(waitHandles, millisecondsTimeout);
 24773242            switch (result)
 243            {
 244                case 0:
 0245                    ExceptionDispatchInfo.Capture(_exception).Throw();
 0246                    break;
 247                case 1:
 0248                    throw new SshException("Connection was closed by the server.");
 249                case 2:
 0250                    throw new SshException("Channel was closed.");
 251                case 3:
 24773252                    break;
 253                case WaitHandle.WaitTimeout:
 0254                    throw new SshOperationTimeoutException("Operation has timed out.");
 255                default:
 0256                    throw new NotImplementedException(string.Format(CultureInfo.InvariantCulture, "WaitAny return value 
 257            }
 24773258        }
 259
 260        /// <summary>
 261        /// Blocks the current thread until the specified <see cref="WaitHandle"/> gets signaled, using a
 262        /// 32-bit signed integer to specify the time interval in milliseconds.
 263        /// </summary>
 264        /// <param name="waitHandle">The handle to wait for.</param>
 265        /// <param name="millisecondsTimeout">To number of milliseconds to wait for <paramref name="waitHandle"/> to get
 266        /// <returns>
 267        /// <see langword="true"/> if <paramref name="waitHandle"/> received a signal within the specified timeout;
 268        /// otherwise, <see langword="false"/>.
 269        /// </returns>
 270        /// <exception cref="SshException">The connection was closed by the server.</exception>
 271        /// <exception cref="SshException">The channel was closed.</exception>
 272        /// <remarks>
 273        /// The blocking wait is also interrupted when either the established channel is closed, the current
 274        /// session is disconnected or an unexpected <see cref="Exception"/> occurred while processing a channel
 275        /// or session event.
 276        /// </remarks>
 277        public bool WaitOne(WaitHandle waitHandle, int millisecondsTimeout)
 0278        {
 0279            var waitHandles = new[]
 0280                {
 0281                    _errorOccuredWaitHandle,
 0282                    _sessionDisconnectedWaitHandle,
 0283                    _channelClosedWaitHandle,
 0284                    waitHandle
 0285                };
 286
 0287            var result = WaitHandle.WaitAny(waitHandles, millisecondsTimeout);
 0288            switch (result)
 289            {
 290                case 0:
 0291                    ExceptionDispatchInfo.Capture(_exception).Throw();
 0292                    return false; // unreached
 293                case 1:
 0294                    throw new SshException("Connection was closed by the server.");
 295                case 2:
 0296                    throw new SshException("Channel was closed.");
 297                case 3:
 0298                    return true;
 299                case WaitHandle.WaitTimeout:
 0300                    return false;
 301                default:
 0302                    throw new NotImplementedException(string.Format(CultureInfo.InvariantCulture, "WaitAny return value 
 303            }
 0304        }
 305
 306        /// <summary>
 307        /// Blocks the current thread until the specified <see cref="WaitHandle"/> gets signaled, using a
 308        /// 32-bit signed integer to specify the time interval in milliseconds.
 309        /// </summary>
 310        /// <param name="waitHandleA">The first handle to wait for.</param>
 311        /// <param name="waitHandleB">The second handle to wait for.</param>
 312        /// <param name="millisecondsTimeout">To number of milliseconds to wait for a <see cref="WaitHandle"/> to get si
 313        /// <returns>
 314        /// <c>0</c> if <paramref name="waitHandleA"/> received a signal within the specified timeout, and <c>1</c>
 315        /// if <paramref name="waitHandleB"/> received a signal within the specified timeout.
 316        /// </returns>
 317        /// <exception cref="SshException">The connection was closed by the server.</exception>
 318        /// <exception cref="SshException">The channel was closed.</exception>
 319        /// <exception cref="SshOperationTimeoutException">The handle did not get signaled within the specified timeout.
 320        /// <remarks>
 321        /// <para>
 322        /// The blocking wait is also interrupted when either the established channel is closed, the current
 323        /// session is disconnected or an unexpected <see cref="Exception"/> occurred while processing a channel
 324        /// or session event.
 325        /// </para>
 326        /// <para>
 327        /// When both <paramref name="waitHandleA"/> and <paramref name="waitHandleB"/> are signaled during the call,
 328        /// then <c>0</c> is returned.
 329        /// </para>
 330        /// </remarks>
 331        public int WaitAny(WaitHandle waitHandleA, WaitHandle waitHandleB, int millisecondsTimeout)
 0332        {
 0333            var waitHandles = new[]
 0334                {
 0335                    _errorOccuredWaitHandle,
 0336                    _sessionDisconnectedWaitHandle,
 0337                    _channelClosedWaitHandle,
 0338                    waitHandleA,
 0339                    waitHandleB
 0340                };
 341
 0342            var result = WaitHandle.WaitAny(waitHandles, millisecondsTimeout);
 0343            switch (result)
 344            {
 345                case 0:
 0346                    ExceptionDispatchInfo.Capture(_exception).Throw();
 0347                    return -1; // unreached
 348                case 1:
 0349                    throw new SshException("Connection was closed by the server.");
 350                case 2:
 0351                    throw new SshException("Channel was closed.");
 352                case 3:
 0353                    return 0;
 354                case 4:
 0355                    return 1;
 356                case WaitHandle.WaitTimeout:
 0357                    throw new SshOperationTimeoutException("Operation has timed out.");
 358                default:
 0359                    throw new NotImplementedException(string.Format(CultureInfo.InvariantCulture, "WaitAny return value 
 360            }
 0361        }
 362
 363        /// <summary>
 364        /// Waits for any of the elements in the specified array to receive a signal, using a 32-bit signed
 365        /// integer to specify the time interval.
 366        /// </summary>
 367        /// <param name="waitHandles">A <see cref="WaitHandle"/> array - constructed using <see cref="CreateWaitHandleAr
 368        /// <param name="millisecondsTimeout">To number of milliseconds to wait for a <see cref="WaitHandle"/> to get si
 369        /// <returns>
 370        /// The array index of the first non-system object that satisfied the wait.
 371        /// </returns>
 372        /// <exception cref="SshException">The connection was closed by the server.</exception>
 373        /// <exception cref="SshException">The channel was closed.</exception>
 374        /// <exception cref="SshOperationTimeoutException">No object satified the wait and a time interval equivalent to
 375        /// <remarks>
 376        /// For the return value, the index of the first non-system object is considered to be zero.
 377        /// </remarks>
 378        public int WaitAny(WaitHandle[] waitHandles, int millisecondsTimeout)
 3695379        {
 3695380            var result = WaitHandle.WaitAny(waitHandles, millisecondsTimeout);
 3695381            switch (result)
 382            {
 383                case 0:
 0384                    ExceptionDispatchInfo.Capture(_exception).Throw();
 0385                    return -1; // unreached
 386                case 1:
 0387                    throw new SshException("Connection was closed by the server.");
 388                case 2:
 0389                    throw new SshException("Channel was closed.");
 390                case WaitHandle.WaitTimeout:
 0391                    throw new SshOperationTimeoutException("Operation has timed out.");
 392                default:
 3695393                    return result - SystemWaitHandleCount;
 394            }
 3695395        }
 396
 397        /// <summary>
 398        /// Creates a <see cref="WaitHandle"/> array that is composed of system objects and the specified
 399        /// elements.
 400        /// </summary>
 401        /// <param name="waitHandle1">The first <see cref="WaitHandle"/> to wait for.</param>
 402        /// <param name="waitHandle2">The second <see cref="WaitHandle"/> to wait for.</param>
 403        /// <returns>
 404        /// A <see cref="WaitHandle"/> array that is composed of system objects and the specified elements.
 405        /// </returns>
 406        public WaitHandle[] CreateWaitHandleArray(WaitHandle waitHandle1, WaitHandle waitHandle2)
 46407        {
 46408            return new WaitHandle[]
 46409                {
 46410                    _errorOccuredWaitHandle,
 46411                    _sessionDisconnectedWaitHandle,
 46412                    _channelClosedWaitHandle,
 46413                    waitHandle1,
 46414                    waitHandle2
 46415                };
 46416        }
 417
 418        /// <summary>
 419        /// Creates a <see cref="WaitHandle"/> array that is composed of system objects and the specified
 420        /// elements.
 421        /// </summary>
 422        /// <param name="waitHandles">A <see cref="WaitHandle"/> array containing the objects to wait for.</param>
 423        /// <returns>
 424        /// A <see cref="WaitHandle"/> array that is composed of system objects and the specified elements.
 425        /// </returns>
 426        public WaitHandle[] CreateWaitHandleArray(params WaitHandle[] waitHandles)
 0427        {
 0428            var array = new WaitHandle[waitHandles.Length + SystemWaitHandleCount];
 0429            array[0] = _errorOccuredWaitHandle;
 0430            array[1] = _sessionDisconnectedWaitHandle;
 0431            array[2] = _channelClosedWaitHandle;
 432
 0433            for (var i = 0; i < waitHandles.Length; i++)
 0434            {
 0435                array[i + SystemWaitHandleCount] = waitHandles[i];
 0436            }
 437
 0438            return array;
 0439        }
 440
 441        private void Session_Disconnected(object sender, EventArgs e)
 15442        {
 15443            _ = _sessionDisconnectedWaitHandle?.Set();
 444
 15445            SignalDisconnected();
 15446        }
 447
 448        private void Session_ErrorOccured(object sender, ExceptionEventArgs e)
 21449        {
 21450            RaiseError(e.Exception);
 21451        }
 452
 453        private void SignalErrorOccurred(Exception error)
 45454        {
 45455            ErrorOccurred?.Invoke(this, new ExceptionEventArgs(error));
 45456        }
 457
 458        private void SignalDisconnected()
 15459        {
 15460            Disconnected?.Invoke(this, EventArgs.Empty);
 15461        }
 462
 463        private void EnsureSessionIsOpen()
 32192464        {
 32192465            if (!IsOpen)
 15466            {
 15467                throw new InvalidOperationException("The session is not open.");
 468            }
 32177469        }
 470
 471        /// <summary>
 472        /// Unsubscribes the current <see cref="SubsystemSession"/> from session events.
 473        /// </summary>
 474        /// <param name="session">The session.</param>
 475        /// <remarks>
 476        /// Does nothing when <paramref name="session"/> is <see langword="null"/>.
 477        /// </remarks>
 478        private void UnsubscribeFromSessionEvents(ISession session)
 826479        {
 826480            if (session is null)
 12481            {
 12482                return;
 483            }
 484
 814485            session.Disconnected -= Session_Disconnected;
 814486            session.ErrorOccured -= Session_ErrorOccured;
 826487        }
 488
 489        /// <summary>
 490        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
 491        /// </summary>
 492        public void Dispose()
 750493        {
 750494            Dispose(disposing: true);
 750495            GC.SuppressFinalize(this);
 750496        }
 497
 498        /// <summary>
 499        /// Releases unmanaged and - optionally - managed resources.
 500        /// </summary>
 501        /// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources; <see langwor
 502        protected virtual void Dispose(bool disposing)
 957503        {
 957504            if (_isDisposed)
 12505            {
 12506                return;
 507            }
 508
 945509            if (disposing)
 738510            {
 738511                Disconnect();
 512
 738513                _session = null;
 514
 738515                var errorOccuredWaitHandle = _errorOccuredWaitHandle;
 738516                if (errorOccuredWaitHandle != null)
 738517                {
 738518                    _errorOccuredWaitHandle = null;
 738519                    errorOccuredWaitHandle.Dispose();
 738520                }
 521
 738522                var sessionDisconnectedWaitHandle = _sessionDisconnectedWaitHandle;
 738523                if (sessionDisconnectedWaitHandle != null)
 738524                {
 738525                    _sessionDisconnectedWaitHandle = null;
 738526                    sessionDisconnectedWaitHandle.Dispose();
 738527                }
 528
 738529                var channelClosedWaitHandle = _channelClosedWaitHandle;
 738530                if (channelClosedWaitHandle != null)
 738531                {
 738532                    _channelClosedWaitHandle = null;
 738533                    channelClosedWaitHandle.Dispose();
 738534                }
 535
 738536                _isDisposed = true;
 738537            }
 957538        }
 539
 540        /// <summary>
 541        /// Finalizes an instance of the <see cref="SubsystemSession" /> class.
 542        /// </summary>
 543        ~SubsystemSession()
 414544        {
 207545            Dispose(disposing: false);
 414546        }
 547
 548        private void EnsureNotDisposed()
 33779549        {
 550#if NET7_0_OR_GREATER
 33626551            ObjectDisposedException.ThrowIf(_isDisposed, this);
 552#else
 153553            if (_isDisposed)
 8554            {
 8555                throw new ObjectDisposedException(GetType().FullName);
 556            }
 557#endif // NET7_0_OR_GREATER
 33755558        }
 559    }
 560}