Kaynağa Gözat

Pass buffer size to ShellStream ctor.
Fixes issue #303.

Added tests for issue #303 and PR #211.

Gert Driesen 8 yıl önce
ebeveyn
işleme
c3d43b67ec
19 değiştirilmiş dosya ile 1822 ekleme ve 19 silme
  1. 37 1
      src/Renci.SshNet.Tests.NET35/Renci.SshNet.Tests.NET35.csproj
  2. 134 0
      src/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream.cs
  3. 10 2
      src/Renci.SshNet.Tests/Classes/ShellStreamTest.cs
  4. 135 0
      src/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferEmptyAndWriteLessBytesThanBufferSize.cs
  5. 142 0
      src/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferEmptyAndWriteMoreBytesThanBufferSize.cs
  6. 129 0
      src/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferEmptyAndWriteNumberOfBytesEqualToBufferSize.cs
  7. 126 0
      src/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferEmptyAndWriteZeroBytes.cs
  8. 135 0
      src/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferFullAndWriteLessBytesThanBufferSize.cs
  9. 133 0
      src/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferFullAndWriteZeroBytes.cs
  10. 141 0
      src/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferNotEmptyAndWriteLessBytesThanBufferCanContain.cs
  11. 149 0
      src/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferNotEmptyAndWriteMoreBytesThanBufferCanContain.cs
  12. 133 0
      src/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferNotEmptyAndWriteZeroBytes.cs
  13. 151 0
      src/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSizeAndTerminalModes_Connected.cs
  14. 147 0
      src/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSize_Connected.cs
  15. 12 0
      src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj
  16. 34 0
      src/Renci.SshNet/IServiceFactory.cs
  17. 30 0
      src/Renci.SshNet/ServiceFactory.cs
  18. 40 12
      src/Renci.SshNet/ShellStream.cs
  19. 4 4
      src/Renci.SshNet/SshClient.cs

+ 37 - 1
src/Renci.SshNet.Tests.NET35/Renci.SshNet.Tests.NET35.csproj

@@ -912,6 +912,9 @@
     <Compile Include="..\Renci.SshNet.Tests\Classes\ServiceFactoryTest_CreateSftpFileReader_FileSizeIsZero.cs">
       <Link>Classes\ServiceFactoryTest_CreateSftpFileReader_FileSizeIsZero.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet.Tests\Classes\ServiceFactoryTest_CreateShellStream.cs">
+      <Link>Classes\ServiceFactoryTest_CreateShellStream.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet.Tests\Classes\SessionTest.cs">
       <Link>Classes\SessionTest.cs</Link>
     </Compile>
@@ -1425,12 +1428,45 @@
     <Compile Include="..\Renci.SshNet.Tests\Classes\ShellStreamTest.cs">
       <Link>Classes\ShellStreamTest.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet.Tests\Classes\ShellStreamTest_Write_WriteBufferEmptyAndWriteLessBytesThanBufferSize.cs">
+      <Link>Classes\ShellStreamTest_Write_WriteBufferEmptyAndWriteLessBytesThanBufferSize.cs</Link>
+    </Compile>
+    <Compile Include="..\Renci.SshNet.Tests\Classes\ShellStreamTest_Write_WriteBufferEmptyAndWriteMoreBytesThanBufferSize.cs">
+      <Link>Classes\ShellStreamTest_Write_WriteBufferEmptyAndWriteMoreBytesThanBufferSize.cs</Link>
+    </Compile>
+    <Compile Include="..\Renci.SshNet.Tests\Classes\ShellStreamTest_Write_WriteBufferEmptyAndWriteNumberOfBytesEqualToBufferSize.cs">
+      <Link>Classes\ShellStreamTest_Write_WriteBufferEmptyAndWriteNumberOfBytesEqualToBufferSize.cs</Link>
+    </Compile>
+    <Compile Include="..\Renci.SshNet.Tests\Classes\ShellStreamTest_Write_WriteBufferEmptyAndWriteZeroBytes.cs">
+      <Link>Classes\ShellStreamTest_Write_WriteBufferEmptyAndWriteZeroBytes.cs</Link>
+    </Compile>
+    <Compile Include="..\Renci.SshNet.Tests\Classes\ShellStreamTest_Write_WriteBufferFullAndWriteLessBytesThanBufferSize.cs">
+      <Link>Classes\ShellStreamTest_Write_WriteBufferFullAndWriteLessBytesThanBufferSize.cs</Link>
+    </Compile>
+    <Compile Include="..\Renci.SshNet.Tests\Classes\ShellStreamTest_Write_WriteBufferFullAndWriteZeroBytes.cs">
+      <Link>Classes\ShellStreamTest_Write_WriteBufferFullAndWriteZeroBytes.cs</Link>
+    </Compile>
+    <Compile Include="..\Renci.SshNet.Tests\Classes\ShellStreamTest_Write_WriteBufferNotEmptyAndWriteLessBytesThanBufferCanContain.cs">
+      <Link>Classes\ShellStreamTest_Write_WriteBufferNotEmptyAndWriteLessBytesThanBufferCanContain.cs</Link>
+    </Compile>
+    <Compile Include="..\Renci.SshNet.Tests\Classes\ShellStreamTest_Write_WriteBufferNotEmptyAndWriteMoreBytesThanBufferCanContain.cs">
+      <Link>Classes\ShellStreamTest_Write_WriteBufferNotEmptyAndWriteMoreBytesThanBufferCanContain.cs</Link>
+    </Compile>
+    <Compile Include="..\Renci.SshNet.Tests\Classes\ShellStreamTest_Write_WriteBufferNotEmptyAndWriteZeroBytes.cs">
+      <Link>Classes\ShellStreamTest_Write_WriteBufferNotEmptyAndWriteZeroBytes.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet.Tests\Classes\ShellTestTest.cs">
       <Link>Classes\ShellTestTest.cs</Link>
     </Compile>
     <Compile Include="..\Renci.SshNet.Tests\Classes\SshClientTest.cs">
       <Link>Classes\SshClientTest.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet.Tests\Classes\SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSizeAndTerminalModes_Connected.cs">
+      <Link>Classes\SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSizeAndTerminalModes_Connected.cs</Link>
+    </Compile>
+    <Compile Include="..\Renci.SshNet.Tests\Classes\SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSize_Connected.cs">
+      <Link>Classes\SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSize_Connected.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet.Tests\Classes\SshClientTest_Disconnect_ForwardedPortStarted.cs">
       <Link>Classes\SshClientTest_Disconnect_ForwardedPortStarted.cs</Link>
     </Compile>
@@ -1632,7 +1668,7 @@
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <ProjectExtensions>
     <VisualStudio>
-      <UserProperties ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" ProjectLinkReference="c45379b9-17b1-4e89-bc2e-6d41726413e8" />
+      <UserProperties ProjectLinkReference="c45379b9-17b1-4e89-bc2e-6d41726413e8" ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" />
     </VisualStudio>
   </ProjectExtensions>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 

+ 134 - 0
src/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream.cs

@@ -0,0 +1,134 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ServiceFactoryTest_CreateShellStream
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private Mock<IChannelSession> _channelSessionMock;
+        private MockSequence _mockSequence;
+        private ServiceFactory _serviceFactory;
+        private string _terminalName;
+        private uint _columns;
+        private uint _rows;
+        private uint _width;
+        private uint _height;
+        private IDictionary<TerminalModes, uint> _terminalModeValues;
+        private int _bufferSize;
+        private ShellStream _shellStream;
+
+        private void SetupData()
+        {
+            var random = new Random();
+
+            _terminalName = random.Next().ToString();
+            _columns = (uint) random.Next();
+            _rows = (uint) random.Next();
+            _width = (uint) random.Next();
+            _height = (uint) random.Next();
+            _terminalModeValues = new Dictionary<TerminalModes, uint>();
+            _bufferSize = random.Next();
+        }
+
+        private void CreateMocks()
+        {
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+            _channelSessionMock = new Mock<IChannelSession>(MockBehavior.Strict);
+        }
+
+        private void SetupMocks()
+        {
+            _mockSequence = new MockSequence();
+
+            _sessionMock.InSequence(_mockSequence)
+                        .Setup(p => p.ConnectionInfo)
+                        .Returns(_connectionInfoMock.Object);
+            _connectionInfoMock.InSequence(_mockSequence)
+                               .Setup(p => p.Encoding)
+                               .Returns(new UTF8Encoding());
+            _sessionMock.InSequence(_mockSequence)
+                        .Setup(p => p.CreateChannelSession())
+                        .Returns(_channelSessionMock.Object);
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.Open());
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.SendPseudoTerminalRequest(_terminalName,
+                                                                       _columns,
+                                                                       _rows,
+                                                                       _width,
+                                                                       _height,
+                                                                       _terminalModeValues))
+                               .Returns(true);
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.SendShellRequest())
+                               .Returns(true);
+        }
+
+        private void Arrange()
+        {
+            SetupData();
+            CreateMocks();
+            SetupMocks();
+
+            _serviceFactory = new ServiceFactory();
+        }
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void Act()
+        {
+            _shellStream = _serviceFactory.CreateShellStream(_sessionMock.Object,
+                                                             _terminalName,
+                                                             _columns,
+                                                             _rows,
+                                                             _width,
+                                                             _height,
+                                                             _terminalModeValues,
+                                                             _bufferSize);
+        }
+
+        [TestMethod]
+        public void CreateShellStreamShouldNotReturnNull()
+        {
+            Assert.IsNotNull(_shellStream);
+        }
+
+        [TestMethod]
+        public void BufferSizeOfShellStreamShouldBeValuePassedToCreateShellStream()
+        {
+            Assert.AreEqual(_bufferSize, _shellStream.BufferSize);
+        }
+
+        [TestMethod]
+        public void SendPseudoTerminalRequestShouldHaveBeenInvokedOnce()
+        {
+            _channelSessionMock.Verify(p => p.SendPseudoTerminalRequest(_terminalName,
+                                                                        _columns,
+                                                                        _rows,
+                                                                        _width,
+                                                                        _height,
+                                                                        _terminalModeValues),
+                                       Times.Once);
+        }
+
+        [TestMethod]
+        public void SendShellRequestShouldHaveBeenInvokedOnce()
+        {
+            _channelSessionMock.Verify(p => p.SendShellRequest(), Times.Once);
+        }
+    }
+}

+ 10 - 2
src/Renci.SshNet.Tests/Classes/ShellStreamTest.cs

@@ -26,6 +26,7 @@ namespace Renci.SshNet.Tests.Classes
         private uint _widthPixels;
         private uint _heightPixels;
         private Dictionary<TerminalModes, uint> _terminalModes;
+        private int _bufferSize;
         private Mock<IChannelSession> _channelSessionMock;
 
         protected override void OnInit()
@@ -39,6 +40,7 @@ namespace Renci.SshNet.Tests.Classes
             _widthPixels = (uint)random.Next();
             _heightPixels = (uint)random.Next();
             _terminalModes = new Dictionary<TerminalModes, uint>();
+            _bufferSize = random.Next(100, 500);
 
             _encoding = Encoding.UTF8;
             _sessionMock = new Mock<ISession>(MockBehavior.Strict);
@@ -111,8 +113,14 @@ namespace Renci.SshNet.Tests.Classes
                 _widthPixels, _heightPixels, _terminalModes)).Returns(true);
             _channelSessionMock.Setup(p => p.SendShellRequest()).Returns(true);
 
-            return new ShellStream(_sessionMock.Object, _terminalName, _widthColumns, _heightRows,
-                _widthPixels, _heightPixels, _terminalModes);
+            return new ShellStream(_sessionMock.Object,
+                                   _terminalName,
+                                   _widthColumns,
+                                   _heightRows,
+                                   _widthPixels,
+                                   _heightPixels,
+                                   _terminalModes,
+                                   _bufferSize);
         }
     }
 }

+ 135 - 0
src/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferEmptyAndWriteLessBytesThanBufferSize.cs

@@ -0,0 +1,135 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Abstractions;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ShellStreamTest_Write_WriteBufferEmptyAndWriteLessBytesThanBufferSize
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private Mock<IChannelSession> _channelSessionMock;
+        private string _terminalName;
+        private uint _widthColumns;
+        private uint _heightRows;
+        private uint _widthPixels;
+        private uint _heightPixels;
+        private Dictionary<TerminalModes, uint> _terminalModes;
+        private ShellStream _shellStream;
+        private int _bufferSize;
+
+        private byte[] _data;
+        private int _offset;
+        private int _count;
+        private MockSequence _mockSequence;
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void SetupData()
+        {
+            var random = new Random();
+
+            _terminalName = random.Next().ToString();
+            _widthColumns = (uint) random.Next();
+            _heightRows = (uint) random.Next();
+            _widthPixels = (uint) random.Next();
+            _heightPixels = (uint) random.Next();
+            _terminalModes = new Dictionary<TerminalModes, uint>();
+            _bufferSize = random.Next(100, 1000);
+
+            _data = CryptoAbstraction.GenerateRandom(_bufferSize - 10);
+            _offset = random.Next(1, 5);
+            _count = _data.Length - _offset - random.Next(1, 10);
+        }
+
+        private void CreateMocks()
+        {
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+            _channelSessionMock = new Mock<IChannelSession>(MockBehavior.Strict);
+        }
+
+        private void SetupMocks()
+        {
+            _mockSequence = new MockSequence();
+
+            _sessionMock.InSequence(_mockSequence)
+                        .Setup(p => p.ConnectionInfo)
+                        .Returns(_connectionInfoMock.Object);
+            _connectionInfoMock.InSequence(_mockSequence)
+                               .Setup(p => p.Encoding)
+                               .Returns(new UTF8Encoding());
+            _sessionMock.InSequence(_mockSequence)
+                        .Setup(p => p.CreateChannelSession())
+                        .Returns(_channelSessionMock.Object);
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.Open());
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.SendPseudoTerminalRequest(_terminalName,
+                                                                       _widthColumns,
+                                                                       _heightRows,
+                                                                       _widthPixels,
+                                                                       _heightPixels,
+                                                                       _terminalModes))
+                               .Returns(true);
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.SendShellRequest())
+                               .Returns(true);
+        }
+
+        private void Arrange()
+        {
+            SetupData();
+            CreateMocks();
+            SetupMocks();
+
+            _shellStream = new ShellStream(_sessionMock.Object,
+                                           _terminalName,
+                                           _widthColumns,
+                                           _heightRows,
+                                           _widthPixels,
+                                           _heightPixels,
+                                           _terminalModes,
+                                           _bufferSize);
+        }
+
+        private void Act()
+        {
+            _shellStream.Write(_data, _offset, _count);
+        }
+
+        [TestMethod]
+        public void NoDataShouldBeSentToServer()
+        {
+            _channelSessionMock.Verify(p => p.SendData(It.IsAny<byte[]>()), Times.Never);
+        }
+
+        [TestMethod]
+        public void FlushShouldSendWrittenBytesToServer()
+        {
+            byte[] bytesSent = null;
+
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.SendData(It.IsAny<byte[]>()))
+                               .Callback<byte[]>(data => bytesSent = data);
+
+            _shellStream.Flush();
+
+            Assert.IsNotNull(bytesSent);
+            Assert.IsTrue(_data.Take(_offset, _count).IsEqualTo(bytesSent));
+
+            _channelSessionMock.Verify(p => p.SendData(It.IsAny<byte[]>()), Times.Once);
+        }
+    }
+}

+ 142 - 0
src/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferEmptyAndWriteMoreBytesThanBufferSize.cs

@@ -0,0 +1,142 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Abstractions;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ShellStreamTest_Write_WriteBufferEmptyAndWriteMoreBytesThanBufferSize
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private Mock<IChannelSession> _channelSessionMock;
+        private MockSequence _mockSequence;
+        private string _terminalName;
+        private uint _widthColumns;
+        private uint _heightRows;
+        private uint _widthPixels;
+        private uint _heightPixels;
+        private Dictionary<TerminalModes, uint> _terminalModes;
+        private ShellStream _shellStream;
+        private int _bufferSize;
+
+        private byte[] _data;
+        private int _offset;
+        private int _count;
+
+        private byte[] _expectedBytesSent1;
+        private byte[] _expectedBytesSent2;
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void SetupData()
+        {
+            var random = new Random();
+
+            _terminalName = random.Next().ToString();
+            _widthColumns = (uint)random.Next();
+            _heightRows = (uint)random.Next();
+            _widthPixels = (uint)random.Next();
+            _heightPixels = (uint)random.Next();
+            _terminalModes = new Dictionary<TerminalModes, uint>();
+            _bufferSize = random.Next(100, 1000);
+
+            _data = CryptoAbstraction.GenerateRandom(_bufferSize * 2 + 10);
+            _offset = 0;
+            _count = _data.Length;
+
+            _expectedBytesSent1 = _data.Take(0, _bufferSize);
+            _expectedBytesSent2 = _data.Take(_bufferSize, _bufferSize);
+        }
+
+        private void CreateMocks()
+        {
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+            _channelSessionMock = new Mock<IChannelSession>(MockBehavior.Strict);
+        }
+
+        private void SetupMocks()
+        {
+            _mockSequence = new MockSequence();
+
+            _sessionMock.InSequence(_mockSequence)
+                        .Setup(p => p.ConnectionInfo)
+                        .Returns(_connectionInfoMock.Object);
+            _connectionInfoMock.InSequence(_mockSequence)
+                               .Setup(p => p.Encoding)
+                               .Returns(new UTF8Encoding());
+            _sessionMock.InSequence(_mockSequence)
+                        .Setup(p => p.CreateChannelSession())
+                        .Returns(_channelSessionMock.Object);
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.Open());
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.SendPseudoTerminalRequest(_terminalName,
+                                                                       _widthColumns,
+                                                                       _heightRows,
+                                                                       _widthPixels,
+                                                                       _heightPixels,
+                                                                       _terminalModes))
+                               .Returns(true);
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.SendShellRequest())
+                               .Returns(true);
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.SendData(_expectedBytesSent1));
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.SendData(_expectedBytesSent2));
+        }
+
+        private void Arrange()
+        {
+            SetupData();
+            CreateMocks();
+            SetupMocks();
+
+            _shellStream = new ShellStream(_sessionMock.Object,
+                                           _terminalName,
+                                           _widthColumns,
+                                           _heightRows,
+                                           _widthPixels,
+                                           _heightPixels,
+                                           _terminalModes,
+                                           _bufferSize);
+        }
+
+        private void Act()
+        {
+            _shellStream.Write(_data, _offset, _count);
+        }
+
+        [TestMethod]
+        public void BufferShouldHaveBeenFlushedTwice()
+        {
+            _channelSessionMock.Verify(p => p.SendData(_expectedBytesSent1), Times.Once);
+            _channelSessionMock.Verify(p => p.SendData(_expectedBytesSent2), Times.Once);
+        }
+
+        [TestMethod]
+        public void FlushShouldSendRemaningBytesToServer()
+        {
+            var expectedBytesSent = _data.Take(_bufferSize * 2, _data.Length - _bufferSize * 2);
+
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.SendData(expectedBytesSent));
+
+            _shellStream.Flush();
+
+            _channelSessionMock.Verify(p => p.SendData(expectedBytesSent), Times.Once);
+        }
+    }
+}

+ 129 - 0
src/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferEmptyAndWriteNumberOfBytesEqualToBufferSize.cs

@@ -0,0 +1,129 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Abstractions;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ShellStreamTest_Write_WriteBufferEmptyAndWriteNumberOfBytesEqualToBufferSize
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private Mock<IChannelSession> _channelSessionMock;
+        private string _terminalName;
+        private uint _widthColumns;
+        private uint _heightRows;
+        private uint _widthPixels;
+        private uint _heightPixels;
+        private Dictionary<TerminalModes, uint> _terminalModes;
+        private ShellStream _shellStream;
+        private int _bufferSize;
+
+        private byte[] _data;
+        private int _offset;
+        private int _count;
+        private MockSequence _mockSequence;
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void SetupData()
+        {
+            var random = new Random();
+
+            _terminalName = random.Next().ToString();
+            _widthColumns = (uint)random.Next();
+            _heightRows = (uint)random.Next();
+            _widthPixels = (uint)random.Next();
+            _heightPixels = (uint)random.Next();
+            _terminalModes = new Dictionary<TerminalModes, uint>();
+            _bufferSize = random.Next(100, 1000);
+
+            _data = CryptoAbstraction.GenerateRandom(_bufferSize);
+            _offset = 0;
+            _count = _data.Length;
+        }
+
+        private void CreateMocks()
+        {
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+            _channelSessionMock = new Mock<IChannelSession>(MockBehavior.Strict);
+        }
+
+        private void SetupMocks()
+        {
+            _mockSequence = new MockSequence();
+
+            _sessionMock.InSequence(_mockSequence)
+                        .Setup(p => p.ConnectionInfo)
+                        .Returns(_connectionInfoMock.Object);
+            _connectionInfoMock.InSequence(_mockSequence)
+                               .Setup(p => p.Encoding)
+                               .Returns(new UTF8Encoding());
+            _sessionMock.InSequence(_mockSequence)
+                        .Setup(p => p.CreateChannelSession())
+                        .Returns(_channelSessionMock.Object);
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.Open());
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.SendPseudoTerminalRequest(_terminalName,
+                                                                       _widthColumns,
+                                                                       _heightRows,
+                                                                       _widthPixels,
+                                                                       _heightPixels,
+                                                                       _terminalModes))
+                               .Returns(true);
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.SendShellRequest())
+                               .Returns(true);
+        }
+
+        private void Arrange()
+        {
+            SetupData();
+            CreateMocks();
+            SetupMocks();
+
+            _shellStream = new ShellStream(_sessionMock.Object,
+                                           _terminalName,
+                                           _widthColumns,
+                                           _heightRows,
+                                           _widthPixels,
+                                           _heightPixels,
+                                           _terminalModes,
+                                           _bufferSize);
+        }
+
+        private void Act()
+        {
+            _shellStream.Write(_data, _offset, _count);
+        }
+
+        [TestMethod]
+        public void NoDataShouldBeSentToServer()
+        {
+            _channelSessionMock.Verify(p => p.SendData(It.IsAny<byte[]>()), Times.Never);
+        }
+
+        [TestMethod]
+        public void FlushShouldSendWrittenBytesToServer()
+        {
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.SendData(_data));
+
+            _shellStream.Flush();
+
+            _channelSessionMock.Verify(p => p.SendData(_data), Times.Once);
+        }
+    }
+}

+ 126 - 0
src/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferEmptyAndWriteZeroBytes.cs

@@ -0,0 +1,126 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Abstractions;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ShellStreamTest_Write_WriteBufferEmptyAndWriteZeroBytes
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private Mock<IChannelSession> _channelSessionMock;
+        private string _terminalName;
+        private uint _widthColumns;
+        private uint _heightRows;
+        private uint _widthPixels;
+        private uint _heightPixels;
+        private Dictionary<TerminalModes, uint> _terminalModes;
+        private ShellStream _shellStream;
+        private int _bufferSize;
+
+        private byte[] _data;
+        private int _offset;
+        private int _count;
+        private MockSequence _mockSequence;
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void SetupData()
+        {
+            var random = new Random();
+
+            _terminalName = random.Next().ToString();
+            _widthColumns = (uint)random.Next();
+            _heightRows = (uint)random.Next();
+            _widthPixels = (uint)random.Next();
+            _heightPixels = (uint)random.Next();
+            _terminalModes = new Dictionary<TerminalModes, uint>();
+            _bufferSize = random.Next(100, 1000);
+
+            _data = new byte[0];
+            _offset = 0;
+            _count = _data.Length;
+        }
+
+        private void CreateMocks()
+        {
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+            _channelSessionMock = new Mock<IChannelSession>(MockBehavior.Strict);
+        }
+
+        private void SetupMocks()
+        {
+            _mockSequence = new MockSequence();
+
+            _sessionMock.InSequence(_mockSequence)
+                        .Setup(p => p.ConnectionInfo)
+                        .Returns(_connectionInfoMock.Object);
+            _connectionInfoMock.InSequence(_mockSequence)
+                               .Setup(p => p.Encoding)
+                               .Returns(new UTF8Encoding());
+            _sessionMock.InSequence(_mockSequence)
+                        .Setup(p => p.CreateChannelSession())
+                        .Returns(_channelSessionMock.Object);
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.Open());
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.SendPseudoTerminalRequest(_terminalName,
+                                                                       _widthColumns,
+                                                                       _heightRows,
+                                                                       _widthPixels,
+                                                                       _heightPixels,
+                                                                       _terminalModes))
+                               .Returns(true);
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.SendShellRequest())
+                               .Returns(true);
+        }
+
+        private void Arrange()
+        {
+            SetupData();
+            CreateMocks();
+            SetupMocks();
+
+            _shellStream = new ShellStream(_sessionMock.Object,
+                                           _terminalName,
+                                           _widthColumns,
+                                           _heightRows,
+                                           _widthPixels,
+                                           _heightPixels,
+                                           _terminalModes,
+                                           _bufferSize);
+        }
+
+        private void Act()
+        {
+            _shellStream.Write(_data, _offset, _count);
+        }
+
+        [TestMethod]
+        public void NoDataShouldBeSentToServer()
+        {
+            _channelSessionMock.Verify(p => p.SendData(It.IsAny<byte[]>()), Times.Never);
+        }
+
+        [TestMethod]
+        public void FlushShouldSendNoBytesToServer()
+        {
+            _shellStream.Flush();
+
+            _channelSessionMock.Verify(p => p.SendData(It.IsAny<byte[]>()), Times.Never);
+        }
+    }
+}

+ 135 - 0
src/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferFullAndWriteLessBytesThanBufferSize.cs

@@ -0,0 +1,135 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Abstractions;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ShellStreamTest_Write_WriteBufferFullAndWriteLessBytesThanBufferSize
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private Mock<IChannelSession> _channelSessionMock;
+        private string _terminalName;
+        private uint _widthColumns;
+        private uint _heightRows;
+        private uint _widthPixels;
+        private uint _heightPixels;
+        private Dictionary<TerminalModes, uint> _terminalModes;
+        private ShellStream _shellStream;
+        private int _bufferSize;
+
+        private byte[] _data;
+        private int _offset;
+        private int _count;
+        private MockSequence _mockSequence;
+        private byte[] _bufferData;
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void SetupData()
+        {
+            var random = new Random();
+
+            _terminalName = random.Next().ToString();
+            _widthColumns = (uint)random.Next();
+            _heightRows = (uint)random.Next();
+            _widthPixels = (uint)random.Next();
+            _heightPixels = (uint)random.Next();
+            _terminalModes = new Dictionary<TerminalModes, uint>();
+            _bufferSize = random.Next(100, 1000);
+
+            _bufferData = CryptoAbstraction.GenerateRandom(_bufferSize);
+            _data = CryptoAbstraction.GenerateRandom(_bufferSize - 10);
+            _offset = 0;
+            _count = _data.Length;
+        }
+
+        private void CreateMocks()
+        {
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+            _channelSessionMock = new Mock<IChannelSession>(MockBehavior.Strict);
+        }
+
+        private void SetupMocks()
+        {
+            _mockSequence = new MockSequence();
+
+            _sessionMock.InSequence(_mockSequence)
+                        .Setup(p => p.ConnectionInfo)
+                        .Returns(_connectionInfoMock.Object);
+            _connectionInfoMock.InSequence(_mockSequence)
+                               .Setup(p => p.Encoding)
+                               .Returns(new UTF8Encoding());
+            _sessionMock.InSequence(_mockSequence)
+                        .Setup(p => p.CreateChannelSession())
+                        .Returns(_channelSessionMock.Object);
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.Open());
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.SendPseudoTerminalRequest(_terminalName,
+                                                                       _widthColumns,
+                                                                       _heightRows,
+                                                                       _widthPixels,
+                                                                       _heightPixels,
+                                                                       _terminalModes))
+                               .Returns(true);
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.SendShellRequest())
+                               .Returns(true);
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.SendData(_bufferData));
+        }
+
+        private void Arrange()
+        {
+            SetupData();
+            CreateMocks();
+            SetupMocks();
+
+            _shellStream = new ShellStream(_sessionMock.Object,
+                                           _terminalName,
+                                           _widthColumns,
+                                           _heightRows,
+                                           _widthPixels,
+                                           _heightPixels,
+                                           _terminalModes,
+                                           _bufferSize);
+
+            _shellStream.Write(_bufferData, 0, _bufferData.Length);
+        }
+
+        private void Act()
+        {
+            _shellStream.Write(_data, _offset, _count);
+        }
+
+        [TestMethod]
+        public void BufferShouldBeSentToServer()
+        {
+            _channelSessionMock.Verify(p => p.SendData(_bufferData), Times.Once);
+        }
+
+        [TestMethod]
+        public void FlushShouldSendRemainingBytesInBufferToServer()
+        {
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.SendData(_data));
+
+            _shellStream.Flush();
+
+            _channelSessionMock.Verify(p => p.SendData(_data), Times.Once);
+        }
+    }
+}

+ 133 - 0
src/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferFullAndWriteZeroBytes.cs

@@ -0,0 +1,133 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Abstractions;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ShellStreamTest_Write_WriteBufferFullAndWriteZeroBytes
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private Mock<IChannelSession> _channelSessionMock;
+        private string _terminalName;
+        private uint _widthColumns;
+        private uint _heightRows;
+        private uint _widthPixels;
+        private uint _heightPixels;
+        private Dictionary<TerminalModes, uint> _terminalModes;
+        private ShellStream _shellStream;
+        private int _bufferSize;
+
+        private byte[] _data;
+        private int _offset;
+        private int _count;
+        private MockSequence _mockSequence;
+        private byte[] _bufferData;
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void SetupData()
+        {
+            var random = new Random();
+
+            _terminalName = random.Next().ToString();
+            _widthColumns = (uint) random.Next();
+            _heightRows = (uint) random.Next();
+            _widthPixels = (uint) random.Next();
+            _heightPixels = (uint) random.Next();
+            _terminalModes = new Dictionary<TerminalModes, uint>();
+            _bufferSize = random.Next(100, 1000);
+
+            _bufferData = CryptoAbstraction.GenerateRandom(_bufferSize);
+            _data = new byte[0];
+            _offset = 0;
+            _count = _data.Length;
+        }
+
+        private void CreateMocks()
+        {
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+            _channelSessionMock = new Mock<IChannelSession>(MockBehavior.Strict);
+        }
+
+        private void SetupMocks()
+        {
+            _mockSequence = new MockSequence();
+
+            _sessionMock.InSequence(_mockSequence)
+                        .Setup(p => p.ConnectionInfo)
+                        .Returns(_connectionInfoMock.Object);
+            _connectionInfoMock.InSequence(_mockSequence)
+                               .Setup(p => p.Encoding)
+                               .Returns(new UTF8Encoding());
+            _sessionMock.InSequence(_mockSequence)
+                        .Setup(p => p.CreateChannelSession())
+                        .Returns(_channelSessionMock.Object);
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.Open());
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.SendPseudoTerminalRequest(_terminalName,
+                                                                       _widthColumns,
+                                                                       _heightRows,
+                                                                       _widthPixels,
+                                                                       _heightPixels,
+                                                                       _terminalModes))
+                               .Returns(true);
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.SendShellRequest())
+                               .Returns(true);
+        }
+
+        private void Arrange()
+        {
+            SetupData();
+            CreateMocks();
+            SetupMocks();
+
+            _shellStream = new ShellStream(_sessionMock.Object,
+                                           _terminalName,
+                                           _widthColumns,
+                                           _heightRows,
+                                           _widthPixels,
+                                           _heightPixels,
+                                           _terminalModes,
+                                           _bufferSize);
+
+            _shellStream.Write(_bufferData, 0, _bufferData.Length);
+        }
+
+        private void Act()
+        {
+            _shellStream.Write(_data, _offset, _count);
+        }
+
+        [TestMethod]
+        public void NoDataShouldBeSentToServer()
+        {
+            _channelSessionMock.Verify(p => p.SendData(It.IsAny<byte[]>()), Times.Never);
+        }
+
+        [TestMethod]
+        public void FlushShouldSendBufferToServer()
+        {
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.SendData(_bufferData));
+
+            _shellStream.Flush();
+
+            _channelSessionMock.Verify(p => p.SendData(_bufferData), Times.Once);
+        }
+    }
+}

+ 141 - 0
src/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferNotEmptyAndWriteLessBytesThanBufferCanContain.cs

@@ -0,0 +1,141 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Abstractions;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ShellStreamTest_Write_WriteBufferNotEmptyAndWriteLessBytesThanBufferCanContain
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private Mock<IChannelSession> _channelSessionMock;
+        private string _terminalName;
+        private uint _widthColumns;
+        private uint _heightRows;
+        private uint _widthPixels;
+        private uint _heightPixels;
+        private Dictionary<TerminalModes, uint> _terminalModes;
+        private ShellStream _shellStream;
+        private int _bufferSize;
+
+        private byte[] _data;
+        private int _offset;
+        private int _count;
+        private MockSequence _mockSequence;
+        private byte[] _bufferData;
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void SetupData()
+        {
+            var random = new Random();
+
+            _terminalName = random.Next().ToString();
+            _widthColumns = (uint) random.Next();
+            _heightRows = (uint) random.Next();
+            _widthPixels = (uint) random.Next();
+            _heightPixels = (uint) random.Next();
+            _terminalModes = new Dictionary<TerminalModes, uint>();
+            _bufferSize = random.Next(100, 1000);
+
+            _bufferData = CryptoAbstraction.GenerateRandom(_bufferSize - 60);
+            _data = CryptoAbstraction.GenerateRandom(_bufferSize + 100);
+            _offset = 0;
+            _count = _bufferSize - _bufferData.Length - random.Next(1, 10);
+        }
+
+        private void CreateMocks()
+        {
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+            _channelSessionMock = new Mock<IChannelSession>(MockBehavior.Strict);
+        }
+
+        private void SetupMocks()
+        {
+            _mockSequence = new MockSequence();
+
+            _sessionMock.InSequence(_mockSequence)
+                        .Setup(p => p.ConnectionInfo)
+                        .Returns(_connectionInfoMock.Object);
+            _connectionInfoMock.InSequence(_mockSequence)
+                               .Setup(p => p.Encoding)
+                               .Returns(new UTF8Encoding());
+            _sessionMock.InSequence(_mockSequence)
+                        .Setup(p => p.CreateChannelSession())
+                        .Returns(_channelSessionMock.Object);
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.Open());
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.SendPseudoTerminalRequest(_terminalName,
+                                                                       _widthColumns,
+                                                                       _heightRows,
+                                                                       _widthPixels,
+                                                                       _heightPixels,
+                                                                       _terminalModes))
+                               .Returns(true);
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.SendShellRequest())
+                               .Returns(true);
+        }
+
+        private void Arrange()
+        {
+            SetupData();
+            CreateMocks();
+            SetupMocks();
+
+            _shellStream = new ShellStream(_sessionMock.Object,
+                                           _terminalName,
+                                           _widthColumns,
+                                           _heightRows,
+                                           _widthPixels,
+                                           _heightPixels,
+                                           _terminalModes,
+                                           _bufferSize);
+
+            _shellStream.Write(_bufferData, 0, _bufferData.Length);
+        }
+
+        private void Act()
+        {
+            _shellStream.Write(_data, _offset, _count);
+        }
+
+        [TestMethod]
+        public void NoDataShouldBeSentToServer()
+        {
+            _channelSessionMock.Verify(p => p.SendData(It.IsAny<byte[]>()), Times.Never);
+        }
+
+        [TestMethod]
+        public void FlushShouldSendWrittenBytesToServer()
+        {
+            byte[] bytesSent = null;
+
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.SendData(It.IsAny<byte[]>()))
+                               .Callback<byte[]>(data => bytesSent = data);
+
+            _shellStream.Flush();
+
+            Assert.IsNotNull(bytesSent);
+            Assert.AreEqual(_bufferData.Length + _count, bytesSent.Length);
+            Assert.IsTrue(_bufferData.IsEqualTo(bytesSent.Take(_bufferData.Length)));
+            Assert.IsTrue(_data.Take(0, _count).IsEqualTo(bytesSent.Take(_bufferData.Length, _count)));
+
+            _channelSessionMock.Verify(p => p.SendData(It.IsAny<byte[]>()), Times.Once);
+        }
+    }
+}

+ 149 - 0
src/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferNotEmptyAndWriteMoreBytesThanBufferCanContain.cs

@@ -0,0 +1,149 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Abstractions;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+using Renci.SshNet.Tests.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ShellStreamTest_Write_WriteBufferNotEmptyAndWriteMoreBytesThanBufferCanContain
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private Mock<IChannelSession> _channelSessionMock;
+        private string _terminalName;
+        private uint _widthColumns;
+        private uint _heightRows;
+        private uint _widthPixels;
+        private uint _heightPixels;
+        private Dictionary<TerminalModes, uint> _terminalModes;
+        private ShellStream _shellStream;
+        private int _bufferSize;
+
+        private byte[] _data;
+        private int _offset;
+        private int _count;
+        private MockSequence _mockSequence;
+        private byte[] _bufferData;
+        private byte[] _expectedBytesSent;
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void SetupData()
+        {
+            var random = new Random();
+
+            _terminalName = random.Next().ToString();
+            _widthColumns = (uint) random.Next();
+            _heightRows = (uint) random.Next();
+            _widthPixels = (uint) random.Next();
+            _heightPixels = (uint) random.Next();
+            _terminalModes = new Dictionary<TerminalModes, uint>();
+            _bufferSize = random.Next(100, 1000);
+
+            _bufferData = CryptoAbstraction.GenerateRandom(_bufferSize - 60);
+            _data = CryptoAbstraction.GenerateRandom(_bufferSize - _bufferData.Length + random.Next(1, 10));
+            _offset = 0;
+            _count = _data.Length;
+
+            _expectedBytesSent = new ArrayBuilder<byte>().Add(_bufferData)
+                                                         .Add(_data, 0, _bufferSize - _bufferData.Length)
+                                                         .Build();
+        }
+
+        private void CreateMocks()
+        {
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+            _channelSessionMock = new Mock<IChannelSession>(MockBehavior.Strict);
+        }
+
+        private void SetupMocks()
+        {
+            _mockSequence = new MockSequence();
+
+            _sessionMock.InSequence(_mockSequence)
+                        .Setup(p => p.ConnectionInfo)
+                        .Returns(_connectionInfoMock.Object);
+            _connectionInfoMock.InSequence(_mockSequence)
+                               .Setup(p => p.Encoding)
+                               .Returns(new UTF8Encoding());
+            _sessionMock.InSequence(_mockSequence)
+                        .Setup(p => p.CreateChannelSession())
+                        .Returns(_channelSessionMock.Object);
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.Open());
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.SendPseudoTerminalRequest(_terminalName,
+                                                                       _widthColumns,
+                                                                       _heightRows,
+                                                                       _widthPixels,
+                                                                       _heightPixels,
+                                                                       _terminalModes))
+                               .Returns(true);
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.SendShellRequest())
+                               .Returns(true);
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.SendData(_expectedBytesSent));
+        }
+
+        private void Arrange()
+        {
+            SetupData();
+            CreateMocks();
+            SetupMocks();
+
+            _shellStream = new ShellStream(_sessionMock.Object,
+                                           _terminalName,
+                                           _widthColumns,
+                                           _heightRows,
+                                           _widthPixels,
+                                           _heightPixels,
+                                           _terminalModes,
+                                           _bufferSize);
+
+            _shellStream.Write(_bufferData, 0, _bufferData.Length);
+        }
+
+        private void Act()
+        {
+            _shellStream.Write(_data, _offset, _count);
+        }
+
+        [TestMethod]
+        public void BufferShouldBeSentToServer()
+        {
+            _channelSessionMock.Verify(p => p.SendData(_expectedBytesSent), Times.Once);
+        }
+
+        [TestMethod]
+        public void FlushShouldSendRemainingBytesInBufferToServer()
+        {
+           var expectedBytesSent = _data.Take(_bufferSize - _bufferData.Length, _data.Length + _bufferData.Length - _bufferSize);
+            byte[] actualBytesSent = null;
+
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.SendData(It.IsAny<byte[]>()))
+                               .Callback<byte[]>(data => actualBytesSent = data);
+
+            _shellStream.Flush();
+
+            Assert.IsNotNull(actualBytesSent);
+            Assert.AreEqual(expectedBytesSent.Length, actualBytesSent.Length);
+            Assert.IsTrue(expectedBytesSent.IsEqualTo(actualBytesSent));
+
+            _channelSessionMock.Verify(p => p.SendData(It.IsAny<byte[]>()), Times.Exactly(2));
+        }
+    }
+}

+ 133 - 0
src/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferNotEmptyAndWriteZeroBytes.cs

@@ -0,0 +1,133 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Abstractions;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ShellStreamTest_Write_WriteBufferNotEmptyAndWriteZeroBytes
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private Mock<IChannelSession> _channelSessionMock;
+        private string _terminalName;
+        private uint _widthColumns;
+        private uint _heightRows;
+        private uint _widthPixels;
+        private uint _heightPixels;
+        private Dictionary<TerminalModes, uint> _terminalModes;
+        private ShellStream _shellStream;
+        private int _bufferSize;
+
+        private byte[] _data;
+        private int _offset;
+        private int _count;
+        private MockSequence _mockSequence;
+        private byte[] _bufferData;
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void SetupData()
+        {
+            var random = new Random();
+
+            _terminalName = random.Next().ToString();
+            _widthColumns = (uint)random.Next();
+            _heightRows = (uint)random.Next();
+            _widthPixels = (uint)random.Next();
+            _heightPixels = (uint)random.Next();
+            _terminalModes = new Dictionary<TerminalModes, uint>();
+            _bufferSize = random.Next(100, 1000);
+
+            _bufferData = CryptoAbstraction.GenerateRandom(_bufferSize - 60);
+            _data = new byte[0];
+            _offset = 0;
+            _count = _data.Length;
+        }
+
+        private void CreateMocks()
+        {
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+            _channelSessionMock = new Mock<IChannelSession>(MockBehavior.Strict);
+        }
+
+        private void SetupMocks()
+        {
+            _mockSequence = new MockSequence();
+
+            _sessionMock.InSequence(_mockSequence)
+                        .Setup(p => p.ConnectionInfo)
+                        .Returns(_connectionInfoMock.Object);
+            _connectionInfoMock.InSequence(_mockSequence)
+                               .Setup(p => p.Encoding)
+                               .Returns(new UTF8Encoding());
+            _sessionMock.InSequence(_mockSequence)
+                        .Setup(p => p.CreateChannelSession())
+                        .Returns(_channelSessionMock.Object);
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.Open());
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.SendPseudoTerminalRequest(_terminalName,
+                                                                       _widthColumns,
+                                                                       _heightRows,
+                                                                       _widthPixels,
+                                                                       _heightPixels,
+                                                                       _terminalModes))
+                               .Returns(true);
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.SendShellRequest())
+                               .Returns(true);
+        }
+
+        private void Arrange()
+        {
+            SetupData();
+            CreateMocks();
+            SetupMocks();
+
+            _shellStream = new ShellStream(_sessionMock.Object,
+                                           _terminalName,
+                                           _widthColumns,
+                                           _heightRows,
+                                           _widthPixels,
+                                           _heightPixels,
+                                           _terminalModes,
+                                           _bufferSize);
+
+            _shellStream.Write(_bufferData, 0, _bufferData.Length);
+        }
+
+        private void Act()
+        {
+            _shellStream.Write(_data, _offset, _count);
+        }
+
+        [TestMethod]
+        public void NoDataShouldBeSentToServer()
+        {
+            _channelSessionMock.Verify(p => p.SendData(It.IsAny<byte[]>()), Times.Never);
+        }
+
+        [TestMethod]
+        public void FlushShouldSendWrittenBytesToServer()
+        {
+            _channelSessionMock.InSequence(_mockSequence)
+                               .Setup(p => p.SendData(_bufferData));
+
+            _shellStream.Flush();
+
+            _channelSessionMock.Verify(p => p.SendData(_bufferData), Times.Once);
+        }
+    }
+}

+ 151 - 0
src/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSizeAndTerminalModes_Connected.cs

@@ -0,0 +1,151 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class
+        SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSizeAndTerminalModes_Connected
+    {
+        private Mock<IServiceFactory> _serviceFactoryMock;
+        private Mock<ISession> _sessionMock;
+        private SshClient _sshClient;
+        private ConnectionInfo _connectionInfo;
+        private string _terminalName;
+        private uint _widthColumns;
+        private uint _heightRows;
+        private uint _widthPixels;
+        private uint _heightPixels;
+        private Dictionary<TerminalModes, uint> _terminalModes;
+        private int _bufferSize;
+        private ShellStream _expected;
+        private ShellStream _actual;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void SetupData()
+        {
+            var random = new Random();
+
+            _connectionInfo = new ConnectionInfo("host", "user", new NoneAuthenticationMethod("userauth"));
+
+            _terminalName = random.Next().ToString();
+            _widthColumns = (uint) random.Next();
+            _heightRows = (uint) random.Next();
+            _widthPixels = (uint) random.Next();
+            _heightPixels = (uint) random.Next();
+            _terminalModes = new Dictionary<TerminalModes, uint>();
+            _bufferSize = random.Next(100, 1000);
+
+            _expected = CreateShellStream();
+        }
+
+        private void CreateMocks()
+        {
+            _serviceFactoryMock = new Mock<IServiceFactory>(MockBehavior.Strict);
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+        }
+
+        private void SetupMocks()
+        {
+            var sequence = new MockSequence();
+
+            _serviceFactoryMock.InSequence(sequence)
+                               .Setup(p => p.CreateSession(_connectionInfo))
+                               .Returns(_sessionMock.Object);
+            _sessionMock.InSequence(sequence)
+                        .Setup(p => p.Connect());
+            _serviceFactoryMock.InSequence(sequence)
+                               .Setup(p => p.CreateShellStream(_sessionMock.Object,
+                                                               _terminalName,
+                                                               _widthColumns,
+                                                               _heightRows,
+                                                               _widthPixels,
+                                                               _heightPixels,
+                                                               _terminalModes,
+                                                               _bufferSize))
+                               .Returns(_expected);
+        }
+
+        private void Arrange()
+        {
+            SetupData();
+            CreateMocks();
+            SetupMocks();
+
+            _sshClient = new SshClient(_connectionInfo, false, _serviceFactoryMock.Object);
+            _sshClient.Connect();
+        }
+
+        protected void Act()
+        {
+            _actual = _sshClient.CreateShellStream(_terminalName,
+                                                   _widthColumns,
+                                                   _heightRows,
+                                                   _widthPixels,
+                                                   _heightPixels,
+                                                   _bufferSize,
+                                                   _terminalModes);
+        }
+
+        [TestMethod]
+        public void CreateShellStreamOnServiceFactoryShouldBeInvokedOnce()
+        {
+            _serviceFactoryMock.Verify(p => p.CreateShellStream(_sessionMock.Object,
+                                                                _terminalName,
+                                                                _widthColumns,
+                                                                _heightRows,
+                                                                _widthPixels,
+                                                                _heightPixels,
+                                                                _terminalModes,
+                                                                _bufferSize),
+                                       Times.Once);
+        }
+
+        [TestMethod]
+        public void CreateShellStreamShouldReturnValueReturnedByCreateShellStreamOnServiceFactory()
+        {
+            Assert.IsNotNull(_actual);
+            Assert.AreSame(_expected, _actual);
+        }
+
+        private ShellStream CreateShellStream()
+        {
+            var sessionMock = new Mock<ISession>(MockBehavior.Loose);
+            var channelSessionMock = new Mock<IChannelSession>(MockBehavior.Strict);
+
+            sessionMock.Setup(p => p.ConnectionInfo)
+                       .Returns(new ConnectionInfo("A", "B", new PasswordAuthenticationMethod("A", "B")));
+            sessionMock.Setup(p => p.CreateChannelSession())
+                       .Returns(channelSessionMock.Object);
+            channelSessionMock.Setup(p => p.Open());
+            channelSessionMock.Setup(p => p.SendPseudoTerminalRequest(_terminalName,
+                                                                      _widthColumns,
+                                                                      _heightRows,
+                                                                      _widthPixels,
+                                                                      _heightPixels,
+                                                                      _terminalModes))
+                              .Returns(true);
+            channelSessionMock.Setup(p => p.SendShellRequest())
+                              .Returns(true);
+
+            return new ShellStream(sessionMock.Object,
+                                   _terminalName,
+                                   _widthColumns,
+                                   _heightRows,
+                                   _widthPixels,
+                                   _heightPixels,
+                                   _terminalModes,
+                                   1);
+        }
+    }
+}

+ 147 - 0
src/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSize_Connected.cs

@@ -0,0 +1,147 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSize_Connected
+    {
+        private Mock<IServiceFactory> _serviceFactoryMock;
+        private Mock<ISession> _sessionMock;
+        private SshClient _sshClient;
+        private ConnectionInfo _connectionInfo;
+        private string _terminalName;
+        private uint _widthColumns;
+        private uint _heightRows;
+        private uint _widthPixels;
+        private uint _heightPixels;
+        private int _bufferSize;
+        private ShellStream _expected;
+        private ShellStream _actual;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void SetupData()
+        {
+            var random = new Random();
+
+            _connectionInfo = new ConnectionInfo("host", "user", new NoneAuthenticationMethod("userauth"));
+
+            _terminalName = random.Next().ToString();
+            _widthColumns = (uint)random.Next();
+            _heightRows = (uint)random.Next();
+            _widthPixels = (uint)random.Next();
+            _heightPixels = (uint)random.Next();
+            _bufferSize = random.Next(100, 1000);
+
+            _expected = CreateShellStream();
+        }
+
+        private void CreateMocks()
+        {
+            _serviceFactoryMock = new Mock<IServiceFactory>(MockBehavior.Strict);
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+        }
+
+        private void SetupMocks()
+        {
+            var sequence = new MockSequence();
+
+            _serviceFactoryMock.InSequence(sequence)
+                               .Setup(p => p.CreateSession(_connectionInfo))
+                               .Returns(_sessionMock.Object);
+            _sessionMock.InSequence(sequence)
+                        .Setup(p => p.Connect());
+            _serviceFactoryMock.InSequence(sequence)
+                               .Setup(p => p.CreateShellStream(_sessionMock.Object,
+                                                               _terminalName,
+                                                               _widthColumns,
+                                                               _heightRows,
+                                                               _widthPixels,
+                                                               _heightPixels,
+                                                               null,
+                                                               _bufferSize))
+                               .Returns(_expected);
+        }
+
+        private void Arrange()
+        {
+            SetupData();
+            CreateMocks();
+            SetupMocks();
+
+            _sshClient = new SshClient(_connectionInfo, false, _serviceFactoryMock.Object);
+            _sshClient.Connect();
+        }
+
+        protected void Act()
+        {
+            _actual = _sshClient.CreateShellStream(_terminalName,
+                                                   _widthColumns,
+                                                   _heightRows,
+                                                   _widthPixels,
+                                                   _heightPixels,
+                                                   _bufferSize);
+        }
+
+        [TestMethod]
+        public void CreateShellStreamOnServiceFactoryShouldBeInvokedOnce()
+        {
+            _serviceFactoryMock.Verify(p => p.CreateShellStream(_sessionMock.Object,
+                                                                _terminalName,
+                                                                _widthColumns,
+                                                                _heightRows,
+                                                                _widthPixels,
+                                                                _heightPixels,
+                                                                null,
+                                                                _bufferSize),
+                                       Times.Once);
+        }
+
+        [TestMethod]
+        public void CreateShellStreamShouldReturnValueReturnedByCreateShellStreamOnServiceFactory()
+        {
+            Assert.IsNotNull(_actual);
+            Assert.AreSame(_expected, _actual);
+        }
+
+        private ShellStream CreateShellStream()
+        {
+            var sessionMock = new Mock<ISession>(MockBehavior.Loose);
+            var channelSessionMock = new Mock<IChannelSession>(MockBehavior.Strict);
+
+            sessionMock.Setup(p => p.ConnectionInfo)
+                       .Returns(new ConnectionInfo("A", "B", new PasswordAuthenticationMethod("A", "B")));
+            sessionMock.Setup(p => p.CreateChannelSession())
+                       .Returns(channelSessionMock.Object);
+            channelSessionMock.Setup(p => p.Open());
+            channelSessionMock.Setup(p => p.SendPseudoTerminalRequest(_terminalName,
+                                                                      _widthColumns,
+                                                                      _heightRows,
+                                                                      _widthPixels,
+                                                                      _heightPixels,
+                                                                      null))
+                              .Returns(true);
+            channelSessionMock.Setup(p => p.SendShellRequest())
+                              .Returns(true);
+
+            return new ShellStream(sessionMock.Object,
+                                   _terminalName,
+                                   _widthColumns,
+                                   _heightRows,
+                                   _widthPixels,
+                                   _heightPixels,
+                                   null,
+                                   1);
+        }
+    }
+}

+ 12 - 0
src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj

@@ -270,6 +270,7 @@
     <Compile Include="Classes\ServiceFactoryTest_CreateSftpFileReader_FileSizeIsLittleMoreThanFiveTimesGreaterThanChunkSize.cs" />
     <Compile Include="Classes\ServiceFactoryTest_CreateSftpFileReader_FileSizeIsMoreThanTenTimesGreaterThanChunkSize.cs" />
     <Compile Include="Classes\ServiceFactoryTest_CreateSftpFileReader_FileSizeIsZero.cs" />
+    <Compile Include="Classes\ServiceFactoryTest_CreateShellStream.cs" />
     <Compile Include="Classes\SessionTest_Connected.cs" />
     <Compile Include="Classes\SessionTest_ConnectedBase.cs" />
     <Compile Include="Classes\SessionTest_Connected_ConnectionReset.cs" />
@@ -500,9 +501,20 @@
     <Compile Include="Classes\Sftp\SftpStatVfsRequestBuilder.cs" />
     <Compile Include="Classes\Sftp\SftpStatVfsResponseBuilder.cs" />
     <Compile Include="Classes\Sftp\SftpVersionResponseBuilder.cs" />
+    <Compile Include="Classes\ShellStreamTest_Write_WriteBufferEmptyAndWriteLessBytesThanBufferSize.cs" />
+    <Compile Include="Classes\ShellStreamTest_Write_WriteBufferEmptyAndWriteMoreBytesThanBufferSize.cs" />
+    <Compile Include="Classes\ShellStreamTest_Write_WriteBufferEmptyAndWriteNumberOfBytesEqualToBufferSize.cs" />
+    <Compile Include="Classes\ShellStreamTest_Write_WriteBufferEmptyAndWriteZeroBytes.cs" />
+    <Compile Include="Classes\ShellStreamTest_Write_WriteBufferFullAndWriteLessBytesThanBufferSize.cs" />
+    <Compile Include="Classes\ShellStreamTest_Write_WriteBufferFullAndWriteZeroBytes.cs" />
+    <Compile Include="Classes\ShellStreamTest_Write_WriteBufferNotEmptyAndWriteLessBytesThanBufferCanContain.cs" />
+    <Compile Include="Classes\ShellStreamTest_Write_WriteBufferNotEmptyAndWriteMoreBytesThanBufferCanContain.cs" />
+    <Compile Include="Classes\ShellStreamTest_Write_WriteBufferNotEmptyAndWriteZeroBytes.cs" />
     <Compile Include="Classes\ShellTestTest.cs" />
     <Compile Include="Classes\ShellStreamTest.cs" />
     <Compile Include="Classes\SshClientTest.cs" />
+    <Compile Include="Classes\SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSizeAndTerminalModes_Connected.cs" />
+    <Compile Include="Classes\SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSize_Connected.cs" />
     <Compile Include="Classes\SshClientTest_Disconnect_ForwardedPortStarted.cs" />
     <Compile Include="Classes\SshClientTest_Dispose_Connected.cs" />
     <Compile Include="Classes\SshClientTest_Dispose_Disconnected.cs" />

+ 34 - 0
src/Renci.SshNet/IServiceFactory.cs

@@ -63,6 +63,40 @@ namespace Renci.SshNet
 
         ISftpResponseFactory CreateSftpResponseFactory();
 
+        /// <summary>
+        /// Creates a shell stream.
+        /// </summary>
+        /// <param name="session">The SSH session.</param>
+        /// <param name="terminalName">The <c>TERM</c> environment variable.</param>
+        /// <param name="columns">The terminal width in columns.</param>
+        /// <param name="rows">The terminal width in rows.</param>
+        /// <param name="width">The terminal height in pixels.</param>
+        /// <param name="height">The terminal height in pixels.</param>
+        /// <param name="bufferSize">Size of the buffer.</param>
+        /// <param name="terminalModeValues">The terminal mode values.</param>
+        /// <returns>
+        /// The created <see cref="ShellStream"/> instance.
+        /// </returns>
+        /// <exception cref="SshConnectionException">Client is not connected.</exception>
+        /// <remarks>
+        /// <para>
+        /// The <c>TERM</c> environment variable contains an identifier for the text window's capabilities.
+        /// You can get a detailed list of these cababilities by using the ‘infocmp’ command.
+        /// </para>
+        /// <para>
+        /// The column/row dimensions override the pixel dimensions(when non-zero). Pixel dimensions refer
+        /// to the drawable area of the window.
+        /// </para>
+        /// </remarks>
+        ShellStream CreateShellStream(ISession session,
+                                      string terminalName,
+                                      uint columns,
+                                      uint rows,
+                                      uint width,
+                                      uint height,
+                                      IDictionary<TerminalModes, uint> terminalModeValues,
+                                      int bufferSize);
+
         /// <summary>
         /// Creates an <see cref="IRemotePathTransformation"/> that encloses a path in double quotes, and escapes
         /// any embedded double quote with a backslash.

+ 30 - 0
src/Renci.SshNet/ServiceFactory.cs

@@ -139,6 +139,36 @@ namespace Renci.SshNet
             return new SftpResponseFactory();
         }
 
+        /// <summary>
+        /// Creates a shell stream.
+        /// </summary>
+        /// <param name="session">The SSH session.</param>
+        /// <param name="terminalName">The <c>TERM</c> environment variable.</param>
+        /// <param name="columns">The terminal width in columns.</param>
+        /// <param name="rows">The terminal width in rows.</param>
+        /// <param name="width">The terminal height in pixels.</param>
+        /// <param name="height">The terminal height in pixels.</param>
+        /// <param name="terminalModeValues">The terminal mode values.</param>
+        /// <param name="bufferSize">The size of the buffer.</param>
+        /// <returns>
+        /// The created <see cref="ShellStream"/> instance.
+        /// </returns>
+        /// <exception cref="SshConnectionException">Client is not connected.</exception>
+        /// <remarks>
+        /// <para>
+        /// The <c>TERM</c> environment variable contains an identifier for the text window's capabilities.
+        /// You can get a detailed list of these cababilities by using the ‘infocmp’ command.
+        /// </para>
+        /// <para>
+        /// The column/row dimensions override the pixel dimensions(when non-zero). Pixel dimensions refer
+        /// to the drawable area of the window.
+        /// </para>
+        /// </remarks>
+        public ShellStream CreateShellStream(ISession session, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary<TerminalModes, uint> terminalModeValues, int bufferSize)
+        {
+            return new ShellStream(session, terminalName, columns, rows, width, height, terminalModeValues, bufferSize);
+        }
+
         /// <summary>
         /// Creates an <see cref="IRemotePathTransformation"/> that encloses a path in double quotes, and escapes
         /// any embedded double quote with a backslash.

+ 40 - 12
src/Renci.SshNet/ShellStream.cs

@@ -16,10 +16,10 @@ namespace Renci.SshNet
     public class ShellStream : Stream
     {
         private const string CrLf = "\r\n";
-        private const int BufferSize = 1024;
 
         private readonly ISession _session;
         private readonly Encoding _encoding;
+        private readonly int _bufferSize;
         private readonly Queue<byte> _incoming;
         private readonly Queue<byte> _outgoing;
         private IChannelSession _channel;
@@ -53,10 +53,34 @@ namespace Renci.SshNet
             }
         }
 
-        internal ShellStream(ISession session, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary<TerminalModes, uint> terminalModeValues)
+        /// <summary>
+        /// Gets the number of bytes that will be written to the internal buffer.
+        /// </summary>
+        /// <value>
+        /// The number of bytes that will be written to the internal buffer.
+        /// </value>
+        internal int BufferSize
+        {
+            get { return _bufferSize; }
+        }
+
+
+        /// <summary>
+        /// Initializes a new <see cref="ShellStream"/> instance.
+        /// </summary>
+        /// <param name="session">The SSH session.</param>
+        /// <param name="terminalName">The <c>TERM</c> environment variable.</param>
+        /// <param name="columns">The terminal width in columns.</param>
+        /// <param name="rows">The terminal width in rows.</param>
+        /// <param name="width">The terminal height in pixels.</param>
+        /// <param name="height">The terminal height in pixels.</param>
+        /// <param name="terminalModeValues">The terminal mode values.</param>
+        /// <param name="bufferSize">The size of the buffer.</param>
+        internal ShellStream(ISession session, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary<TerminalModes, uint> terminalModeValues, int bufferSize)
         {
             _encoding = session.ConnectionInfo.Encoding;
             _session = session;
+            _bufferSize = bufferSize;
             _incoming = new Queue<byte>();
             _outgoing = new Queue<byte>();
 
@@ -110,22 +134,27 @@ namespace Renci.SshNet
         /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device.
         /// </summary>
         /// <exception cref="T:System.IO.IOException">An I/O error occurs.</exception>
+        /// <exception cref="ObjectDisposedException">Methods were called after the stream was closed.</exception>
         public override void Flush()
         {
             if (_channel == null)
             {
                 throw new ObjectDisposedException("ShellStream");
             }
-            _channel.SendData(_outgoing.ToArray());
-            _outgoing.Clear();
+
+            if (_outgoing.Count > 0)
+            {
+                _channel.SendData(_outgoing.ToArray());
+                _outgoing.Clear();
+            }
         }
 
         /// <summary>
         /// Gets the length in bytes of the stream.
         /// </summary>
         /// <returns>A long value representing the length of the stream in bytes.</returns>
-        /// <exception cref="T:System.NotSupportedException">A class derived from Stream does not support seeking.</exception>
-        /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed.</exception>
+        /// <exception cref="NotSupportedException">A class derived from Stream does not support seeking.</exception>
+        /// <exception cref="ObjectDisposedException">Methods were called after the stream was closed.</exception>
         public override long Length
         {
             get
@@ -226,13 +255,12 @@ namespace Renci.SshNet
         {
             foreach (var b in buffer.Take(offset, count))
             {
-                _outgoing.Enqueue(b);
-                if (_outgoing.Count < BufferSize)
+                if (_outgoing.Count == _bufferSize)
                 {
-                    continue;
+                    Flush();
                 }
 
-                Flush();
+                _outgoing.Enqueue(b);
             }
         }
 
@@ -662,9 +690,9 @@ namespace Renci.SshNet
         }
 
         /// <summary>
-        /// Releases the unmanaged resources used by the <see cref="T:System.IO.Stream"/> and optionally releases the managed resources.
+        /// Releases the unmanaged resources used by the <see cref="Stream"/> and optionally releases the managed resources.
         /// </summary>
-        /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
+        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
         protected override void Dispose(bool disposing)
         {
             base.Dispose(disposing);

+ 4 - 4
src/Renci.SshNet/SshClient.cs

@@ -412,7 +412,7 @@ namespace Renci.SshNet
         /// <param name="rows">The terminal width in rows.</param>
         /// <param name="width">The terminal height in pixels.</param>
         /// <param name="height">The terminal height in pixels.</param>
-        /// <param name="bufferSize">Size of the buffer.</param>
+        /// <param name="bufferSize">The size of the buffer.</param>
         /// <returns>
         /// The created <see cref="ShellStream"/> instance.
         /// </returns>
@@ -440,7 +440,7 @@ namespace Renci.SshNet
         /// <param name="rows">The terminal width in rows.</param>
         /// <param name="width">The terminal height in pixels.</param>
         /// <param name="height">The terminal height in pixels.</param>
-        /// <param name="bufferSize">Size of the buffer.</param>
+        /// <param name="bufferSize">The size of the buffer.</param>
         /// <param name="terminalModeValues">The terminal mode values.</param>
         /// <returns>
         /// The created <see cref="ShellStream"/> instance.
@@ -452,7 +452,7 @@ namespace Renci.SshNet
         /// You can get a detailed list of these cababilities by using the ‘infocmp’ command.
         /// </para>
         /// <para>
-        /// The column/row dimensions override the pixel dimensions(when nonzero). Pixel dimensions refer
+        /// The column/row dimensions override the pixel dimensions(when non-zero). Pixel dimensions refer
         /// to the drawable area of the window.
         /// </para>
         /// </remarks>
@@ -460,7 +460,7 @@ namespace Renci.SshNet
         {
             EnsureSessionIsOpen();
 
-            return new ShellStream(Session, terminalName, columns, rows, width, height, terminalModeValues);
+            return ServiceFactory.CreateShellStream(Session, terminalName, columns, rows, width, height, terminalModeValues, bufferSize);
         }
 
         /// <summary>