|  | @@ -2,7 +2,6 @@
 | 
	
		
			
				|  |  |  using System;
 | 
	
		
			
				|  |  |  using System.Collections.Generic;
 | 
	
		
			
				|  |  |  using System.Diagnostics;
 | 
	
		
			
				|  |  | -using System.Globalization;
 | 
	
		
			
				|  |  |  using System.IO;
 | 
	
		
			
				|  |  |  using System.Text;
 | 
	
		
			
				|  |  |  using System.Text.RegularExpressions;
 | 
	
	
		
			
				|  | @@ -27,16 +26,13 @@ namespace Renci.SshNet
 | 
	
		
			
				|  |  |          private readonly IChannelSession _channel;
 | 
	
		
			
				|  |  |          private readonly byte[] _carriageReturnBytes;
 | 
	
		
			
				|  |  |          private readonly byte[] _lineFeedBytes;
 | 
	
		
			
				|  |  | +        private readonly bool _noTerminal;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          private readonly object _sync = new object();
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        private readonly byte[] _writeBuffer;
 | 
	
		
			
				|  |  | -        private readonly bool _noTerminal;
 | 
	
		
			
				|  |  | -        private int _writeLength; // The length of the data in _writeBuffer.
 | 
	
		
			
				|  |  | +        private System.Net.ArrayBuffer _readBuffer;
 | 
	
		
			
				|  |  | +        private System.Net.ArrayBuffer _writeBuffer;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        private byte[] _readBuffer;
 | 
	
		
			
				|  |  | -        private int _readHead; // The index from which the data starts in _readBuffer.
 | 
	
		
			
				|  |  | -        private int _readTail; // The index at which to add new data into _readBuffer.
 | 
	
		
			
				|  |  |          private bool _disposed;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
	
		
			
				|  | @@ -66,23 +62,11 @@ namespace Renci.SshNet
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  lock (_sync)
 | 
	
		
			
				|  |  |                  {
 | 
	
		
			
				|  |  | -                    AssertValid();
 | 
	
		
			
				|  |  | -                    return _readTail != _readHead;
 | 
	
		
			
				|  |  | +                    return _readBuffer.ActiveLength > 0;
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        [Conditional("DEBUG")]
 | 
	
		
			
				|  |  | -        private void AssertValid()
 | 
	
		
			
				|  |  | -        {
 | 
	
		
			
				|  |  | -            Debug.Assert(Monitor.IsEntered(_sync), $"Should be in lock on {nameof(_sync)}");
 | 
	
		
			
				|  |  | -            Debug.Assert(_readHead >= 0, $"{nameof(_readHead)} should be non-negative but is {_readHead.ToString(CultureInfo.InvariantCulture)}");
 | 
	
		
			
				|  |  | -            Debug.Assert(_readTail >= 0, $"{nameof(_readTail)} should be non-negative but is {_readTail.ToString(CultureInfo.InvariantCulture)}");
 | 
	
		
			
				|  |  | -            Debug.Assert(_readHead <= _readBuffer.Length, $"{nameof(_readHead)} should be <= {nameof(_readBuffer)}.Length but is {_readHead.ToString(CultureInfo.InvariantCulture)}");
 | 
	
		
			
				|  |  | -            Debug.Assert(_readTail <= _readBuffer.Length, $"{nameof(_readTail)} should be <= {nameof(_readBuffer)}.Length but is {_readTail.ToString(CultureInfo.InvariantCulture)}");
 | 
	
		
			
				|  |  | -            Debug.Assert(_readHead <= _readTail, $"Should have {nameof(_readHead)} <= {nameof(_readTail)} but have {_readHead.ToString(CultureInfo.InvariantCulture)} <= {_readTail.ToString(CultureInfo.InvariantCulture)}");
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  |          /// Initializes a new instance of the <see cref="ShellStream"/> class.
 | 
	
		
			
				|  |  |          /// </summary>
 | 
	
	
		
			
				|  | @@ -180,8 +164,8 @@ namespace Renci.SshNet
 | 
	
		
			
				|  |  |              _session.Disconnected += Session_Disconnected;
 | 
	
		
			
				|  |  |              _session.ErrorOccured += Session_ErrorOccured;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            _readBuffer = new byte[bufferSize];
 | 
	
		
			
				|  |  | -            _writeBuffer = new byte[bufferSize];
 | 
	
		
			
				|  |  | +            _readBuffer = new System.Net.ArrayBuffer(bufferSize);
 | 
	
		
			
				|  |  | +            _writeBuffer = new System.Net.ArrayBuffer(bufferSize);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              _noTerminal = noTerminal;
 | 
	
		
			
				|  |  |          }
 | 
	
	
		
			
				|  | @@ -233,12 +217,14 @@ namespace Renci.SshNet
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  |              ThrowHelper.ThrowObjectDisposedIf(_disposed, this);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            Debug.Assert(_writeLength >= 0 && _writeLength <= _writeBuffer.Length);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            if (_writeLength > 0)
 | 
	
		
			
				|  |  | +            if (_writeBuffer.ActiveLength > 0)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | -                _channel.SendData(_writeBuffer, 0, _writeLength);
 | 
	
		
			
				|  |  | -                _writeLength = 0;
 | 
	
		
			
				|  |  | +                _channel.SendData(
 | 
	
		
			
				|  |  | +                    _writeBuffer.DangerousGetUnderlyingBuffer(),
 | 
	
		
			
				|  |  | +                    _writeBuffer.ActiveStartOffset,
 | 
	
		
			
				|  |  | +                    _writeBuffer.ActiveLength);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                _writeBuffer.Discard(_writeBuffer.ActiveLength);
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -252,8 +238,7 @@ namespace Renci.SshNet
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  lock (_sync)
 | 
	
		
			
				|  |  |                  {
 | 
	
		
			
				|  |  | -                    AssertValid();
 | 
	
		
			
				|  |  | -                    return _readTail - _readHead;
 | 
	
		
			
				|  |  | +                    return _readBuffer.ActiveLength;
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |          }
 | 
	
	
		
			
				|  | @@ -385,23 +370,19 @@ namespace Renci.SshNet
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  while (true)
 | 
	
		
			
				|  |  |                  {
 | 
	
		
			
				|  |  | -                    AssertValid();
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |                      var searchHead = lookback == -1
 | 
	
		
			
				|  |  | -                        ? _readHead
 | 
	
		
			
				|  |  | -                        : Math.Max(_readTail - lookback, _readHead);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                    Debug.Assert(_readHead <= searchHead && searchHead <= _readTail);
 | 
	
		
			
				|  |  | +                        ? 0
 | 
	
		
			
				|  |  | +                        : Math.Max(0, _readBuffer.ActiveLength - lookback);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                    var indexOfMatch = _readBuffer.AsSpan(searchHead, _readTail - searchHead).IndexOf(expectBytes);
 | 
	
		
			
				|  |  | +                    var indexOfMatch = _readBuffer.ActiveReadOnlySpan.Slice(searchHead).IndexOf(expectBytes);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                      if (indexOfMatch >= 0)
 | 
	
		
			
				|  |  |                      {
 | 
	
		
			
				|  |  | -                        var returnText = _encoding.GetString(_readBuffer, _readHead, searchHead - _readHead + indexOfMatch + expectBytes.Length);
 | 
	
		
			
				|  |  | +                        var readLength = searchHead + indexOfMatch + expectBytes.Length;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                        _readHead = searchHead + indexOfMatch + expectBytes.Length;
 | 
	
		
			
				|  |  | +                        var returnText = GetString(readLength);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                        AssertValid();
 | 
	
		
			
				|  |  | +                        _readBuffer.Discard(readLength);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                          return returnText;
 | 
	
		
			
				|  |  |                      }
 | 
	
	
		
			
				|  | @@ -471,9 +452,7 @@ namespace Renci.SshNet
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  while (true)
 | 
	
		
			
				|  |  |                  {
 | 
	
		
			
				|  |  | -                    AssertValid();
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                    var bufferText = _encoding.GetString(_readBuffer, _readHead, _readTail - _readHead);
 | 
	
		
			
				|  |  | +                    var bufferText = GetString(_readBuffer.ActiveLength);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                      var searchStart = lookback == -1
 | 
	
		
			
				|  |  |                          ? 0
 | 
	
	
		
			
				|  | @@ -496,9 +475,7 @@ namespace Renci.SshNet
 | 
	
		
			
				|  |  |                          {
 | 
	
		
			
				|  |  |                              var returnText = bufferText.Substring(0, match.Index + match.Length);
 | 
	
		
			
				|  |  |  #endif
 | 
	
		
			
				|  |  | -                            _readHead += _encoding.GetByteCount(returnText);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                            AssertValid();
 | 
	
		
			
				|  |  | +                            _readBuffer.Discard(_encoding.GetByteCount(returnText));
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                              expectAction.Action(returnText);
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -659,48 +636,40 @@ namespace Renci.SshNet
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  while (true)
 | 
	
		
			
				|  |  |                  {
 | 
	
		
			
				|  |  | -                    AssertValid();
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                    var indexOfCr = _readBuffer.AsSpan(_readHead, _readTail - _readHead).IndexOf(_carriageReturnBytes);
 | 
	
		
			
				|  |  | +                    var indexOfCr = _readBuffer.ActiveReadOnlySpan.IndexOf(_carriageReturnBytes);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                      if (indexOfCr >= 0)
 | 
	
		
			
				|  |  |                      {
 | 
	
		
			
				|  |  |                          // We have found \r. We only need to search for \n up to and just after the \r
 | 
	
		
			
				|  |  |                          // (in order to consume \r\n if we can).
 | 
	
		
			
				|  |  | -                        var indexOfLf = indexOfCr + _carriageReturnBytes.Length + _lineFeedBytes.Length <= _readTail - _readHead
 | 
	
		
			
				|  |  | -                            ? _readBuffer.AsSpan(_readHead, indexOfCr + _carriageReturnBytes.Length + _lineFeedBytes.Length).IndexOf(_lineFeedBytes)
 | 
	
		
			
				|  |  | -                            : _readBuffer.AsSpan(_readHead, indexOfCr).IndexOf(_lineFeedBytes);
 | 
	
		
			
				|  |  | +                        var indexOfLf = indexOfCr + _carriageReturnBytes.Length + _lineFeedBytes.Length <= _readBuffer.ActiveLength
 | 
	
		
			
				|  |  | +                            ? _readBuffer.ActiveReadOnlySpan.Slice(0, indexOfCr + _carriageReturnBytes.Length + _lineFeedBytes.Length).IndexOf(_lineFeedBytes)
 | 
	
		
			
				|  |  | +                            : _readBuffer.ActiveReadOnlySpan.Slice(0, indexOfCr).IndexOf(_lineFeedBytes);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                          if (indexOfLf >= 0 && indexOfLf < indexOfCr)
 | 
	
		
			
				|  |  |                          {
 | 
	
		
			
				|  |  |                              // If there is \n before the \r, then return up to the \n
 | 
	
		
			
				|  |  | -                            var returnText = _encoding.GetString(_readBuffer, _readHead, indexOfLf);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                            _readHead += indexOfLf + _lineFeedBytes.Length;
 | 
	
		
			
				|  |  | +                            var returnText = GetString(indexOfLf);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                            AssertValid();
 | 
	
		
			
				|  |  | +                            _readBuffer.Discard(indexOfLf + _lineFeedBytes.Length);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                              return returnText;
 | 
	
		
			
				|  |  |                          }
 | 
	
		
			
				|  |  |                          else if (indexOfLf == indexOfCr + _carriageReturnBytes.Length)
 | 
	
		
			
				|  |  |                          {
 | 
	
		
			
				|  |  |                              // If we have \r\n, then consume both
 | 
	
		
			
				|  |  | -                            var returnText = _encoding.GetString(_readBuffer, _readHead, indexOfCr);
 | 
	
		
			
				|  |  | +                            var returnText = GetString(indexOfCr);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                            _readHead += indexOfCr + _carriageReturnBytes.Length + _lineFeedBytes.Length;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                            AssertValid();
 | 
	
		
			
				|  |  | +                            _readBuffer.Discard(indexOfCr + _carriageReturnBytes.Length + _lineFeedBytes.Length);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                              return returnText;
 | 
	
		
			
				|  |  |                          }
 | 
	
		
			
				|  |  |                          else
 | 
	
		
			
				|  |  |                          {
 | 
	
		
			
				|  |  |                              // Return up to the \r
 | 
	
		
			
				|  |  | -                            var returnText = _encoding.GetString(_readBuffer, _readHead, indexOfCr);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                            _readHead += indexOfCr + _carriageReturnBytes.Length;
 | 
	
		
			
				|  |  | +                            var returnText = GetString(indexOfCr);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                            AssertValid();
 | 
	
		
			
				|  |  | +                            _readBuffer.Discard(indexOfCr + _carriageReturnBytes.Length);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                              return returnText;
 | 
	
		
			
				|  |  |                          }
 | 
	
	
		
			
				|  | @@ -708,15 +677,13 @@ namespace Renci.SshNet
 | 
	
		
			
				|  |  |                      else
 | 
	
		
			
				|  |  |                      {
 | 
	
		
			
				|  |  |                          // There is no \r. What about \n?
 | 
	
		
			
				|  |  | -                        var indexOfLf = _readBuffer.AsSpan(_readHead, _readTail - _readHead).IndexOf(_lineFeedBytes);
 | 
	
		
			
				|  |  | +                        var indexOfLf = _readBuffer.ActiveReadOnlySpan.IndexOf(_lineFeedBytes);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                          if (indexOfLf >= 0)
 | 
	
		
			
				|  |  |                          {
 | 
	
		
			
				|  |  | -                            var returnText = _encoding.GetString(_readBuffer, _readHead, indexOfLf);
 | 
	
		
			
				|  |  | +                            var returnText = GetString(indexOfLf);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                            _readHead += indexOfLf + _lineFeedBytes.Length;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                            AssertValid();
 | 
	
		
			
				|  |  | +                            _readBuffer.Discard(indexOfLf + _lineFeedBytes.Length);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                              return returnText;
 | 
	
		
			
				|  |  |                          }
 | 
	
	
		
			
				|  | @@ -724,11 +691,11 @@ namespace Renci.SshNet
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                      if (_disposed)
 | 
	
		
			
				|  |  |                      {
 | 
	
		
			
				|  |  | -                        var lastLine = _readHead == _readTail
 | 
	
		
			
				|  |  | +                        var lastLine = _readBuffer.ActiveLength == 0
 | 
	
		
			
				|  |  |                              ? null
 | 
	
		
			
				|  |  | -                            : _encoding.GetString(_readBuffer, _readHead, _readTail - _readHead);
 | 
	
		
			
				|  |  | +                            : GetString(_readBuffer.ActiveLength);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                        _readHead = _readTail = 0;
 | 
	
		
			
				|  |  | +                        _readBuffer.Discard(_readBuffer.ActiveLength);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                          return lastLine;
 | 
	
		
			
				|  |  |                      }
 | 
	
	
		
			
				|  | @@ -776,11 +743,9 @@ namespace Renci.SshNet
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  |              lock (_sync)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | -                AssertValid();
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                var text = _encoding.GetString(_readBuffer, _readHead, _readTail - _readHead);
 | 
	
		
			
				|  |  | +                var text = GetString(_readBuffer.ActiveLength);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                _readHead = _readTail = 0;
 | 
	
		
			
				|  |  | +                _readBuffer.Discard(_readBuffer.ActiveLength);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                  return text;
 | 
	
		
			
				|  |  |              }
 | 
	
	
		
			
				|  | @@ -794,27 +759,54 @@ namespace Renci.SshNet
 | 
	
		
			
				|  |  |  #endif
 | 
	
		
			
				|  |  |              ValidateBufferArguments(buffer, offset, count);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +            return Read(buffer.AsSpan(offset, count));
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +#if NETSTANDARD2_1 || NET
 | 
	
		
			
				|  |  | +        /// <inheritdoc/>
 | 
	
		
			
				|  |  | +        public override int Read(Span<byte> buffer)
 | 
	
		
			
				|  |  | +#else
 | 
	
		
			
				|  |  | +        private int Read(Span<byte> buffer)
 | 
	
		
			
				|  |  | +#endif
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  |              lock (_sync)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | -                while (_readHead == _readTail && !_disposed)
 | 
	
		
			
				|  |  | +                while (_readBuffer.ActiveLength == 0 && !_disposed)
 | 
	
		
			
				|  |  |                  {
 | 
	
		
			
				|  |  |                      _ = Monitor.Wait(_sync);
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                AssertValid();
 | 
	
		
			
				|  |  | +                var bytesRead = Math.Min(buffer.Length, _readBuffer.ActiveLength);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                var bytesRead = Math.Min(count, _readTail - _readHead);
 | 
	
		
			
				|  |  | +                _readBuffer.ActiveReadOnlySpan.Slice(0, bytesRead).CopyTo(buffer);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                Buffer.BlockCopy(_readBuffer, _readHead, buffer, offset, bytesRead);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                _readHead += bytesRead;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                AssertValid();
 | 
	
		
			
				|  |  | +                _readBuffer.Discard(bytesRead);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                  return bytesRead;
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +#if NET
 | 
	
		
			
				|  |  | +        /// <inheritdoc/>
 | 
	
		
			
				|  |  | +        public override int ReadByte()
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            byte b = default;
 | 
	
		
			
				|  |  | +            var read = Read(new Span<byte>(ref b));
 | 
	
		
			
				|  |  | +            return read == 0 ? -1 : b;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +#endif
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        private string GetString(int length)
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            Debug.Assert(Monitor.IsEntered(_sync));
 | 
	
		
			
				|  |  | +            Debug.Assert(length <= _readBuffer.ActiveLength);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            return _encoding.GetString(
 | 
	
		
			
				|  |  | +                _readBuffer.DangerousGetUnderlyingBuffer(),
 | 
	
		
			
				|  |  | +                _readBuffer.ActiveStartOffset,
 | 
	
		
			
				|  |  | +                length);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  |          /// Writes the specified text to the shell.
 | 
	
		
			
				|  |  |          /// </summary>
 | 
	
	
		
			
				|  | @@ -831,9 +823,7 @@ namespace Renci.SshNet
 | 
	
		
			
				|  |  |                  return;
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            var data = _encoding.GetBytes(text);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            Write(data, 0, data.Length);
 | 
	
		
			
				|  |  | +            Write(_encoding.GetBytes(text));
 | 
	
		
			
				|  |  |              Flush();
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -845,27 +835,43 @@ namespace Renci.SshNet
 | 
	
		
			
				|  |  |  #endif
 | 
	
		
			
				|  |  |              ValidateBufferArguments(buffer, offset, count);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +            Write(buffer.AsSpan(offset, count));
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +#if NETSTANDARD2_1 || NET
 | 
	
		
			
				|  |  | +        /// <inheritdoc/>
 | 
	
		
			
				|  |  | +        public override void Write(ReadOnlySpan<byte> buffer)
 | 
	
		
			
				|  |  | +#else
 | 
	
		
			
				|  |  | +        private void Write(ReadOnlySpan<byte> buffer)
 | 
	
		
			
				|  |  | +#endif
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  |              ThrowHelper.ThrowObjectDisposedIf(_disposed, this);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            while (count > 0)
 | 
	
		
			
				|  |  | +            while (!buffer.IsEmpty)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | -                if (_writeLength == _writeBuffer.Length)
 | 
	
		
			
				|  |  | +                if (_writeBuffer.AvailableLength == 0)
 | 
	
		
			
				|  |  |                  {
 | 
	
		
			
				|  |  |                      Flush();
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                var bytesToCopy = Math.Min(count, _writeBuffer.Length - _writeLength);
 | 
	
		
			
				|  |  | +                var bytesToCopy = Math.Min(buffer.Length, _writeBuffer.AvailableLength);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                Buffer.BlockCopy(buffer, offset, _writeBuffer, _writeLength, bytesToCopy);
 | 
	
		
			
				|  |  | +                Debug.Assert(bytesToCopy > 0);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                offset += bytesToCopy;
 | 
	
		
			
				|  |  | -                count -= bytesToCopy;
 | 
	
		
			
				|  |  | -                _writeLength += bytesToCopy;
 | 
	
		
			
				|  |  | +                buffer.Slice(0, bytesToCopy).CopyTo(_writeBuffer.AvailableSpan);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                Debug.Assert(_writeLength >= 0 && _writeLength <= _writeBuffer.Length);
 | 
	
		
			
				|  |  | +                _writeBuffer.Commit(bytesToCopy);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                buffer = buffer.Slice(bytesToCopy);
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +        /// <inheritdoc/>
 | 
	
		
			
				|  |  | +        public override void WriteByte(byte value)
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            Write([value]);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  |          /// Writes the line to the shell.
 | 
	
		
			
				|  |  |          /// </summary>
 | 
	
	
		
			
				|  | @@ -940,45 +946,11 @@ namespace Renci.SshNet
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  |              lock (_sync)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | -                AssertValid();
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                // Ensure sufficient buffer space and copy the new data in.
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                if (_readBuffer.Length - _readTail >= e.Data.Length)
 | 
	
		
			
				|  |  | -                {
 | 
	
		
			
				|  |  | -                    // If there is enough space after _tail for the new data,
 | 
	
		
			
				|  |  | -                    // then copy the data there.
 | 
	
		
			
				|  |  | -                    Buffer.BlockCopy(e.Data, 0, _readBuffer, _readTail, e.Data.Length);
 | 
	
		
			
				|  |  | -                    _readTail += e.Data.Length;
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -                else
 | 
	
		
			
				|  |  | -                {
 | 
	
		
			
				|  |  | -                    // We can't fit the new data after _tail.
 | 
	
		
			
				|  |  | +                _readBuffer.EnsureAvailableSpace(e.Data.Length);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                    var newLength = _readTail - _readHead + e.Data.Length;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                    if (newLength <= _readBuffer.Length)
 | 
	
		
			
				|  |  | -                    {
 | 
	
		
			
				|  |  | -                        // If there is sufficient space at the start of the buffer,
 | 
	
		
			
				|  |  | -                        // then move the current data to the start of the buffer.
 | 
	
		
			
				|  |  | -                        Buffer.BlockCopy(_readBuffer, _readHead, _readBuffer, 0, _readTail - _readHead);
 | 
	
		
			
				|  |  | -                    }
 | 
	
		
			
				|  |  | -                    else
 | 
	
		
			
				|  |  | -                    {
 | 
	
		
			
				|  |  | -                        // Otherwise, we're gonna need a bigger buffer.
 | 
	
		
			
				|  |  | -                        var newBuffer = new byte[Math.Max(newLength, _readBuffer.Length * 2)];
 | 
	
		
			
				|  |  | -                        Buffer.BlockCopy(_readBuffer, _readHead, newBuffer, 0, _readTail - _readHead);
 | 
	
		
			
				|  |  | -                        _readBuffer = newBuffer;
 | 
	
		
			
				|  |  | -                    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                    // Copy the new data into the freed-up space.
 | 
	
		
			
				|  |  | -                    Buffer.BlockCopy(e.Data, 0, _readBuffer, _readTail - _readHead, e.Data.Length);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                    _readHead = 0;
 | 
	
		
			
				|  |  | -                    _readTail = newLength;
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | +                e.Data.AsSpan().CopyTo(_readBuffer.AvailableSpan);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                AssertValid();
 | 
	
		
			
				|  |  | +                _readBuffer.Commit(e.Data.Length);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                  Monitor.PulseAll(_sync);
 | 
	
		
			
				|  |  |              }
 |