浏览代码

Merge branch 'develop'

drieseng 4 年之前
父节点
当前提交
84b9281149
共有 100 个文件被更改,包括 7051 次插入1155 次删除
  1. 3 0
      src/Renci.SshNet.Silverlight/Renci.SshNet.Silverlight.csproj
  2. 3 0
      src/Renci.SshNet.Silverlight5/Renci.SshNet.Silverlight5.csproj
  3. 35 0
      src/Renci.SshNet.Tests/Classes/BaseClientTestBase.cs
  4. 26 35
      src/Renci.SshNet.Tests/Classes/BaseClientTest_Connect_OnConnectedThrowsException.cs
  5. 27 36
      src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAliveInterval_NegativeOne.cs
  6. 27 36
      src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAliveInterval_NotNegativeOne.cs
  7. 30 41
      src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAlivesNotSentConcurrently.cs
  8. 107 0
      src/Renci.SshNet.Tests/Classes/BaseClientTest_Disconnected_Connect.cs
  9. 27 36
      src/Renci.SshNet.Tests/Classes/BaseClientTest_Disconnected_KeepAliveInterval_NotNegativeOne.cs
  10. 17 36
      src/Renci.SshNet.Tests/Classes/BaseClientTest_NotConnected_KeepAliveInterval_NotNegativeOne.cs
  11. 1 1
      src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInAlternateBranch.cs
  12. 2 0
      src/Renci.SshNet.Tests/Classes/Common/BigIntegerTest.cs
  13. 155 0
      src/Renci.SshNet.Tests/Classes/Common/PacketDumpTest.cs
  14. 12 13
      src/Renci.SshNet.Tests/Classes/Common/PipeStream_Close_BlockingRead.cs
  15. 14 14
      src/Renci.SshNet.Tests/Classes/Common/PipeStream_Close_BlockingWrite.cs
  16. 12 13
      src/Renci.SshNet.Tests/Classes/Common/PipeStream_Flush_BytesRemainingAfterRead.cs
  17. 12 12
      src/Renci.SshNet.Tests/Classes/Common/PipeStream_Flush_NoBytesRemainingAfterRead.cs
  18. 44 0
      src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTestBase.cs
  19. 103 0
      src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_ConnectionRefusedByServer.cs
  20. 118 0
      src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_ConnectionSucceeded.cs
  21. 42 0
      src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_HostNameInvalid.cs
  22. 108 0
      src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_TimeoutConnectingToServer.cs
  23. 35 0
      src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTestBase.cs
  24. 111 0
      src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ConnectionToProxyRefused.cs
  25. 115 0
      src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyClosesConnectionBeforeStatusLineIsSent.cs
  26. 49 0
      src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyHostInvalid.cs
  27. 131 0
      src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyPasswordIsEmpty.cs
  28. 131 0
      src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyPasswordIsNull.cs
  29. 120 0
      src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseDoesNotContainHttpStatusLine.cs
  30. 135 0
      src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseStatusIs200_ExtraTextBeforeStatusLine.cs
  31. 135 0
      src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseStatusIs200_HeadersAndContent.cs
  32. 133 0
      src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseStatusIs200_OnlyHeaders.cs
  33. 120 0
      src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseStatusIsNot200.cs
  34. 131 0
      src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyUserNameIsEmpty.cs
  35. 128 0
      src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyUserNameIsNotNullAndNotEmpty.cs
  36. 132 0
      src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyUserNameIsNull.cs
  37. 115 0
      src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_TimeoutConnectingToProxy.cs
  38. 169 0
      src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_TimeoutReadingHttpContent.cs
  39. 139 0
      src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_TimeoutReadingStatusLine.cs
  40. 122 0
      src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ConnectionClosedByServer_NoDataSentByServer.cs
  41. 132 0
      src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseContainsNullCharacter.cs
  42. 128 0
      src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseInvalid_SshIdentificationOnlyContainsProtocolVersion.cs
  43. 119 0
      src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseValid_Comments.cs
  44. 119 0
      src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseValid_NoComments.cs
  45. 113 0
      src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_TimeoutReadingIdentificationString.cs
  46. 49 0
      src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTestBase.cs
  47. 134 0
      src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_ConnectionRejectedByProxy.cs
  48. 166 0
      src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_ConnectionSucceeded.cs
  49. 102 0
      src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_ConnectionToProxyRefused.cs
  50. 105 0
      src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutConnectingToProxy.cs
  51. 143 0
      src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingDestinationAddress.cs
  52. 139 0
      src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingReplyCode.cs
  53. 131 0
      src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingReplyVersion.cs
  54. 68 0
      src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTestBase.cs
  55. 103 0
      src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_ConnectionToProxyRefused.cs
  56. 206 0
      src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_NoAuthentication_ConnectionSucceeded.cs
  57. 123 0
      src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_ProxySocksVersionIsNotSupported.cs
  58. 107 0
      src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_TimeoutConnectingToProxy.cs
  59. 177 0
      src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_AuthenticationFailed.cs
  60. 220 0
      src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_ConnectionSucceeded.cs
  61. 149 0
      src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_PasswordExceedsMaximumLength.cs
  62. 149 0
      src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_UserNameExceedsMaximumLength.cs
  63. 139 0
      src/Renci.SshNet.Tests/Classes/Connection/SshIdentificationTest.cs
  64. 1 1
      src/Renci.SshNet.Tests/Classes/Messages/Connection/ChannelExtendedDataMessageTest.cs
  65. 17 0
      src/Renci.SshNet.Tests/Classes/NetConfClientTestBase.cs
  66. 9 31
      src/Renci.SshNet.Tests/Classes/NetConfClientTest_Connect_NetConfSessionConnectFailure.cs
  67. 39 33
      src/Renci.SshNet.Tests/Classes/NetConfClientTest_Dispose_Connected.cs
  68. 27 15
      src/Renci.SshNet.Tests/Classes/NetConfClientTest_Dispose_Disconnected.cs
  69. 27 27
      src/Renci.SshNet.Tests/Classes/NetConfClientTest_Dispose_Disposed.cs
  70. 40 29
      src/Renci.SshNet.Tests/Classes/NetConfClientTest_Finalize_Connected.cs
  71. 5 28
      src/Renci.SshNet.Tests/Classes/ScpClientTestBase.cs
  72. 8 1
      src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndDirectoryInfo_SendExecRequestReturnsFalse.cs
  73. 8 1
      src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndFileInfo_SendExecRequestReturnsFalse.cs
  74. 18 10
      src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndStream_SendExecRequestReturnsFalse.cs
  75. 8 1
      src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_DirectoryInfoAndPath_SendExecRequestReturnsFalse.cs
  76. 20 12
      src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_SendExecRequestReturnsFalse.cs
  77. 19 11
      src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_Success.cs
  78. 18 10
      src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_StreamAndPath_SendExecRequestReturnsFalse.cs
  79. 140 0
      src/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateConnector.cs
  80. 2 229
      src/Renci.SshNet.Tests/Classes/SessionTest.HttpProxy.cs
  81. 16 331
      src/Renci.SshNet.Tests/Classes/SessionTest.cs
  82. 35 0
      src/Renci.SshNet.Tests/Classes/SessionTestBase.cs
  83. 265 0
      src/Renci.SshNet.Tests/Classes/SessionTest_ConnectToServerFails.cs
  84. 21 1
      src/Renci.SshNet.Tests/Classes/SessionTest_Connected.cs
  85. 63 37
      src/Renci.SshNet.Tests/Classes/SessionTest_ConnectedBase.cs
  86. 9 1
      src/Renci.SshNet.Tests/Classes/SessionTest_Connected_Disconnect.cs
  87. 2 1
      src/Renci.SshNet.Tests/Classes/SessionTest_Connected_GlobalRequestMessageAfterAuthenticationRace.cs
  88. 55 31
      src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerAndClientDisconnectRace.cs
  89. 2 1
      src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerSendsDisconnectMessage.cs
  90. 2 1
      src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerSendsDisconnectMessageAndShutsDownSocket.cs
  91. 2 1
      src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerSendsUnsupportedMessageType.cs
  92. 18 13
      src/Renci.SshNet.Tests/Classes/SessionTest_NotConnected.cs
  93. 44 12
      src/Renci.SshNet.Tests/Classes/SessionTest_SocketConnected_BadPacketAndDispose.cs
  94. 2 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileReaderTestBase.cs
  95. 1 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanRead_Closed_FileAccessRead.cs
  96. 2 2
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Ctor_FileModeAppend_FileAccessWrite.cs
  97. 3 3
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthGreatherThanPosition.cs
  98. 2 2
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthLessThanPosition.cs
  99. 2 2
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthGreatherThanPosition.cs
  100. 2 2
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthLessThanPosition.cs

+ 3 - 0
src/Renci.SshNet.Silverlight/Renci.SshNet.Silverlight.csproj

@@ -1183,6 +1183,9 @@
     </Compile>
     <Compile Include="..\Renci.SshNet\SftpClient.cs">
       <Link>SftpClient.cs</Link>
+    </Compile>
+	<Compile Include="..\Renci.SshNet\ISftpClient.cs">
+      <Link>ISftpClient.cs</Link>
     </Compile>
     <Compile Include="..\Renci.SshNet\Sftp\Flags.cs">
       <Link>Sftp\Flags.cs</Link>

+ 3 - 0
src/Renci.SshNet.Silverlight5/Renci.SshNet.Silverlight5.csproj

@@ -1189,6 +1189,9 @@
     </Compile>
     <Compile Include="..\Renci.SshNet\SftpClient.cs">
       <Link>SftpClient.cs</Link>
+    </Compile>
+	<Compile Include="..\Renci.SshNet\ISftpClient.cs">
+      <Link>ISftpClient.cs</Link>
     </Compile>
     <Compile Include="..\Renci.SshNet\Sftp\Flags.cs">
       <Link>Sftp\Flags.cs</Link>

+ 35 - 0
src/Renci.SshNet.Tests/Classes/BaseClientTestBase.cs

@@ -0,0 +1,35 @@
+using Moq;
+using Renci.SshNet.Connection;
+using Renci.SshNet.Tests.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    public abstract class BaseClientTestBase : TripleATestBase
+    {
+        internal Mock<IServiceFactory> _serviceFactoryMock { get; private set; }
+        internal Mock<ISocketFactory> _socketFactoryMock { get; private set; }
+        internal Mock<ISession> _sessionMock { get; private set; }
+
+        protected virtual void CreateMocks()
+        {
+            _serviceFactoryMock = new Mock<IServiceFactory>(MockBehavior.Strict);
+            _socketFactoryMock = new Mock<ISocketFactory>(MockBehavior.Strict);
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+        }
+
+        protected virtual void SetupData()
+        {
+        }
+
+        protected virtual void SetupMocks()
+        {
+        }
+
+        protected override void Arrange()
+        {
+            CreateMocks();
+            SetupData();
+            SetupMocks();
+        }
+    }
+}

+ 26 - 35
src/Renci.SshNet.Tests/Classes/BaseClientTest_Connect_OnConnectedThrowsException.cs

@@ -9,24 +9,30 @@ using Renci.SshNet.Security;
 namespace Renci.SshNet.Tests.Classes
 {
     [TestClass]
-    public class BaseClientTest_Connect_OnConnectedThrowsException
+    public class BaseClientTest_Connect_OnConnectedThrowsException : BaseClientTestBase
     {
-        private Mock<IServiceFactory> _serviceFactoryMock;
-        private Mock<ISession> _sessionMock;
         private MyClient _client;
         private ConnectionInfo _connectionInfo;
         private ApplicationException _onConnectException;
         private ApplicationException _actualException;
 
-        [TestInitialize]
-        public void Setup()
+        protected override void SetupData()
         {
-            Arrange();
-            Act();
+            _connectionInfo = new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "pwd"));
+            _onConnectException = new ApplicationException();
         }
 
-        [TestCleanup]
-        public void Cleanup()
+        protected override void SetupMocks()
+        {
+            _serviceFactoryMock.Setup(p => p.CreateSocketFactory())
+                               .Returns(_socketFactoryMock.Object);
+            _serviceFactoryMock.Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object))
+                               .Returns(_sessionMock.Object);
+            _sessionMock.Setup(p => p.Connect());
+            _sessionMock.Setup(p => p.Dispose());
+        }
+
+        protected override void TearDown()
         {
             if (_client != null)
             {
@@ -36,31 +42,9 @@ namespace Renci.SshNet.Tests.Classes
             }
         }
 
-        private void SetupData()
-        {
-            _connectionInfo = new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "pwd"));
-            _onConnectException = new ApplicationException();
-        }
-
-        private void CreateMocks()
+        protected override void Arrange()
         {
-            _serviceFactoryMock = new Mock<IServiceFactory>(MockBehavior.Strict);
-            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
-        }
-
-        private void SetupMocks()
-        {
-            _serviceFactoryMock.Setup(p => p.CreateSession(_connectionInfo))
-                               .Returns(_sessionMock.Object);
-            _sessionMock.Setup(p => p.Connect());
-            _sessionMock.Setup(p => p.Dispose());
-        }
-
-        protected void Arrange()
-        {
-            SetupData();
-            CreateMocks();
-            SetupMocks();
+            base.Arrange();
 
             _client = new MyClient(_connectionInfo, false, _serviceFactoryMock.Object)
                 {
@@ -68,7 +52,7 @@ namespace Renci.SshNet.Tests.Classes
                 };
         }
 
-        protected void Act()
+        protected override void Act()
         {
             try
             {
@@ -88,10 +72,17 @@ namespace Renci.SshNet.Tests.Classes
             Assert.AreSame(_onConnectException, _actualException);
         }
 
+        [TestMethod]
+        public void CreateSocketFactoryOnServiceFactoryShouldBeInvokedOnce()
+        {
+            _serviceFactoryMock.Verify(p => p.CreateSocketFactory(), Times.Once);
+        }
+
         [TestMethod]
         public void CreateSessionOnServiceFactoryShouldBeInvokedOnce()
         {
-            _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo), Times.Once);
+            _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object),
+                                       Times.Once);
         }
 
         [TestMethod]

+ 27 - 36
src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAliveInterval_NegativeOne.cs

@@ -2,54 +2,31 @@
 using System.Threading;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using Moq;
+using Renci.SshNet.Connection;
 using Renci.SshNet.Messages.Transport;
 
 namespace Renci.SshNet.Tests.Classes
 {
     [TestClass]
-    public class BaseClientTest_Connected_KeepAliveInterval_NegativeOne
+    public class BaseClientTest_Connected_KeepAliveInterval_NegativeOne : BaseClientTestBase
     {
-        private Mock<IServiceFactory> _serviceFactoryMock;
-        private Mock<ISession> _sessionMock;
         private BaseClient _client;
         private ConnectionInfo _connectionInfo;
         private TimeSpan _keepAliveInterval;
         private int _keepAliveCount;
 
-        [TestInitialize]
-        public void Setup()
-        {
-            Arrange();
-            Act();
-        }
-
-        [TestCleanup]
-        public void Cleanup()
-        {
-            if (_client != null)
-            {
-                _sessionMock.Setup(p => p.OnDisconnecting());
-                _sessionMock.Setup(p => p.Dispose());
-                _client.Dispose();
-            }
-        }
-
-        private void SetupData()
+        protected override void SetupData()
         {
             _connectionInfo = new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "pwd"));
             _keepAliveInterval = TimeSpan.FromMilliseconds(100d);
             _keepAliveCount = 0;
         }
 
-        private void CreateMocks()
-        {
-            _serviceFactoryMock = new Mock<IServiceFactory>(MockBehavior.Strict);
-            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
-        }
-
-        private void SetupMocks()
+        protected override void SetupMocks()
         {
-            _serviceFactoryMock.Setup(p => p.CreateSession(_connectionInfo))
+            _serviceFactoryMock.Setup(p => p.CreateSocketFactory())
+                               .Returns(_socketFactoryMock.Object);
+            _serviceFactoryMock.Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object))
                                .Returns(_sessionMock.Object);
             _sessionMock.Setup(p => p.Connect());
             _sessionMock.Setup(p => p.IsConnected).Returns(true);
@@ -58,18 +35,26 @@ namespace Renci.SshNet.Tests.Classes
                         .Callback(() => Interlocked.Increment(ref _keepAliveCount));
         }
 
-        protected void Arrange()
+        protected override void Arrange()
         {
-            SetupData();
-            CreateMocks();
-            SetupMocks();
+            base.Arrange();
 
             _client = new MyClient(_connectionInfo, false, _serviceFactoryMock.Object);
             _client.Connect();
             _client.KeepAliveInterval = _keepAliveInterval;
         }
 
-        protected void Act()
+        protected override void TearDown()
+        {
+            if (_client != null)
+            {
+                _sessionMock.Setup(p => p.OnDisconnecting());
+                _sessionMock.Setup(p => p.Dispose());
+                _client.Dispose();
+            }
+        }
+
+        protected override void Act()
         {
             // allow keep-alive to be sent once
             Thread.Sleep(150);
@@ -84,10 +69,16 @@ namespace Renci.SshNet.Tests.Classes
             Assert.AreEqual(TimeSpan.FromMilliseconds(-1), _client.KeepAliveInterval);
         }
 
+        [TestMethod]
+        public void CreateSocketFactoryOnServiceFactoryShouldBeInvokedOnce()
+        {
+            _serviceFactoryMock.Verify(p => p.CreateSocketFactory(), Times.Once);
+        }
+
         [TestMethod]
         public void CreateSessionOnServiceFactoryShouldBeInvokedOnce()
         {
-            _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo), Times.Once);
+            _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object), Times.Once);
         }
 
         [TestMethod]

+ 27 - 36
src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAliveInterval_NotNegativeOne.cs

@@ -7,49 +7,25 @@ using Renci.SshNet.Messages.Transport;
 namespace Renci.SshNet.Tests.Classes
 {
     [TestClass]
-    public class BaseClientTest_Connected_KeepAliveInterval_NotNegativeOne
+    public class BaseClientTest_Connected_KeepAliveInterval_NotNegativeOne : BaseClientTestBase
     {
-        private Mock<IServiceFactory> _serviceFactoryMock;
-        private Mock<ISession> _sessionMock;
         private BaseClient _client;
         private ConnectionInfo _connectionInfo;
         private TimeSpan _keepAliveInterval;
         private int _keepAliveCount;
 
-        [TestInitialize]
-        public void Setup()
-        {
-            Arrange();
-            Act();
-        }
-
-        [TestCleanup]
-        public void Cleanup()
-        {
-            if (_client != null)
-            {
-                _sessionMock.Setup(p => p.OnDisconnecting());
-                _sessionMock.Setup(p => p.Dispose());
-                _client.Dispose();
-            }
-        }
-
-        private void SetupData()
+        protected override void SetupData()
         {
             _connectionInfo = new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "pwd"));
             _keepAliveInterval = TimeSpan.FromMilliseconds(50d);
             _keepAliveCount = 0;
         }
 
-        private void CreateMocks()
-        {
-            _serviceFactoryMock = new Mock<IServiceFactory>(MockBehavior.Strict);
-            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
-        }
-
-        private void SetupMocks()
+        protected override void SetupMocks()
         {
-            _serviceFactoryMock.Setup(p => p.CreateSession(_connectionInfo))
+            _serviceFactoryMock.Setup(p => p.CreateSocketFactory())
+                               .Returns(_socketFactoryMock.Object);
+            _serviceFactoryMock.Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object))
                                .Returns(_sessionMock.Object);
             _sessionMock.Setup(p => p.Connect());
             _sessionMock.Setup(p => p.IsConnected).Returns(true);
@@ -58,17 +34,25 @@ namespace Renci.SshNet.Tests.Classes
                         .Callback(() => Interlocked.Increment(ref _keepAliveCount));
         }
 
-        protected void Arrange()
+        protected override void Arrange()
         {
-            SetupData();
-            CreateMocks();
-            SetupMocks();
+            base.Arrange();
 
             _client = new MyClient(_connectionInfo, false, _serviceFactoryMock.Object);
             _client.Connect();
         }
 
-        protected void Act()
+        protected override void TearDown()
+        {
+            if (_client != null)
+            {
+                _sessionMock.Setup(p => p.OnDisconnecting());
+                _sessionMock.Setup(p => p.Dispose());
+                _client.Dispose();
+            }
+        }
+
+        protected override void Act()
         {
             _client.KeepAliveInterval = _keepAliveInterval;
 
@@ -82,10 +66,17 @@ namespace Renci.SshNet.Tests.Classes
             Assert.AreEqual(_keepAliveInterval, _client.KeepAliveInterval);
         }
 
+        [TestMethod]
+        public void CreateSocketFactoryOnServiceFactoryShouldBeInvokedOnce()
+        {
+            _serviceFactoryMock.Verify(p => p.CreateSocketFactory(), Times.Once);
+        }
+
         [TestMethod]
         public void CreateSessionOnServiceFactoryShouldBeInvokedOnce()
         {
-            _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo), Times.Once);
+            _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object),
+                                       Times.Once);
         }
 
         [TestMethod]

+ 30 - 41
src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAlivesNotSentConcurrently.cs

@@ -7,65 +7,44 @@ using Renci.SshNet.Messages.Transport;
 namespace Renci.SshNet.Tests.Classes
 {
     [TestClass]
-    public class BaseClientTest_Connected_KeepAlivesNotSentConcurrently
+    public class BaseClientTest_Connected_KeepAlivesNotSentConcurrently : BaseClientTestBase
     {
-        private Mock<IServiceFactory> _serviceFactoryMock;
-        private Mock<ISession> _sessionMock;
         private MockSequence _mockSequence;
         private BaseClient _client;
         private ConnectionInfo _connectionInfo;
         private ManualResetEvent _keepAliveSent;
 
-        [TestInitialize]
-        public void Setup()
-        {
-            Arrange();
-            Act();
-        }
-
-        [TestCleanup]
-        public void Cleanup()
-        {
-            if (_client != null)
-            {
-                _sessionMock.InSequence(_mockSequence).Setup(p => p.OnDisconnecting());
-                _sessionMock.InSequence(_mockSequence).Setup(p => p.Dispose());
-                _client.Dispose();
-            }
-        }
-
-        private void SetupData()
+        protected override void SetupData()
         {
             _connectionInfo = new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "pwd"));
             _keepAliveSent = new ManualResetEvent(false);
         }
 
-        private void CreateMocks()
-        {
-            _serviceFactoryMock = new Mock<IServiceFactory>(MockBehavior.Strict);
-            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
-        }
-
-        private void SetupMocks()
+        protected override void SetupMocks()
         {
             _mockSequence = new MockSequence();
 
-            _serviceFactoryMock.InSequence(_mockSequence).Setup(p => p.CreateSession(_connectionInfo)).Returns(_sessionMock.Object);
-            _sessionMock.InSequence(_mockSequence).Setup(p => p.Connect());
-            _sessionMock.InSequence(_mockSequence).Setup(p => p.TrySendMessage(It.IsAny<IgnoreMessage>()))
+            _serviceFactoryMock.InSequence(_mockSequence)
+                               .Setup(p => p.CreateSocketFactory())
+                               .Returns(_socketFactoryMock.Object);
+            _serviceFactoryMock.InSequence(_mockSequence)
+                               .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object))
+                               .Returns(_sessionMock.Object);
+            _sessionMock.InSequence(_mockSequence)
+                        .Setup(p => p.Connect());
+            _sessionMock.InSequence(_mockSequence)
+                        .Setup(p => p.TrySendMessage(It.IsAny<IgnoreMessage>()))
                         .Returns(true)
                         .Callback(() =>
-                        {
-                            Thread.Sleep(300);
-                            _keepAliveSent.Set();
-                        });
+                            {
+                                Thread.Sleep(300);
+                                _keepAliveSent.Set();
+                            });
         }
 
-        protected void Arrange()
+        protected override void Arrange()
         {
-            SetupData();
-            CreateMocks();
-            SetupMocks();
+            base.Arrange();
 
             _client = new MyClient(_connectionInfo, false, _serviceFactoryMock.Object)
                 {
@@ -74,7 +53,17 @@ namespace Renci.SshNet.Tests.Classes
             _client.Connect();
         }
 
-        protected void Act()
+        protected override void TearDown()
+        {
+            if (_client != null)
+            {
+                _sessionMock.InSequence(_mockSequence).Setup(p => p.OnDisconnecting());
+                _sessionMock.InSequence(_mockSequence).Setup(p => p.Dispose());
+                _client.Dispose();
+            }
+        }
+
+        protected override void Act()
         {
             // should keep-alive message be sent concurrently, then multiple keep-alive
             // message would be sent during this sleep period

+ 107 - 0
src/Renci.SshNet.Tests/Classes/BaseClientTest_Disconnected_Connect.cs

@@ -0,0 +1,107 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Connection;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class BaseClientTest_Disconnected_Connect : BaseClientTestBase
+    {
+        private Mock<ISocketFactory> _socketFactory2Mock;
+        private Mock<ISession> _session2Mock;
+        private BaseClient _client;
+        private ConnectionInfo _connectionInfo;
+
+        protected override void SetupData()
+        {
+            _connectionInfo = new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "pwd"));
+        }
+
+        protected override void CreateMocks()
+        {
+            base.CreateMocks();
+
+            _socketFactory2Mock = new Mock<ISocketFactory>(MockBehavior.Strict);
+            _session2Mock = new Mock<ISession>(MockBehavior.Strict);
+        }
+
+        protected override void SetupMocks()
+        {
+            var sequence = new MockSequence();
+
+            _serviceFactoryMock.InSequence(sequence)
+                               .Setup(p => p.CreateSocketFactory())
+                               .Returns(_socketFactoryMock.Object);
+            _serviceFactoryMock.InSequence(sequence)
+                               .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object))
+                               .Returns(_sessionMock.Object);
+            _sessionMock.InSequence(sequence)
+                        .Setup(p => p.Connect());
+            _sessionMock.InSequence(sequence)
+                        .Setup(p => p.OnDisconnecting());
+            _sessionMock.InSequence(sequence)
+                        .Setup(p => p.Dispose());
+            _serviceFactoryMock.InSequence(sequence)
+                               .Setup(p => p.CreateSocketFactory())
+                               .Returns(_socketFactory2Mock.Object);
+            _serviceFactoryMock.InSequence(sequence)
+                               .Setup(p => p.CreateSession(_connectionInfo, _socketFactory2Mock.Object))
+                               .Returns(_session2Mock.Object);
+            _session2Mock.InSequence(sequence)
+                        .Setup(p => p.Connect());
+        }
+
+        protected override void Arrange()
+        {
+            base.Arrange();
+
+            _client = new MyClient(_connectionInfo, false, _serviceFactoryMock.Object);
+            _client.Connect();
+            _client.Disconnect();
+        }
+
+        protected override void TearDown()
+        {
+            if (_client != null)
+            {
+                _session2Mock.Setup(p => p.OnDisconnecting());
+                _session2Mock.Setup(p => p.Dispose());
+                _client.Dispose();
+            }
+        }
+
+        protected override void Act()
+        {
+            _client.Connect();
+        }
+
+        [TestMethod]
+        public void CreateSocketFactoryOnServiceFactoryShouldBeInvokedTwic()
+        {
+            _serviceFactoryMock.Verify(p => p.CreateSocketFactory(), Times.Exactly(2));
+        }
+
+        [TestMethod]
+        public void CreateSessionOnServiceFactoryShouldBeInvokedTwice()
+        {
+            _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object),
+                                       Times.Once);
+            _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo, _socketFactory2Mock.Object),
+                                       Times.Once);
+        }
+
+        [TestMethod]
+        public void ConnectOnSessionShouldBeInvokedTwice()
+        {
+            _sessionMock.Verify(p => p.Connect(), Times.Once);
+            _session2Mock.Verify(p => p.Connect(), Times.Once);
+        }
+
+        private class MyClient : BaseClient
+        {
+            public MyClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo, IServiceFactory serviceFactory) : base(connectionInfo, ownsConnectionInfo, serviceFactory)
+            {
+            }
+        }
+    }
+}

+ 27 - 36
src/Renci.SshNet.Tests/Classes/BaseClientTest_Disconnected_KeepAliveInterval_NotNegativeOne.cs

@@ -7,47 +7,23 @@ using Renci.SshNet.Messages.Transport;
 namespace Renci.SshNet.Tests.Classes
 {
     [TestClass]
-    public class BaseClientTest_Disconnected_KeepAliveInterval_NotNegativeOne
+    public class BaseClientTest_Disconnected_KeepAliveInterval_NotNegativeOne : BaseClientTestBase
     {
-        private Mock<IServiceFactory> _serviceFactoryMock;
-        private Mock<ISession> _sessionMock;
         private BaseClient _client;
         private ConnectionInfo _connectionInfo;
         private TimeSpan _keepAliveInterval;
 
-        [TestInitialize]
-        public void Setup()
-        {
-            Arrange();
-            Act();
-        }
-
-        [TestCleanup]
-        public void Cleanup()
-        {
-            if (_client != null)
-            {
-                _sessionMock.Setup(p => p.OnDisconnecting());
-                _sessionMock.Setup(p => p.Dispose());
-                _client.Dispose();
-            }
-        }
-
-        private void SetupData()
+        protected override void SetupData()
         {
             _connectionInfo = new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "pwd"));
             _keepAliveInterval = TimeSpan.FromMilliseconds(50d);
         }
 
-        private void CreateMocks()
-        {
-            _serviceFactoryMock = new Mock<IServiceFactory>(MockBehavior.Strict);
-            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
-        }
-
-        private void SetupMocks()
+        protected override void SetupMocks()
         {
-            _serviceFactoryMock.Setup(p => p.CreateSession(_connectionInfo))
+            _serviceFactoryMock.Setup(p => p.CreateSocketFactory())
+                               .Returns(_socketFactoryMock.Object);
+            _serviceFactoryMock.Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object))
                                .Returns(_sessionMock.Object);
             _sessionMock.Setup(p => p.Connect());
             _sessionMock.Setup(p => p.IsConnected).Returns(false);
@@ -55,17 +31,25 @@ namespace Renci.SshNet.Tests.Classes
                         .Returns(true);
         }
 
-        protected void Arrange()
+        protected override void Arrange()
         {
-            SetupData();
-            CreateMocks();
-            SetupMocks();
+            base.Arrange();
 
             _client = new MyClient(_connectionInfo, false, _serviceFactoryMock.Object);
             _client.Connect();
         }
 
-        protected void Act()
+        protected override void TearDown()
+        {
+            if (_client != null)
+            {
+                _sessionMock.Setup(p => p.OnDisconnecting());
+                _sessionMock.Setup(p => p.Dispose());
+                _client.Dispose();
+            }
+        }
+
+        protected override void Act()
         {
             _client.KeepAliveInterval = _keepAliveInterval;
 
@@ -79,10 +63,17 @@ namespace Renci.SshNet.Tests.Classes
             Assert.AreEqual(_keepAliveInterval, _client.KeepAliveInterval);
         }
 
+        [TestMethod]
+        public void CreateSocketFactoryOnServiceFactoryShouldBeInvokedOnce()
+        {
+            _serviceFactoryMock.Verify(p => p.CreateSocketFactory(), Times.Once);
+        }
+
         [TestMethod]
         public void CreateSessionOnServiceFactoryShouldBeInvokedOnce()
         {
-            _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo), Times.Once);
+            _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object),
+                                       Times.Once);
         }
 
         [TestMethod]

+ 17 - 36
src/Renci.SshNet.Tests/Classes/BaseClientTest_NotConnected_KeepAliveInterval_NotNegativeOne.cs

@@ -7,60 +7,38 @@ using Renci.SshNet.Messages.Transport;
 namespace Renci.SshNet.Tests.Classes
 {
     [TestClass]
-    public class BaseClientTest_NotConnected_KeepAliveInterval_NotNegativeOne
+    public class BaseClientTest_NotConnected_KeepAliveInterval_NotNegativeOne : BaseClientTestBase
     {
-        private Mock<IServiceFactory> _serviceFactoryMock;
-        private Mock<ISession> _sessionMock;
         private BaseClient _client;
         private ConnectionInfo _connectionInfo;
         private TimeSpan _keepAliveInterval;
         private int _keepAliveCount;
 
-        [TestInitialize]
-        public void Setup()
-        {
-            Arrange();
-            Act();
-        }
-
-        [TestCleanup]
-        public void Cleanup()
-        {
-            if (_client != null)
-            {
-                _sessionMock.Setup(p => p.OnDisconnecting());
-                _sessionMock.Setup(p => p.Dispose());
-                _client.Dispose();
-            }
-        }
-
-        private void SetupData()
+        protected override void SetupData()
         {
             _connectionInfo = new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "pwd"));
             _keepAliveInterval = TimeSpan.FromMilliseconds(100d);
             _keepAliveCount = 0;
         }
 
-        private void CreateMocks()
+        protected override void Arrange()
         {
-            _serviceFactoryMock = new Mock<IServiceFactory>(MockBehavior.Strict);
-            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
-        }
+            base.Arrange();
 
-        private static void SetupMocks()
-        {
+            _client = new MyClient(_connectionInfo, false, _serviceFactoryMock.Object);
         }
 
-        protected void Arrange()
+        protected override void TearDown()
         {
-            SetupData();
-            CreateMocks();
-            SetupMocks();
-
-            _client = new MyClient(_connectionInfo, false, _serviceFactoryMock.Object);
+            if (_client != null)
+            {
+                _sessionMock.Setup(p => p.OnDisconnecting());
+                _sessionMock.Setup(p => p.Dispose());
+                _client.Dispose();
+            }
         }
 
-        protected void Act()
+        protected override void Act()
         {
             _client.KeepAliveInterval = _keepAliveInterval;
 
@@ -77,7 +55,10 @@ namespace Renci.SshNet.Tests.Classes
         [TestMethod]
         public void ConnectShouldActivateKeepAliveIfSessionIs()
         {
-            _serviceFactoryMock.Setup(p => p.CreateSession(_connectionInfo)).Returns(_sessionMock.Object);
+            _serviceFactoryMock.Setup(p => p.CreateSocketFactory())
+                               .Returns(_socketFactoryMock.Object);
+            _serviceFactoryMock.Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object))
+                               .Returns(_sessionMock.Object);
             _sessionMock.Setup(p => p.Connect());
             _sessionMock.Setup(p => p.TrySendMessage(It.IsAny<IgnoreMessage>()))
                         .Returns(true)

+ 1 - 1
src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInAlternateBranch.cs

@@ -181,4 +181,4 @@ namespace Renci.SshNet.Tests.Classes
             PublicKeyAuthenticationMethodMock.Verify(p => p.Authenticate(SessionMock.Object), Times.Exactly(2));
         }
     }
-}
+}

+ 2 - 0
src/Renci.SshNet.Tests/Classes/Common/BigIntegerTest.cs

@@ -1506,10 +1506,12 @@ namespace Renci.SshNet.Tests.Classes.Common
             Assert.AreEqual("0", a.ToString(), "#4");
 
             a = new BigInteger();
+#pragma warning disable CS1718 // Comparison made to same variable
             Assert.AreEqual(true, a == a, "#5");
 
             a = new BigInteger();
             Assert.AreEqual(false, a < a, "#6");
+#pragma warning restore CS1718 // Comparison made to same variable
 
             a = new BigInteger();
             Assert.AreEqual(true, a < 10L, "#7");

+ 155 - 0
src/Renci.SshNet.Tests/Classes/Common/PacketDumpTest.cs

@@ -0,0 +1,155 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Renci.SshNet.Common;
+using System;
+
+namespace Renci.SshNet.Tests.Classes.Common
+{
+    [TestClass]
+    public class PacketDumpTest
+    {
+        [TestMethod]
+        public void Create_ByteArrayAndIndentLevel_DataIsNull()
+        {
+            const byte[] data = null;
+
+            try
+            {
+                PacketDump.Create(data, 0);
+                Assert.Fail();
+            }
+            catch (ArgumentNullException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+                Assert.AreEqual("data", ex.ParamName);
+            }
+        }
+
+        [TestMethod]
+        public void Create_ByteArrayAndIndentLevel_IndentLevelLessThanZero()
+        {
+            var data = new byte[0];
+
+            try
+            {
+                PacketDump.Create(data, -1);
+                Assert.Fail();
+            }
+            catch (ArgumentOutOfRangeException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+                Assert.AreEqual(string.Format("Cannot be less than zero.{0}Parameter name: {1}", Environment.NewLine, ex.ParamName), ex.Message);
+                Assert.AreEqual("indentLevel", ex.ParamName);
+            }
+        }
+
+        [TestMethod]
+        public void Create_ByteArrayAndIndentLevel_DataIsEmpty()
+        {
+            var data = new byte[0];
+
+            var actual = PacketDump.Create(data, 2);
+
+            Assert.AreEqual(string.Empty, actual);
+        }
+
+        [TestMethod]
+        public void Create_ByteArrayAndIndentLevel_DataIsMultipleOfLineWidth_IndentLevelTwo()
+        {
+            var data = new byte[]
+                {
+                    0x07, 0x00, 0x1f, 0x65, 0x20, 0x62, 0x09, 0x44, 0x7f, 0x0d, 0x0a, 0x36, 0x80, 0x53, 0x53, 0x48,
+                    0x2e, 0x4e, 0x45, 0x54, 0x20, 0x32, 0x30, 0x32, 0x30, 0x20, 0xf6, 0x7a, 0x32, 0x7f, 0x1f, 0x7e
+                };
+            var expected = "  00000000  07 00 1F 65 20 62 09 44 7F 0D 0A 36 80 53 53 48  ...e b.D...6.SSH" +
+                           Environment.NewLine +
+                           "  00000010  2E 4E 45 54 20 32 30 32 30 20 F6 7A 32 7F 1F 7E  .NET 2020 .z2..~";
+
+            var actual = PacketDump.Create(data, 2);
+
+            Assert.AreEqual(expected, actual);
+        }
+
+        [TestMethod]
+        public void Create_ByteArrayAndIndentLevel_DataIsMultipleOfLineWidth_IndentLevelZero()
+        {
+            var data = new byte[]
+                {
+                    0x07, 0x00, 0x1f, 0x65, 0x20, 0x62, 0x09, 0x44, 0x7f, 0x0d, 0x0a, 0x36, 0x80, 0x53, 0x53, 0x48,
+                    0x2e, 0x4e, 0x45, 0x54, 0x20, 0x32, 0x30, 0x32, 0x30, 0x20, 0xf6, 0x7a, 0x32, 0x7f, 0x1f, 0x7e
+                };
+            var expected = "00000000  07 00 1F 65 20 62 09 44 7F 0D 0A 36 80 53 53 48  ...e b.D...6.SSH" +
+                           Environment.NewLine +
+                           "00000010  2E 4E 45 54 20 32 30 32 30 20 F6 7A 32 7F 1F 7E  .NET 2020 .z2..~";
+
+            var actual = PacketDump.Create(data, 0);
+
+            Assert.AreEqual(expected, actual);
+        }
+
+        [TestMethod]
+        public void Create_ByteArrayAndIndentLevel_DataIsLineWith()
+        {
+            var data = new byte[]
+                {
+                    0x07, 0x00, 0x1f, 0x65, 0x20, 0x62, 0x09, 0x44, 0x7f, 0x0d, 0x0a, 0x36, 0x80, 0x53, 0x53, 0x48
+                };
+            var expected = "  00000000  07 00 1F 65 20 62 09 44 7F 0D 0A 36 80 53 53 48  ...e b.D...6.SSH";
+
+            var actual = PacketDump.Create(data, 2);
+
+            Assert.AreEqual(expected, actual);
+        }
+
+        [TestMethod]
+        public void Create_ByteArrayAndIndentLevel_DataIsLessThanLineWith()
+        {
+            var data = new byte[]
+                {
+                    0x07, 0x00, 0x1f, 0x65, 0x20, 0x62, 0x09, 0x44, 0x7f, 0x0d, 0x0a, 0x36, 0x80, 0x53
+                };
+            var expected = "  00000000  07 00 1F 65 20 62 09 44 7F 0D 0A 36 80 53        ...e b.D...6.S";
+
+            var actual = PacketDump.Create(data, 2);
+
+            Assert.AreEqual(expected, actual);
+        }
+
+        [TestMethod]
+        public void Create_ByteArrayAndIndentLevel_DataIsGreaterThanLineWidthButLessThanMultipleOfLineWidth()
+        {
+            var data = new byte[]
+                {
+                    0x07, 0x00, 0x1f, 0x65, 0x20, 0x62, 0x09, 0x44, 0x7f, 0x0d, 0x0a, 0x36, 0x80, 0x53, 0x53, 0x48,
+                    0x2e, 0x4e, 0x45, 0x54
+                };
+            var expected = "  00000000  07 00 1F 65 20 62 09 44 7F 0D 0A 36 80 53 53 48  ...e b.D...6.SSH" +
+                           Environment.NewLine +
+                           "  00000010  2E 4E 45 54                                      .NET";
+
+            var actual = PacketDump.Create(data, 2);
+
+            Assert.AreEqual(expected, actual);
+        }
+
+        [TestMethod]
+        public void Create_ByteArrayAndIndentLevel_DataIsGreaterThanMultipleOfLineWidth()
+        {
+            var data = new byte[]
+                {
+                    0x07, 0x00, 0x1f, 0x65, 0x20, 0x62, 0x09, 0x44, 0x7f, 0x0d, 0x0a, 0x36, 0x80, 0x53, 0x53, 0x48,
+                    0x2e, 0x4e, 0x45, 0x54, 0x20, 0x32, 0x30, 0x32, 0x30, 0x20, 0xf6, 0x7a, 0x32, 0x7f, 0x1f, 0x7e,
+                    0x78, 0x54, 0x00, 0x52
+                };
+            var expected = "  00000000  07 00 1F 65 20 62 09 44 7F 0D 0A 36 80 53 53 48  ...e b.D...6.SSH" +
+                           Environment.NewLine +
+                           "  00000010  2E 4E 45 54 20 32 30 32 30 20 F6 7A 32 7F 1F 7E  .NET 2020 .z2..~" +
+                           Environment.NewLine +
+                           "  00000020  78 54 00 52                                      xT.R";
+
+            var actual = PacketDump.Create(data, 2);
+
+            Assert.AreEqual(expected, actual);
+
+        }
+    }
+}

+ 12 - 13
src/Renci.SshNet.Tests/Classes/Common/PipeStream_Close_BlockingRead.cs

@@ -1,18 +1,18 @@
-using System;
+using System.Threading;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using Renci.SshNet.Common;
+using Renci.SshNet.Tests.Common;
 
 namespace Renci.SshNet.Tests.Classes.Common
 {
     [TestClass]
-    public class PipeStream_Close_BlockingRead
+    public class PipeStream_Close_BlockingRead : TripleATestBase
     {
         private PipeStream _pipeStream;
         private int _bytesRead;
-        private IAsyncResult _asyncReadResult;
+        private Thread _readThread;
 
-        [TestInitialize]
-        public void Init()
+        protected override void Arrange()
         {
             _pipeStream = new PipeStream();
 
@@ -22,26 +22,25 @@ namespace Renci.SshNet.Tests.Classes.Common
 
             _bytesRead = 123;
 
-            Action readAction = () => _bytesRead = _pipeStream.Read(new byte[4], 0, 4);
-            _asyncReadResult = readAction.BeginInvoke(null, null);
-            // ensure we've started reading
-            _asyncReadResult.AsyncWaitHandle.WaitOne(50);
+            _readThread = new Thread(() => _bytesRead = _pipeStream.Read(new byte[4], 0, 4));
+            _readThread.Start();
 
-            Act();
+            // ensure we've started reading
+            Assert.IsFalse(_readThread.Join(50));
         }
 
-        protected void Act()
+        protected override void Act()
         {
             _pipeStream.Close();
 
             // give async read time to complete
-            _asyncReadResult.AsyncWaitHandle.WaitOne(100);
+            _readThread.Join(100);
         }
 
         [TestMethod]
         public void BlockingReadShouldHaveBeenInterrupted()
         {
-            Assert.IsTrue(_asyncReadResult.IsCompleted);
+            Assert.AreEqual(ThreadState.Stopped, _readThread.ThreadState);
         }
 
         [TestMethod]

+ 14 - 14
src/Renci.SshNet.Tests/Classes/Common/PipeStream_Close_BlockingWrite.cs

@@ -1,22 +1,23 @@
 using System;
+using System.Threading;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using Renci.SshNet.Common;
+using Renci.SshNet.Tests.Common;
 
 namespace Renci.SshNet.Tests.Classes.Common
 {
     [TestClass]
-    public class PipeStream_Close_BlockingWrite
+    public class PipeStream_Close_BlockingWrite : TripleATestBase
     {
         private PipeStream _pipeStream;
         private Exception _writeException;
-        private IAsyncResult _asyncWriteResult;
+        private Thread _writehread;
 
-        [TestInitialize]
-        public void Init()
+        protected override void Arrange()
         {
             _pipeStream = new PipeStream {MaxBufferLength = 3};
 
-            Action writeAction = () =>
+            _writehread = new Thread(() =>
                 {
                     _pipeStream.WriteByte(10);
                     _pipeStream.WriteByte(13);
@@ -33,26 +34,25 @@ namespace Renci.SshNet.Tests.Classes.Common
                         _writeException = ex;
                         throw;
                     }
-                };
-            _asyncWriteResult = writeAction.BeginInvoke(null, null);
-            // ensure we've started writing
-            _asyncWriteResult.AsyncWaitHandle.WaitOne(50);
+                });
+            _writehread.Start();
 
-            Act();
+            // ensure we've started writing
+            Assert.IsFalse(_writehread.Join(50));
         }
 
-        protected void Act()
+        protected override void Act()
         {
             _pipeStream.Close();
 
-            // give async write time to complete
-            _asyncWriteResult.AsyncWaitHandle.WaitOne(100);
+            // give write time to complete
+            _writehread.Join(100);
         }
 
         [TestMethod]
         public void BlockingWriteShouldHaveBeenInterrupted()
         {
-            Assert.IsTrue(_asyncWriteResult.IsCompleted);
+            Assert.AreEqual(ThreadState.Stopped, _writehread.ThreadState);
         }
 
         [TestMethod]

+ 12 - 13
src/Renci.SshNet.Tests/Classes/Common/PipeStream_Flush_BytesRemainingAfterRead.cs

@@ -1,20 +1,19 @@
-using System;
-using System.Threading;
+using System.Threading;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using Renci.SshNet.Common;
+using Renci.SshNet.Tests.Common;
 
 namespace Renci.SshNet.Tests.Classes.Common
 {
     [TestClass]
-    public class PipeStream_Flush_BytesRemainingAfterRead
+    public class PipeStream_Flush_BytesRemainingAfterRead : TripleATestBase
     {
         private PipeStream _pipeStream;
         private byte[] _readBuffer;
         private int _bytesRead;
-        private IAsyncResult _asyncReadResult;
+        private Thread _readThread;
 
-        [TestInitialize]
-        public void Init()
+        protected override void Arrange()
         {
             _pipeStream = new PipeStream();
             _pipeStream.WriteByte(10);
@@ -27,25 +26,25 @@ namespace Renci.SshNet.Tests.Classes.Common
             _bytesRead = 0;
             _readBuffer = new byte[4];
 
-            Action readAction = () => _bytesRead = _pipeStream.Read(_readBuffer, 0, _readBuffer.Length);
-            _asyncReadResult = readAction.BeginInvoke(null, null);
-            _asyncReadResult.AsyncWaitHandle.WaitOne(50);
+            _readThread = new Thread(() => _bytesRead = _pipeStream.Read(_readBuffer, 0, _readBuffer.Length));
+            _readThread.Start();
 
-            Act();
+            // ensure we've started reading
+            _readThread.Join(50);
         }
 
-        protected void Act()
+        protected override void Act()
         {
             _pipeStream.Flush();
 
             // give async read time to complete
-            _asyncReadResult.AsyncWaitHandle.WaitOne(100);
+            _readThread.Join(100);
         }
 
         [TestMethod]
         public void AsyncReadShouldHaveFinished()
         {
-            Assert.IsTrue(_asyncReadResult.IsCompleted);
+            Assert.AreEqual(ThreadState.Stopped, _readThread.ThreadState);
         }
 
         [TestMethod]

+ 12 - 12
src/Renci.SshNet.Tests/Classes/Common/PipeStream_Flush_NoBytesRemainingAfterRead.cs

@@ -1,19 +1,19 @@
-using System;
+using System.Threading;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using Renci.SshNet.Common;
+using Renci.SshNet.Tests.Common;
 
 namespace Renci.SshNet.Tests.Classes.Common
 {
     [TestClass]
-    public class PipeStream_Flush_NoBytesRemainingAfterRead
+    public class PipeStream_Flush_NoBytesRemainingAfterRead : TripleATestBase
     {
         private PipeStream _pipeStream;
         private byte[] _readBuffer;
         private int _bytesRead;
-        private IAsyncResult _asyncReadResult;
+        private Thread _readThread;
 
-        [TestInitialize]
-        public void Init()
+        protected override void Arrange()
         {
             _pipeStream = new PipeStream();
             _pipeStream.WriteByte(10);
@@ -22,25 +22,25 @@ namespace Renci.SshNet.Tests.Classes.Common
             _bytesRead = 0;
             _readBuffer = new byte[4];
 
-            Action readAction = () => _bytesRead = _pipeStream.Read(_readBuffer, 0, _readBuffer.Length);
-            _asyncReadResult = readAction.BeginInvoke(null, null);
-            _asyncReadResult.AsyncWaitHandle.WaitOne(50);
+            _readThread = new Thread(() => _bytesRead = _pipeStream.Read(_readBuffer, 0, _readBuffer.Length));
+            _readThread.Start();
 
-            Act();
+            // ensure we've started reading
+            Assert.IsFalse(_readThread.Join(50));
         }
 
-        protected void Act()
+        protected override void Act()
         {
             _pipeStream.Flush();
 
             // give async read time to complete
-            _asyncReadResult.AsyncWaitHandle.WaitOne(100);
+            _readThread.Join(100);
         }
 
         [TestMethod]
         public void AsyncReadShouldHaveFinished()
         {
-            Assert.IsTrue(_asyncReadResult.IsCompleted);
+            Assert.AreEqual(ThreadState.Stopped, _readThread.ThreadState);
         }
 
         [TestMethod]

+ 44 - 0
src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTestBase.cs

@@ -0,0 +1,44 @@
+using Moq;
+using Renci.SshNet.Connection;
+using Renci.SshNet.Tests.Common;
+using System.Net;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    public abstract class DirectConnectorTestBase : TripleATestBase
+    {
+        internal Mock<ISocketFactory> SocketFactoryMock { get; private set; }
+        internal DirectConnector Connector { get; private set; }
+        internal SocketFactory SocketFactory { get; private set; }
+
+        protected virtual void CreateMocks()
+        {
+            SocketFactoryMock = new Mock<ISocketFactory>(MockBehavior.Strict);
+        }
+
+        protected virtual void SetupData()
+        {
+            Connector = new DirectConnector(SocketFactoryMock.Object);
+            SocketFactory = new SocketFactory();
+        }
+
+        protected virtual void SetupMocks()
+        {
+        }
+
+        protected sealed override void Arrange()
+        {
+            CreateMocks();
+            SetupData();
+            SetupMocks();
+        }
+
+        protected ConnectionInfo CreateConnectionInfo(string hostName)
+        {
+            return new ConnectionInfo(hostName,
+                                      777,
+                                      "user",
+                                      new KeyboardInteractiveAuthenticationMethod("user"));
+        }
+    }
+}

+ 103 - 0
src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_ConnectionRefusedByServer.cs

@@ -0,0 +1,103 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using System;
+using System.Diagnostics;
+using System.Net;
+using System.Net.Sockets;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class DirectConnectorTest_Connect_ConnectionRefusedByServer : DirectConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private SocketException _actualException;
+        private Socket _clientSocket;
+        private Stopwatch _stopWatch;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _connectionInfo = CreateConnectionInfo(IPAddress.Loopback.ToString());
+            _connectionInfo.Timeout = TimeSpan.FromMilliseconds(5000);
+            _stopWatch = new Stopwatch();
+            _actualException = null;
+
+            _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+        }
+
+        protected override void SetupMocks()
+        {
+            SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+                             .Returns(_clientSocket);
+        }
+
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_clientSocket != null)
+            {
+                _clientSocket.Dispose();
+            }
+        }
+
+        protected override void Act()
+        {
+            _stopWatch.Start();
+
+            try
+            {
+                Connector.Connect(_connectionInfo);
+                Assert.Fail();
+            }
+            catch (SocketException ex)
+            {
+                _actualException = ex;
+            }
+            finally
+            {
+                _stopWatch.Stop();
+            }
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveThrownSocketException()
+        {
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual(SocketError.ConnectionRefused, _actualException.SocketErrorCode);
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveRespectedTimeout()
+        {
+            var errorText = string.Format("Elapsed: {0}, Timeout: {1}",
+                                          _stopWatch.ElapsedMilliseconds,
+                                          _connectionInfo.Timeout.TotalMilliseconds);
+
+            Assert.IsTrue(_stopWatch.ElapsedMilliseconds < _connectionInfo.Timeout.TotalMilliseconds, errorText);
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldHaveBeenDisposed()
+        {
+            try
+            {
+                _clientSocket.Receive(new byte[0]);
+                Assert.Fail();
+            }
+            catch (ObjectDisposedException)
+            {
+            }
+        }
+
+        [TestMethod]
+        public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce()
+        {
+            SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),
+                                     Times.Once());
+        }
+    }
+}

+ 118 - 0
src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_ConnectionSucceeded.cs

@@ -0,0 +1,118 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Tests.Common;
+using System;
+using System.Diagnostics;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class DirectConnectorTest_Connect_ConnectionSucceeded : DirectConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private AsyncSocketListener _server;
+        private Socket _clientSocket;
+        private Stopwatch _stopWatch;
+        private bool _disconnected;
+        private Socket _actual;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            var random = new Random();
+
+            _connectionInfo = CreateConnectionInfo(IPAddress.Loopback.ToString());
+            _connectionInfo.Timeout = TimeSpan.FromMilliseconds(random.Next(50, 200));
+            _stopWatch = new Stopwatch();
+            _disconnected = false;
+
+            _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+
+            _server = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.Port));
+            _server.Disconnected += (socket) => _disconnected = true;
+            _server.Connected += (socket) => socket.Send(new byte[1] { 0x44 });
+            _server.Start();
+        }
+
+        protected override void SetupMocks()
+        {
+            SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+                             .Returns(_clientSocket);
+        }
+
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_server != null)
+            {
+                _server.Dispose();
+            }
+
+            if (_clientSocket != null)
+            {
+                _clientSocket.Dispose();
+            }
+        }
+
+        protected override void Act()
+        {
+            _stopWatch.Start();
+
+            try
+            {
+                _actual = Connector.Connect(_connectionInfo);
+            }
+            finally
+            {
+                _stopWatch.Stop();
+            }
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveReturnedSocketCreatedUsingSocketFactory()
+        {
+            Assert.IsNotNull(_actual);
+            Assert.AreSame(_clientSocket, _actual);
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveRespectedTimeout()
+        {
+            var errorText = string.Format("Elapsed: {0}, Timeout: {1}",
+                                          _stopWatch.ElapsedMilliseconds,
+                                          _connectionInfo.Timeout.TotalMilliseconds);
+
+            Assert.IsTrue(_stopWatch.ElapsedMilliseconds < (_connectionInfo.Timeout.TotalMilliseconds + 100), errorText);
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldBeConnected()
+        {
+            Assert.IsTrue(_clientSocket.Connected);
+            Assert.IsFalse(_disconnected);
+        }
+
+        [TestMethod]
+        public void NoBytesShouldHaveBeenReadFromSocket()
+        {
+            var buffer = new byte[1];
+
+            var bytesRead = _clientSocket.Receive(buffer);
+            Assert.AreEqual(1, bytesRead);
+            Assert.AreEqual(0x44, buffer[0]);
+        }
+
+        [TestMethod]
+        public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce()
+        {
+            SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),
+                                     Times.Once());
+        }
+    }
+}

+ 42 - 0
src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_HostNameInvalid.cs

@@ -0,0 +1,42 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using System.Net.Sockets;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class DirectConnectorTest_Connect_HostNameInvalid : DirectConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private SocketException _actualException;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _connectionInfo = CreateConnectionInfo("invalid.");
+            _actualException = null;
+        }
+
+        protected override void Act()
+        {
+            try
+            {
+                Connector.Connect(_connectionInfo);
+                Assert.Fail();
+            }
+            catch (SocketException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveThrownSocketException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual(SocketError.HostNotFound, _actualException.SocketErrorCode);
+        }
+    }
+}

+ 108 - 0
src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_TimeoutConnectingToServer.cs

@@ -0,0 +1,108 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Tests.Common;
+using System;
+using System.Diagnostics;
+using System.Globalization;
+using System.Net;
+using System.Net.Sockets;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class DirectConnectorTest_Connect_TimeoutConnectingToServer : DirectConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private SshOperationTimeoutException _actualException;
+        private Socket _clientSocket;
+        private Stopwatch _stopWatch;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            var random = new Random();
+
+            _connectionInfo = CreateConnectionInfo(IPAddress.Loopback.ToString());
+            _connectionInfo.Timeout = TimeSpan.FromMilliseconds(random.Next(50, 200));
+            _stopWatch = new Stopwatch();
+            _actualException = null;
+
+            _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+        }
+
+        protected override void SetupMocks()
+        {
+            SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+                             .Returns(_clientSocket);
+        }
+
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_clientSocket != null)
+            {
+                _clientSocket.Dispose();
+            }
+        }
+
+        protected override void Act()
+        {
+            _stopWatch.Start();
+
+            try
+            {
+                Connector.Connect(_connectionInfo);
+                Assert.Fail();
+            }
+            catch (SshOperationTimeoutException ex)
+            {
+                _actualException = ex;
+            }
+            finally
+            {
+                _stopWatch.Stop();
+            }
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveThrownSshOperationTimeoutException()
+        {
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual(string.Format(CultureInfo.InvariantCulture, "Connection failed to establish within {0} milliseconds.", _connectionInfo.Timeout.TotalMilliseconds), _actualException.Message);
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveRespectedTimeout()
+        {
+            var errorText = string.Format("Elapsed: {0}, Timeout: {1}",
+                                          _stopWatch.ElapsedMilliseconds,
+                                          _connectionInfo.Timeout.TotalMilliseconds);
+
+            Assert.IsTrue(_stopWatch.ElapsedMilliseconds >= _connectionInfo.Timeout.TotalMilliseconds, errorText);
+            Assert.IsTrue(_stopWatch.ElapsedMilliseconds < (_connectionInfo.Timeout.TotalMilliseconds + 100), errorText);
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldHaveBeenDisposed()
+        {
+            try
+            {
+                _clientSocket.Receive(new byte[0]);
+                Assert.Fail();
+            }
+            catch (ObjectDisposedException)
+            {
+            }
+        }
+
+        [TestMethod]
+        public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce()
+        {
+            SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),
+                                     Times.Once());
+        }
+    }
+}

+ 35 - 0
src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTestBase.cs

@@ -0,0 +1,35 @@
+using Moq;
+using Renci.SshNet.Connection;
+using Renci.SshNet.Tests.Common;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    public abstract class HttpConnectorTestBase : TripleATestBase
+    {
+        internal Mock<ISocketFactory> SocketFactoryMock { get; private set; }
+        internal HttpConnector Connector { get; private set; }
+        internal SocketFactory SocketFactory { get; private set; }
+
+        protected virtual void CreateMocks()
+        {
+            SocketFactoryMock = new Mock<ISocketFactory>(MockBehavior.Strict);
+        }
+
+        protected virtual void SetupData()
+        {
+            Connector = new HttpConnector(SocketFactoryMock.Object);
+            SocketFactory = new SocketFactory();
+        }
+
+        protected virtual void SetupMocks()
+        {
+        }
+
+        protected sealed override void Arrange()
+        {
+            CreateMocks();
+            SetupData();
+            SetupMocks();
+        }
+    }
+}

+ 111 - 0
src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ConnectionToProxyRefused.cs

@@ -0,0 +1,111 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using System;
+using System.Diagnostics;
+using System.Net;
+using System.Net.Sockets;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class HttpConnectorTest_Connect_ConnectionToProxyRefused : HttpConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private SocketException _actualException;
+        private Socket _clientSocket;
+        private Stopwatch _stopWatch;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _connectionInfo = new ConnectionInfo(IPAddress.Loopback.ToString(),
+                                                 777,
+                                                 "user",
+                                                 ProxyTypes.Http,
+                                                 IPAddress.Loopback.ToString(),
+                                                 8122,
+                                                 "proxyUser",
+                                                 "proxyPwd",
+                                                 new KeyboardInteractiveAuthenticationMethod("user"));
+            _connectionInfo.Timeout = TimeSpan.FromMilliseconds(5000);
+            _stopWatch = new Stopwatch();
+            _actualException = null;
+
+            _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+        }
+
+        protected override void SetupMocks()
+        {
+            SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+                             .Returns(_clientSocket);
+        }
+
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_clientSocket != null)
+            {
+                _clientSocket.Dispose();
+            }
+        }
+
+        protected override void Act()
+        {
+            _stopWatch.Start();
+
+            try
+            {
+                Connector.Connect(_connectionInfo);
+                Assert.Fail();
+            }
+            catch (SocketException ex)
+            {
+                _actualException = ex;
+            }
+            finally
+            {
+                _stopWatch.Stop();
+            }
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveThrownSocketException()
+        {
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual(SocketError.ConnectionRefused, _actualException.SocketErrorCode);
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveRespectedTimeout()
+        {
+            var errorText = string.Format("Elapsed: {0}, Timeout: {1}",
+                                          _stopWatch.ElapsedMilliseconds,
+                                          _connectionInfo.Timeout.TotalMilliseconds);
+
+            Assert.IsTrue(_stopWatch.ElapsedMilliseconds < _connectionInfo.Timeout.TotalMilliseconds, errorText);
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldHaveBeenDisposed()
+        {
+            try
+            {
+                _clientSocket.Receive(new byte[0]);
+                Assert.Fail();
+            }
+            catch (ObjectDisposedException)
+            {
+            }
+        }
+
+        [TestMethod]
+        public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce()
+        {
+            SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),
+                                     Times.Once());
+        }
+    }
+}

+ 115 - 0
src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyClosesConnectionBeforeStatusLineIsSent.cs

@@ -0,0 +1,115 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Tests.Common;
+using System;
+using System.Net;
+using System.Net.Sockets;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class HttpConnectorTest_Connect_ProxyClosesConnectionBeforeStatusLineIsSent : HttpConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private AsyncSocketListener _proxyServer;
+        private Socket _clientSocket;
+        private bool _disconnected;
+        private ProxyException _actualException;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _connectionInfo = new ConnectionInfo(IPAddress.Loopback.ToString(),
+                                                 777,
+                                                 "user",
+                                                 ProxyTypes.Http,
+                                                 IPAddress.Loopback.ToString(),
+                                                 8122,
+                                                 "proxyUser",
+                                                 "proxyPwd",
+                                                 new KeyboardInteractiveAuthenticationMethod("user"));
+            _connectionInfo.Timeout = TimeSpan.FromMilliseconds(100);
+            _actualException = null;
+
+            _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+
+            _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort));
+            _proxyServer.Disconnected += socket => _disconnected = true;
+            _proxyServer.BytesReceived += (bytesReceived, socket) =>
+                {
+                    socket.Shutdown(SocketShutdown.Send);
+                };
+            _proxyServer.Start();
+        }
+
+        protected override void SetupMocks()
+        {
+            SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+                             .Returns(_clientSocket);
+        }
+
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_proxyServer != null)
+            {
+                _proxyServer.Dispose();
+            }
+
+            if (_clientSocket != null)
+            {
+                _clientSocket.Dispose();
+            }
+        }
+
+        protected override void Act()
+        {
+            try
+            {
+                Connector.Connect(_connectionInfo);
+                Assert.Fail();
+            }
+            catch (ProxyException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveThrownProxyException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual("HTTP response does not contain status line.", _actualException.Message);
+        }
+
+        [TestMethod]
+        public void ConnectionToProxyShouldHaveBeenShutDown()
+        {
+            Assert.IsTrue(_disconnected);
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldHaveBeenDisposed()
+        {
+            try
+            {
+                _clientSocket.Receive(new byte[0]);
+                Assert.Fail();
+            }
+            catch (ObjectDisposedException)
+            {
+            }
+        }
+
+        [TestMethod]
+        public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce()
+        {
+            SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),
+                                     Times.Once());
+        }
+    }
+}

+ 49 - 0
src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyHostInvalid.cs

@@ -0,0 +1,49 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System.Net.Sockets;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class HttpConnectorTest_Connect_ProxyHostInvalid : HttpConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private SocketException _actualException;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _connectionInfo = new ConnectionInfo("localhost",
+                                                 40,
+                                                 "user",
+                                                 ProxyTypes.Http,
+                                                 "invalid.",
+                                                 80,
+                                                 "proxyUser",
+                                                 "proxyPwd",
+                                                 new KeyboardInteractiveAuthenticationMethod("user"));
+            _actualException = null;
+        }
+
+        protected override void Act()
+        {
+            try
+            {
+                Connector.Connect(_connectionInfo);
+                Assert.Fail();
+            }
+            catch (SocketException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveThrownSocketException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual(SocketError.HostNotFound, _actualException.SocketErrorCode);
+        }
+    }
+}

+ 131 - 0
src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyPasswordIsEmpty.cs

@@ -0,0 +1,131 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Tests.Common;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class HttpConnectorTest_Connect_ProxyPasswordIsEmpty : HttpConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private AsyncSocketListener _proxyServer;
+        private bool _disconnected;
+        private Socket _clientSocket;
+        private List<byte> _bytesReceivedByProxy;
+        private string _expectedHttpRequest;
+        private Socket _actual;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _connectionInfo = new ConnectionInfo(IPAddress.Loopback.ToString(),
+                                                 777,
+                                                 "user",
+                                                 ProxyTypes.Http,
+                                                 IPAddress.Loopback.ToString(),
+                                                 8122,
+                                                 "proxyUser",
+                                                 string.Empty,
+                                                 new KeyboardInteractiveAuthenticationMethod("user"));
+            _connectionInfo.Timeout = TimeSpan.FromMilliseconds(20);
+            _expectedHttpRequest = string.Format("CONNECT {0}:{1} HTTP/1.0{2}" +
+                                                 "Proxy-Authorization: Basic cHJveHlVc2VyOg=={2}{2}",
+                                                 _connectionInfo.Host,
+                                                 _connectionInfo.Port.ToString(CultureInfo.InvariantCulture),
+                                                 "\r\n");
+            _bytesReceivedByProxy = new List<byte>();
+            _disconnected = false;
+            _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+
+            _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort));
+            _proxyServer.Disconnected += (socket) => _disconnected = true;
+            _proxyServer.Connected += socket =>
+                {
+                        socket.Send(Encoding.ASCII.GetBytes("\r\n"));
+                        socket.Send(Encoding.ASCII.GetBytes("SSH.NET\r\n"));
+                        socket.Send(Encoding.ASCII.GetBytes("HTTP/1.0 200 OK\r\n"));
+                        socket.Send(Encoding.ASCII.GetBytes("Content-Type: application/octet-stream\r\n"));
+                        socket.Send(Encoding.ASCII.GetBytes("\r\n"));
+                        socket.Send(Encoding.ASCII.GetBytes("SSH4EVER"));
+                        socket.Shutdown(SocketShutdown.Send);
+                };
+            _proxyServer.BytesReceived += (bytesReceived, socket) =>
+                {
+                    _bytesReceivedByProxy.AddRange(bytesReceived);
+                };
+            _proxyServer.Start();
+        }
+
+        protected override void SetupMocks()
+        {
+            SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+                             .Returns(_clientSocket);
+        }
+
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_proxyServer != null)
+            {
+                _proxyServer.Dispose();
+            }
+
+            if (_clientSocket != null)
+            {
+                _clientSocket.Shutdown(SocketShutdown.Both);
+                _clientSocket.Close();
+            }
+        }
+
+        protected override void Act()
+        {
+            _actual = Connector.Connect(_connectionInfo);
+        }
+
+        [TestMethod]
+        public void ProxyShouldHaveReceivedExpectedHttpRequest()
+        {
+            Assert.AreEqual(_expectedHttpRequest, Encoding.ASCII.GetString(_bytesReceivedByProxy.ToArray()));
+        }
+
+        [TestMethod]
+        public void ConnectShouldReturnSocketCreatedUsingSocketFactory()
+        {
+            Assert.IsNotNull(_actual);
+            Assert.AreSame(_clientSocket, _actual);
+        }
+
+        [TestMethod]
+        public void OnlyHttpResponseShouldHaveBeenConsumed()
+        {
+            var buffer = new byte[8];
+
+            Assert.AreEqual(8, _actual.Available);
+            Assert.AreEqual(8, _actual.Receive(buffer));
+            Assert.AreEqual("SSH4EVER", Encoding.ASCII.GetString(buffer));
+            Assert.AreEqual(0, _actual.Receive(buffer));
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldBeConnected()
+        {
+            Assert.IsFalse(_disconnected);
+            Assert.IsTrue(_actual.Connected);
+        }
+
+        [TestMethod]
+        public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce()
+        {
+            SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),
+                                     Times.Once());
+        }
+    }
+}

+ 131 - 0
src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyPasswordIsNull.cs

@@ -0,0 +1,131 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Tests.Common;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class HttpConnectorTest_Connect_ProxyPasswordIsNull : HttpConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private AsyncSocketListener _proxyServer;
+        private bool _disconnected;
+        private Socket _clientSocket;
+        private List<byte> _bytesReceivedByProxy;
+        private string _expectedHttpRequest;
+        private Socket _actual;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _connectionInfo = new ConnectionInfo(IPAddress.Loopback.ToString(),
+                                                 777,
+                                                 "user",
+                                                 ProxyTypes.Http,
+                                                 IPAddress.Loopback.ToString(),
+                                                 8122,
+                                                 "proxyUser",
+                                                 null,
+                                                 new KeyboardInteractiveAuthenticationMethod("user"));
+            _connectionInfo.Timeout = TimeSpan.FromMilliseconds(20);
+            _expectedHttpRequest = string.Format("CONNECT {0}:{1} HTTP/1.0{2}" +
+                                                 "Proxy-Authorization: Basic cHJveHlVc2VyOg=={2}{2}",
+                                                 _connectionInfo.Host,
+                                                 _connectionInfo.Port.ToString(CultureInfo.InvariantCulture),
+                                                 "\r\n");
+            _bytesReceivedByProxy = new List<byte>();
+            _disconnected = false;
+            _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+
+            _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort));
+            _proxyServer.Disconnected += (socket) => _disconnected = true;
+            _proxyServer.Connected += socket =>
+            {
+                socket.Send(Encoding.ASCII.GetBytes("\r\n"));
+                socket.Send(Encoding.ASCII.GetBytes("SSH.NET\r\n"));
+                socket.Send(Encoding.ASCII.GetBytes("HTTP/1.0 200 OK\r\n"));
+                socket.Send(Encoding.ASCII.GetBytes("Content-Type: application/octet-stream\r\n"));
+                socket.Send(Encoding.ASCII.GetBytes("\r\n"));
+                socket.Send(Encoding.ASCII.GetBytes("SSH4EVER"));
+                socket.Shutdown(SocketShutdown.Send);
+            };
+            _proxyServer.BytesReceived += (bytesReceived, socket) =>
+            {
+                _bytesReceivedByProxy.AddRange(bytesReceived);
+            };
+            _proxyServer.Start();
+        }
+
+        protected override void SetupMocks()
+        {
+            SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+                             .Returns(_clientSocket);
+        }
+
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_proxyServer != null)
+            {
+                _proxyServer.Dispose();
+            }
+
+            if (_clientSocket != null)
+            {
+                _clientSocket.Shutdown(SocketShutdown.Both);
+                _clientSocket.Close();
+            }
+        }
+
+        protected override void Act()
+        {
+            _actual = Connector.Connect(_connectionInfo);
+        }
+
+        [TestMethod]
+        public void ProxyShouldHaveReceivedExpectedHttpRequest()
+        {
+            Assert.AreEqual(_expectedHttpRequest, Encoding.ASCII.GetString(_bytesReceivedByProxy.ToArray()));
+        }
+
+        [TestMethod]
+        public void ConnectShouldReturnSocketCreatedUsingSocketFactory()
+        {
+            Assert.IsNotNull(_actual);
+            Assert.AreSame(_clientSocket, _actual);
+        }
+
+        [TestMethod]
+        public void OnlyHttpResponseShouldHaveBeenConsumed()
+        {
+            var buffer = new byte[8];
+
+            Assert.AreEqual(8, _actual.Available);
+            Assert.AreEqual(8, _actual.Receive(buffer));
+            Assert.AreEqual("SSH4EVER", Encoding.ASCII.GetString(buffer));
+            Assert.AreEqual(0, _actual.Receive(buffer));
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldBeConnected()
+        {
+            Assert.IsFalse(_disconnected);
+            Assert.IsTrue(_actual.Connected);
+        }
+
+        [TestMethod]
+        public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce()
+        {
+            SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),
+                                     Times.Once());
+        }
+    }
+}

+ 120 - 0
src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseDoesNotContainHttpStatusLine.cs

@@ -0,0 +1,120 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Tests.Common;
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class HttpConnectorTest_Connect_ProxyResponseDoesNotContainHttpStatusLine : HttpConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private AsyncSocketListener _proxyServer;
+        private Socket _clientSocket;
+        private List<byte> _bytesReceivedByProxy;
+        private bool _disconnected;
+        private ProxyException _actualException;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _connectionInfo = new ConnectionInfo(IPAddress.Loopback.ToString(),
+                                                 777,
+                                                 "user",
+                                                 ProxyTypes.Http,
+                                                 IPAddress.Loopback.ToString(),
+                                                 8122,
+                                                 "proxyUser",
+                                                 "proxyPwd",
+                                                 new KeyboardInteractiveAuthenticationMethod("user"));
+            _connectionInfo.Timeout = TimeSpan.FromMilliseconds(100);
+            _bytesReceivedByProxy = new List<byte>();
+            _actualException = null;
+
+            _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+
+            _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort));
+            _proxyServer.Disconnected += socket => _disconnected = true;
+            _proxyServer.BytesReceived += (bytesReceived, socket) =>
+                {
+                    if (_bytesReceivedByProxy.Count == 0)
+                    {
+                        socket.Send(Encoding.ASCII.GetBytes("Whatever\r\n"));
+                        socket.Shutdown(SocketShutdown.Send);
+                    }
+
+                    _bytesReceivedByProxy.AddRange(bytesReceived);
+                };
+            _proxyServer.Start();
+        }
+
+        protected override void SetupMocks()
+        {
+            SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+                             .Returns(_clientSocket);
+        }
+
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_proxyServer != null)
+            {
+                _proxyServer.Dispose();
+            }
+        }
+
+        protected override void Act()
+        {
+            try
+            {
+                Connector.Connect(_connectionInfo);
+                Assert.Fail();
+            }
+            catch (ProxyException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveThrownProxyException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual("HTTP response does not contain status line.", _actualException.Message);
+        }
+
+        [TestMethod]
+        public void ConnectionToProxyShouldHaveBeenShutDown()
+        {
+            Assert.IsTrue(_disconnected);
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldHaveBeenDisposed()
+        {
+            try
+            {
+                _clientSocket.Receive(new byte[0]);
+                Assert.Fail();
+            }
+            catch (ObjectDisposedException)
+            {
+            }
+        }
+
+        [TestMethod]
+        public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce()
+        {
+            SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),
+                                     Times.Once());
+        }
+    }
+}

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

@@ -0,0 +1,135 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Tests.Common;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class HttpConnectorTest_Connect_ProxyResponseStatusIs200_ExtraTextBeforeStatusLine : HttpConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private AsyncSocketListener _proxyServer;
+        private bool _disconnected;
+        private Socket _clientSocket;
+        private List<byte> _bytesReceivedByProxy;
+        private string _expectedHttpRequest;
+        private Socket _actual;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _connectionInfo = new ConnectionInfo(IPAddress.Loopback.ToString(),
+                                                 777,
+                                                 "user",
+                                                 ProxyTypes.Http,
+                                                 IPAddress.Loopback.ToString(),
+                                                 8122,
+                                                 "proxyUser",
+                                                 "proxyPwd",
+                                                 new KeyboardInteractiveAuthenticationMethod("user"));
+            _connectionInfo.Timeout = TimeSpan.FromMilliseconds(20);
+            _expectedHttpRequest = string.Format("CONNECT {0}:{1} HTTP/1.0{2}" +
+                                                 "Proxy-Authorization: Basic cHJveHlVc2VyOnByb3h5UHdk{2}{2}",
+                                                 _connectionInfo.Host,
+                                                 _connectionInfo.Port.ToString(CultureInfo.InvariantCulture),
+                                                 "\r\n");
+            _bytesReceivedByProxy = new List<byte>();
+            _disconnected = false;
+            _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+
+            _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort));
+            _proxyServer.Disconnected += (socket) => _disconnected = true;
+            _proxyServer.BytesReceived += (bytesReceived, socket) =>
+            {
+                _bytesReceivedByProxy.AddRange(bytesReceived);
+
+                // Only send response back after we've received the complete CONNECT request
+                // as we want to make sure HttpConnector is not waiting for any data before
+                // it sends the CONNECT request
+                if (_bytesReceivedByProxy.Count == _expectedHttpRequest.Length)
+                {
+                    socket.Send(Encoding.ASCII.GetBytes("\r\n"));
+                    socket.Send(Encoding.ASCII.GetBytes("SSH.NET\r\n"));
+                    socket.Send(Encoding.ASCII.GetBytes("HTTP/1.0 200 OK\r\n"));
+                    socket.Send(Encoding.ASCII.GetBytes("Content-Type: application/octet-stream\r\n"));
+                    socket.Send(Encoding.ASCII.GetBytes("\r\n"));
+                    socket.Send(Encoding.ASCII.GetBytes("SSH4EVER"));
+                    socket.Shutdown(SocketShutdown.Send);
+                }
+            };
+            _proxyServer.Start();
+        }
+
+        protected override void SetupMocks()
+        {
+            SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+                             .Returns(_clientSocket);
+        }
+
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_proxyServer != null)
+            {
+                _proxyServer.Dispose();
+            }
+
+            if (_clientSocket != null)
+            {
+                _clientSocket.Shutdown(SocketShutdown.Both);
+                _clientSocket.Close();
+            }
+        }
+
+        protected override void Act()
+        {
+            _actual = Connector.Connect(_connectionInfo);
+        }
+
+        [TestMethod]
+        public void ProxyShouldHaveReceivedExpectedHttpRequest()
+        {
+            Assert.AreEqual(_expectedHttpRequest, Encoding.ASCII.GetString(_bytesReceivedByProxy.ToArray()));
+        }
+
+        [TestMethod]
+        public void ConnectShouldReturnSocketCreatedUsingSocketFactory()
+        {
+            Assert.IsNotNull(_actual);
+            Assert.AreSame(_clientSocket, _actual);
+        }
+
+        [TestMethod]
+        public void OnlyHttpResponseShouldHaveBeenConsumed()
+        {
+            var buffer = new byte[8];
+
+            Assert.AreEqual(8, _actual.Available);
+            Assert.AreEqual(8, _actual.Receive(buffer));
+            Assert.AreEqual("SSH4EVER", Encoding.ASCII.GetString(buffer));
+            Assert.AreEqual(0, _actual.Receive(buffer));
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldBeConnected()
+        {
+            Assert.IsFalse(_disconnected);
+            Assert.IsTrue(_actual.Connected);
+        }
+
+        [TestMethod]
+        public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce()
+        {
+            SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),
+                                     Times.Once());
+        }
+    }
+}

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

@@ -0,0 +1,135 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Tests.Common;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class HttpConnectorTest_Connect_ProxyResponseStatusIs200_HeadersAndContent : HttpConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private AsyncSocketListener _proxyServer;
+        private bool _disconnected;
+        private Socket _clientSocket;
+        private List<byte> _bytesReceivedByProxy;
+        private string _expectedHttpRequest;
+        private Socket _actual;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _connectionInfo = new ConnectionInfo(IPAddress.Loopback.ToString(),
+                                                 777,
+                                                 "user",
+                                                 ProxyTypes.Http,
+                                                 IPAddress.Loopback.ToString(),
+                                                 8122,
+                                                 "proxyUser",
+                                                 "proxyPwd",
+                                                 new KeyboardInteractiveAuthenticationMethod("user"));
+            _connectionInfo.Timeout = TimeSpan.FromMilliseconds(20);
+            _expectedHttpRequest = string.Format("CONNECT {0}:{1} HTTP/1.0{2}" +
+                                                 "Proxy-Authorization: Basic cHJveHlVc2VyOnByb3h5UHdk{2}{2}",
+                                                 _connectionInfo.Host,
+                                                 _connectionInfo.Port.ToString(CultureInfo.InvariantCulture),
+                                                 "\r\n");
+            _bytesReceivedByProxy = new List<byte>();
+            _disconnected = false;
+            _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+
+            _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort));
+            _proxyServer.Disconnected += (socket) => _disconnected = true;
+            _proxyServer.BytesReceived += (bytesReceived, socket) =>
+                {
+                    _bytesReceivedByProxy.AddRange(bytesReceived);
+
+                    // Only send response back after we've received the complete CONNECT request
+                    // as we want to make sure HttpConnector is not waiting for any data before
+                    // it sends the CONNECT request
+                    if (_bytesReceivedByProxy.Count == _expectedHttpRequest.Length)
+                    {
+                        socket.Send(Encoding.ASCII.GetBytes("HTTP/1.0 200 OK\r\n"));
+                        socket.Send(Encoding.ASCII.GetBytes("Content-Length: 10\r\n"));
+                        socket.Send(Encoding.ASCII.GetBytes("Content-Type: application/octet-stream\r\n"));
+                        socket.Send(Encoding.ASCII.GetBytes("\r\n"));
+                        socket.Send(Encoding.ASCII.GetBytes("TEEN_BYTES"));
+                        socket.Send(Encoding.ASCII.GetBytes("!666!"));
+                        socket.Shutdown(SocketShutdown.Send);
+                    }
+                };
+            _proxyServer.Start();
+        }
+
+        protected override void SetupMocks()
+        {
+            SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+                             .Returns(_clientSocket);
+        }
+
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_proxyServer != null)
+            {
+                _proxyServer.Dispose();
+            }
+
+            if (_clientSocket != null)
+            {
+                _clientSocket.Shutdown(SocketShutdown.Both);
+                _clientSocket.Close();
+            }
+        }
+
+        protected override void Act()
+        {
+            _actual = Connector.Connect(_connectionInfo);
+        }
+
+        [TestMethod]
+        public void ProxyShouldHaveReceivedExpectedHttpRequest()
+        {
+            Assert.AreEqual(_expectedHttpRequest, Encoding.ASCII.GetString(_bytesReceivedByProxy.ToArray()));
+        }
+
+        [TestMethod]
+        public void ConnectShouldReturnSocketCreatedUsingSocketFactory()
+        {
+            Assert.IsNotNull(_actual);
+            Assert.AreSame(_clientSocket, _actual);
+        }
+
+        [TestMethod]
+        public void OnlyHttpResponseShouldHaveBeenConsumed()
+        {
+            var buffer = new byte[5];
+
+            Assert.AreEqual(5, _actual.Available);
+            Assert.AreEqual(5, _actual.Receive(buffer));
+            Assert.AreEqual("!666!", Encoding.ASCII.GetString(buffer));
+            Assert.AreEqual(0, _actual.Receive(buffer));
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldBeConnected()
+        {
+            Assert.IsFalse(_disconnected);
+            Assert.IsTrue(_actual.Connected);
+        }
+
+        [TestMethod]
+        public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce()
+        {
+            SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),
+                                     Times.Once());
+        }
+    }
+}

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

@@ -0,0 +1,133 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Tests.Common;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class HttpConnectorTest_Connect_ProxyResponseStatusIs200_OnlyHeaders : HttpConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private AsyncSocketListener _proxyServer;
+        private bool _disconnected;
+        private Socket _clientSocket;
+        private List<byte> _bytesReceivedByProxy;
+        private string _expectedHttpRequest;
+        private Socket _actual;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _connectionInfo = new ConnectionInfo(IPAddress.Loopback.ToString(),
+                                                 777,
+                                                 "user",
+                                                 ProxyTypes.Http,
+                                                 IPAddress.Loopback.ToString(),
+                                                 8122,
+                                                 "proxyUser",
+                                                 "proxyPwd",
+                                                 new KeyboardInteractiveAuthenticationMethod("user"));
+            _connectionInfo.Timeout = TimeSpan.FromMilliseconds(20);
+            _expectedHttpRequest = string.Format("CONNECT {0}:{1} HTTP/1.0{2}" +
+                                                 "Proxy-Authorization: Basic cHJveHlVc2VyOnByb3h5UHdk{2}{2}",
+                                                 _connectionInfo.Host,
+                                                 _connectionInfo.Port.ToString(CultureInfo.InvariantCulture),
+                                                 "\r\n");
+            _bytesReceivedByProxy = new List<byte>();
+            _disconnected = false;
+            _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+
+            _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort));
+            _proxyServer.Disconnected += (socket) => _disconnected = true;
+            _proxyServer.BytesReceived += (bytesReceived, socket) =>
+                {
+                    _bytesReceivedByProxy.AddRange(bytesReceived);
+
+                    // Only send response back after we've received the complete CONNECT request
+                    // as we want to make sure HttpConnector is not waiting for any data before
+                    // it sends the CONNECT request
+                    if (_bytesReceivedByProxy.Count == _expectedHttpRequest.Length)
+                    {
+                        socket.Send(Encoding.ASCII.GetBytes("HTTP/1.0 200 OK\r\n"));
+                        socket.Send(Encoding.ASCII.GetBytes("Content-Type: application/octet-stream\r\n"));
+                        socket.Send(Encoding.ASCII.GetBytes("\r\n"));
+                        socket.Send(Encoding.ASCII.GetBytes("SSH4EVER"));
+                        socket.Shutdown(SocketShutdown.Send);
+                    }
+                };
+            _proxyServer.Start();
+        }
+
+        protected override void SetupMocks()
+        {
+            SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+                             .Returns(_clientSocket);
+        }
+
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_proxyServer != null)
+            {
+                _proxyServer.Dispose();
+            }
+
+            if (_clientSocket != null)
+            {
+                _clientSocket.Shutdown(SocketShutdown.Both);
+                _clientSocket.Close();
+            }
+        }
+
+        protected override void Act()
+        {
+            _actual = Connector.Connect(_connectionInfo);
+        }
+
+        [TestMethod]
+        public void ProxyShouldHaveReceivedExpectedHttpRequest()
+        {
+            Assert.AreEqual(_expectedHttpRequest, Encoding.ASCII.GetString(_bytesReceivedByProxy.ToArray()));
+        }
+
+        [TestMethod]
+        public void ConnectShouldReturnSocketCreatedUsingSocketFactory()
+        {
+            Assert.IsNotNull(_actual);
+            Assert.AreSame(_clientSocket, _actual);
+        }
+
+        [TestMethod]
+        public void OnlyHttpResponseShouldHaveBeenConsumed()
+        {
+            var buffer = new byte[8];
+
+            Assert.AreEqual(8, _actual.Available);
+            Assert.AreEqual(8, _actual.Receive(buffer));
+            Assert.AreEqual("SSH4EVER", Encoding.ASCII.GetString(buffer));
+            Assert.AreEqual(0, _actual.Receive(buffer));
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldBeConnected()
+        {
+            Assert.IsFalse(_disconnected);
+            Assert.IsTrue(_actual.Connected);
+        }
+
+        [TestMethod]
+        public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce()
+        {
+            SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),
+                                     Times.Once());
+        }
+    }
+}

+ 120 - 0
src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseStatusIsNot200.cs

@@ -0,0 +1,120 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Tests.Common;
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class HttpConnectorTest_Connect_ProxyResponseStatusIsNot200 : HttpConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private AsyncSocketListener _proxyServer;
+        private Socket _clientSocket;
+        private List<byte> _bytesReceivedByProxy;
+        private bool _disconnected;
+        private ProxyException _actualException;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _connectionInfo = new ConnectionInfo(IPAddress.Loopback.ToString(),
+                                                 777,
+                                                 "user",
+                                                 ProxyTypes.Http,
+                                                 IPAddress.Loopback.ToString(),
+                                                 8122,
+                                                 "proxyUser",
+                                                 "proxyPwd",
+                                                 new KeyboardInteractiveAuthenticationMethod("user"));
+            _connectionInfo.Timeout = TimeSpan.FromMilliseconds(100);
+            _bytesReceivedByProxy = new List<byte>();
+            _actualException = null;
+
+            _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+
+            _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort));
+            _proxyServer.Disconnected += (socket) => _disconnected = true;
+            _proxyServer.BytesReceived += (bytesReceived, socket) =>
+                {
+                    if (_bytesReceivedByProxy.Count == 0)
+                    {
+                        socket.Send(Encoding.ASCII.GetBytes("HTTP/1.0 404 I searched everywhere, really...\r\n"));
+                        socket.Shutdown(SocketShutdown.Send);
+                    }
+
+                    _bytesReceivedByProxy.AddRange(bytesReceived);
+                };
+            _proxyServer.Start();
+        }
+
+        protected override void SetupMocks()
+        {
+            SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+                             .Returns(_clientSocket);
+        }
+
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_proxyServer != null)
+            {
+                _proxyServer.Dispose();
+            }
+        }
+
+        protected override void Act()
+        {
+            try
+            {
+                Connector.Connect(_connectionInfo);
+                Assert.Fail();
+            }
+            catch (ProxyException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveThrownProxyException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual("HTTP: Status code 404, \"I searched everywhere, really...\"", _actualException.Message);
+        }
+
+        [TestMethod]
+        public void ConnectionToProxyShouldHaveBeenShutDown()
+        {
+            Assert.IsTrue(_disconnected);
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldHaveBeenDisposed()
+        {
+            try
+            {
+                _clientSocket.Receive(new byte[0]);
+                Assert.Fail();
+            }
+            catch (ObjectDisposedException)
+            {
+            }
+        }
+
+        [TestMethod]
+        public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce()
+        {
+            SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),
+                                     Times.Once());
+        }
+    }
+}

+ 131 - 0
src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyUserNameIsEmpty.cs

@@ -0,0 +1,131 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Tests.Common;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class HttpConnectorTest_Connect_ProxyUserNameIsEmpty : HttpConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private AsyncSocketListener _proxyServer;
+        private bool _disconnected;
+        private Socket _clientSocket;
+        private List<byte> _bytesReceivedByProxy;
+        private string _expectedHttpRequest;
+        private Socket _actual;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _connectionInfo = new ConnectionInfo(IPAddress.Loopback.ToString(),
+                                                 777,
+                                                 "user",
+                                                 ProxyTypes.Http,
+                                                 IPAddress.Loopback.ToString(),
+                                                 8122,
+                                                 string.Empty,
+                                                 "proxyPwd",
+                                                 new KeyboardInteractiveAuthenticationMethod("user"));
+            _connectionInfo.Timeout = TimeSpan.FromMilliseconds(20);
+            _expectedHttpRequest = string.Format("CONNECT {0}:{1} HTTP/1.0{2}{2}",
+                                                 _connectionInfo.Host,
+                                                 _connectionInfo.Port.ToString(CultureInfo.InvariantCulture),
+                                                 "\r\n");
+            _bytesReceivedByProxy = new List<byte>();
+            _disconnected = false;
+            _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+
+            _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort));
+            _proxyServer.Disconnected += (socket) => _disconnected = true;
+            _proxyServer.BytesReceived += (bytesReceived, socket) =>
+                {
+                    _bytesReceivedByProxy.AddRange(bytesReceived);
+
+                    // Only send response back after we've received the complete CONNECT request
+                    // as we want to make sure HttpConnector is not waiting for any data before
+                    // it sends the CONNECT request
+                    if (_bytesReceivedByProxy.Count == _expectedHttpRequest.Length)
+                    {
+                        socket.Send(Encoding.ASCII.GetBytes("\r\n"));
+                        socket.Send(Encoding.ASCII.GetBytes("SSH.NET\r\n"));
+                        socket.Send(Encoding.ASCII.GetBytes("HTTP/1.0 200 OK\r\n"));
+                        socket.Send(Encoding.ASCII.GetBytes("Content-Type: application/octet-stream\r\n"));
+                        socket.Send(Encoding.ASCII.GetBytes("\r\n"));
+                        socket.Send(Encoding.ASCII.GetBytes("SSH4EVER"));
+                    }
+                };
+            _proxyServer.Start();
+        }
+
+        protected override void SetupMocks()
+        {
+            SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+                             .Returns(_clientSocket);
+        }
+
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_proxyServer != null)
+            {
+                _proxyServer.Dispose();
+            }
+
+            if (_clientSocket != null)
+            {
+                _clientSocket.Close();
+            }
+        }
+
+        protected override void Act()
+        {
+            _actual = Connector.Connect(_connectionInfo);
+        }
+
+        [TestMethod]
+        public void ProxyShouldHaveReceivedExpectedHttpRequest()
+        {
+            Assert.AreEqual(_expectedHttpRequest, Encoding.ASCII.GetString(_bytesReceivedByProxy.ToArray()));
+        }
+
+        [TestMethod]
+        public void ConnectShouldReturnSocketCreatedUsingSocketFactory()
+        {
+            Assert.IsNotNull(_actual);
+            Assert.AreSame(_clientSocket, _actual);
+        }
+
+        [TestMethod]
+        public void OnlyHttpResponseShouldHaveBeenConsumed()
+        {
+            var buffer = new byte[8];
+
+            Assert.AreEqual(8, _actual.Available);
+            Assert.AreEqual(8, _actual.Receive(buffer));
+            Assert.AreEqual("SSH4EVER", Encoding.ASCII.GetString(buffer));
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldBeConnected()
+        {
+            Assert.IsFalse(_disconnected);
+            Assert.IsTrue(_actual.Connected);
+        }
+
+        [TestMethod]
+        public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce()
+        {
+            SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),
+                                     Times.Once());
+        }
+    }
+}

+ 128 - 0
src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyUserNameIsNotNullAndNotEmpty.cs

@@ -0,0 +1,128 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Tests.Common;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class HttpConnectorTest_Connect_ProxyUserNameIsNotNullAndNotEmpty : HttpConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private AsyncSocketListener _proxyServer;
+        private bool _disconnected;
+        private Socket _clientSocket;
+        private List<byte> _bytesReceivedByProxy;
+        private string _expectedHttpRequest;
+        private Socket _actual;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _connectionInfo = new ConnectionInfo(IPAddress.Loopback.ToString(),
+                                                 777,
+                                                 "user",
+                                                 ProxyTypes.Http,
+                                                 IPAddress.Loopback.ToString(),
+                                                 8122,
+                                                 "user",
+                                                 "pwd",
+                                                 new KeyboardInteractiveAuthenticationMethod("user"));
+            _connectionInfo.Timeout = TimeSpan.FromMilliseconds(20);
+            _expectedHttpRequest = string.Format("CONNECT {0}:{1} HTTP/1.0{2}" +
+                                                 "Proxy-Authorization: Basic dXNlcjpwd2Q={2}{2}",
+                                                 _connectionInfo.Host,
+                                                 _connectionInfo.Port.ToString(CultureInfo.InvariantCulture),
+                                                 "\r\n");
+            _bytesReceivedByProxy = new List<byte>();
+            _disconnected = false;
+            _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+
+            _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort));
+            _proxyServer.Connected += (socket) =>
+                {
+                    socket.Send(Encoding.ASCII.GetBytes("HTTP/1.0 200 OK\r\n"));
+                    socket.Send(Encoding.ASCII.GetBytes("Content-Type: application/octet-stream\r\n"));
+                    socket.Send(Encoding.ASCII.GetBytes("\r\n"));
+                    socket.Send(Encoding.ASCII.GetBytes("SSH4EVER"));
+                };
+            _proxyServer.Disconnected += (socket) => _disconnected = true;
+            _proxyServer.BytesReceived += (bytesReceived, socket) =>
+                {
+                    _bytesReceivedByProxy.AddRange(bytesReceived);
+                };
+            _proxyServer.Start();
+        }
+
+        protected override void SetupMocks()
+        {
+            SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+                             .Returns(_clientSocket);
+        }
+
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_proxyServer != null)
+            {
+                _proxyServer.Dispose();
+            }
+
+            if (_clientSocket != null)
+            {
+                _clientSocket.Shutdown(SocketShutdown.Both);
+                _clientSocket.Close();
+            }
+        }
+
+        protected override void Act()
+        {
+            _actual = Connector.Connect(_connectionInfo);
+        }
+
+        [TestMethod]
+        public void ProxyShouldHaveReceivedExpectedHttpRequest()
+        {
+            Assert.AreEqual(_expectedHttpRequest, Encoding.ASCII.GetString(_bytesReceivedByProxy.ToArray()));
+        }
+
+        [TestMethod]
+        public void ConnectShouldReturnSocketCreatedUsingSocketFactory()
+        {
+            Assert.IsNotNull(_actual);
+            Assert.AreSame(_clientSocket, _actual);
+        }
+
+        [TestMethod]
+        public void OnlyHttpResponseShouldHaveBeenConsumed()
+        {
+            var buffer = new byte[8];
+
+            Assert.AreEqual(8, _actual.Available);
+            Assert.AreEqual(8, _actual.Receive(buffer));
+            Assert.AreEqual("SSH4EVER", Encoding.ASCII.GetString(buffer));
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldBeConnected()
+        {
+            Assert.IsFalse(_disconnected);
+            Assert.IsTrue(_actual.Connected);
+        }
+
+        [TestMethod]
+        public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce()
+        {
+            SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),
+                                     Times.Once());
+        }
+    }
+}
+

+ 132 - 0
src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyUserNameIsNull.cs

@@ -0,0 +1,132 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Tests.Common;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class HttpConnectorTest_Connect_ProxyUserNameIsNull : HttpConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private AsyncSocketListener _proxyServer;
+        private bool _disconnected;
+        private Socket _clientSocket;
+        private List<byte> _bytesReceivedByProxy;
+        private string _expectedHttpRequest;
+        private Socket _actual;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _connectionInfo = new ConnectionInfo(IPAddress.Loopback.ToString(),
+                                                 777,
+                                                 "user",
+                                                 ProxyTypes.Http,
+                                                 IPAddress.Loopback.ToString(),
+                                                 8122,
+                                                 null,
+                                                 "proxyPwd",
+                                                 new KeyboardInteractiveAuthenticationMethod("user"));
+            _connectionInfo.Timeout = TimeSpan.FromMilliseconds(20);
+            _expectedHttpRequest = string.Format("CONNECT {0}:{1} HTTP/1.0{2}{2}",
+                                                 _connectionInfo.Host,
+                                                 _connectionInfo.Port.ToString(CultureInfo.InvariantCulture),
+                                                 "\r\n");
+            _bytesReceivedByProxy = new List<byte>();
+            _disconnected = false;
+            _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+
+            _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort));
+            _proxyServer.Disconnected += (socket) => _disconnected = true;
+            _proxyServer.BytesReceived += (bytesReceived, socket) =>
+            {
+                _bytesReceivedByProxy.AddRange(bytesReceived);
+
+                // Only send response back after we've received the complete CONNECT request
+                // as we want to make sure HttpConnector is not waiting for any data before
+                // it sends the CONNECT request
+                if (_bytesReceivedByProxy.Count == _expectedHttpRequest.Length)
+                {
+                    socket.Send(Encoding.ASCII.GetBytes("\r\n"));
+                    socket.Send(Encoding.ASCII.GetBytes("SSH.NET\r\n"));
+                    socket.Send(Encoding.ASCII.GetBytes("HTTP/1.0 200 OK\r\n"));
+                    socket.Send(Encoding.ASCII.GetBytes("Content-Type: application/octet-stream\r\n"));
+                    socket.Send(Encoding.ASCII.GetBytes("\r\n"));
+                    socket.Send(Encoding.ASCII.GetBytes("SSH4EVER"));
+                }
+            };
+            _proxyServer.Start();
+        }
+
+        protected override void SetupMocks()
+        {
+            SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+                             .Returns(_clientSocket);
+        }
+
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_clientSocket != null)
+            {
+                _clientSocket.Shutdown(SocketShutdown.Send);
+                _clientSocket.Close();
+            }
+
+            if (_proxyServer != null)
+            {
+                _proxyServer.Dispose();
+            }
+        }
+
+        protected override void Act()
+        {
+            _actual = Connector.Connect(_connectionInfo);
+        }
+
+        [TestMethod]
+        public void ProxyShouldHaveReceivedExpectedHttpRequest()
+        {
+            Assert.AreEqual(_expectedHttpRequest, Encoding.ASCII.GetString(_bytesReceivedByProxy.ToArray()));
+        }
+
+        [TestMethod]
+        public void ConnectShouldReturnSocketCreatedUsingSocketFactory()
+        {
+            Assert.IsNotNull(_actual);
+            Assert.AreSame(_clientSocket, _actual);
+        }
+
+        [TestMethod]
+        public void OnlyHttpResponseShouldHaveBeenConsumed()
+        {
+            var buffer = new byte[8];
+
+            Assert.AreEqual(8, _actual.Available);
+            Assert.AreEqual(8, _actual.Receive(buffer));
+            Assert.AreEqual("SSH4EVER", Encoding.ASCII.GetString(buffer));
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldBeConnected()
+        {
+            Assert.IsFalse(_disconnected);
+            Assert.IsTrue(_actual.Connected);
+        }
+
+        [TestMethod]
+        public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce()
+        {
+            SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),
+                                     Times.Once());
+        }
+    }
+}

+ 115 - 0
src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_TimeoutConnectingToProxy.cs

@@ -0,0 +1,115 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using System;
+using System.Diagnostics;
+using System.Globalization;
+using System.Net;
+using System.Net.Sockets;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class HttpConnectorTest_Connect_TimeoutConnectingToProxy : HttpConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private SshOperationTimeoutException _actualException;
+        private Socket _clientSocket;
+        private Stopwatch _stopWatch;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            var random = new Random();
+
+            _connectionInfo = new ConnectionInfo(IPAddress.Loopback.ToString(),
+                                                 777,
+                                                 "user",
+                                                 ProxyTypes.Http,
+                                                 IPAddress.Loopback.ToString(),
+                                                 8122,
+                                                 "proxyUser",
+                                                 "proxyPwd",
+                                                 new KeyboardInteractiveAuthenticationMethod("user"));
+            _connectionInfo.Timeout = TimeSpan.FromMilliseconds(random.Next(50, 200));
+            _stopWatch = new Stopwatch();
+            _actualException = null;
+
+            _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+        }
+
+        protected override void SetupMocks()
+        {
+            SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+                             .Returns(_clientSocket);
+        }
+
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_clientSocket != null)
+            {
+                _clientSocket.Dispose();
+            }
+        }
+
+        protected override void Act()
+        {
+            _stopWatch.Start();
+
+            try
+            {
+                Connector.Connect(_connectionInfo);
+                Assert.Fail();
+            }
+            catch (SshOperationTimeoutException ex)
+            {
+                _actualException = ex;
+            }
+            finally
+            {
+                _stopWatch.Stop();
+            }
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveThrownSshOperationTimeoutException()
+        {
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual(string.Format(CultureInfo.InvariantCulture, "Connection failed to establish within {0} milliseconds.", _connectionInfo.Timeout.TotalMilliseconds), _actualException.Message);
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveRespectedTimeout()
+        {
+            var errorText = string.Format("Elapsed: {0}, Timeout: {1}",
+                                          _stopWatch.ElapsedMilliseconds,
+                                          _connectionInfo.Timeout.TotalMilliseconds);
+
+            Assert.IsTrue(_stopWatch.ElapsedMilliseconds >= _connectionInfo.Timeout.TotalMilliseconds, errorText);
+            Assert.IsTrue(_stopWatch.ElapsedMilliseconds < (_connectionInfo.Timeout.TotalMilliseconds + 100), errorText);
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldHaveBeenDisposed()
+        {
+            try
+            {
+                _clientSocket.Receive(new byte[0]);
+                Assert.Fail();
+            }
+            catch (ObjectDisposedException)
+            {
+            }
+        }
+
+        [TestMethod]
+        public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce()
+        {
+            SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),
+                                     Times.Once());
+        }
+    }
+}

+ 169 - 0
src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_TimeoutReadingHttpContent.cs

@@ -0,0 +1,169 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Connection;
+using Renci.SshNet.Tests.Common;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class HttpConnectorTest_Connect_TimeoutReadingHttpContent : HttpConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private SshOperationTimeoutException _actualException;
+        private Socket _clientSocket;
+        private AsyncSocketListener _proxyServer;
+        private List<byte> _bytesReceivedByProxy;
+        private string _expectedHttpRequest;
+        private Stopwatch _stopWatch;
+        private AsyncSocketListener _server;
+        private bool _disconnected;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            var random = new Random();
+
+            _connectionInfo = new ConnectionInfo(IPAddress.Loopback.ToString(),
+                                                 777,
+                                                 "user",
+                                                 ProxyTypes.Http,
+                                                 IPAddress.Loopback.ToString(),
+                                                 8122,
+                                                 "proxyUser",
+                                                 "proxyPwd",
+                                                 new KeyboardInteractiveAuthenticationMethod("user"));
+            _connectionInfo.Timeout = TimeSpan.FromMilliseconds(random.Next(50, 200));
+            _expectedHttpRequest = string.Format("CONNECT {0}:{1} HTTP/1.0{2}" +
+                                                 "Proxy-Authorization: Basic cHJveHlVc2VyOnByb3h5UHdk{2}{2}",
+                                                 _connectionInfo.Host,
+                                                 _connectionInfo.Port.ToString(CultureInfo.InvariantCulture),
+                                                 "\r\n");
+            _bytesReceivedByProxy = new List<byte>();
+            _stopWatch = new Stopwatch();
+            _actualException = null;
+
+            _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+
+            _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort));
+            _proxyServer.Disconnected += (socket) => _disconnected = true;
+            _proxyServer.BytesReceived += (bytesReceived, socket) =>
+                {
+                    _bytesReceivedByProxy.AddRange(bytesReceived);
+
+                    // Force a timeout by sending less content than indicated by Content-Length header
+                    if (_bytesReceivedByProxy.Count == _expectedHttpRequest.Length)
+                    {
+                        socket.Send(Encoding.ASCII.GetBytes("HTTP/1.0 200 OK\r\n"));
+                        socket.Send(Encoding.ASCII.GetBytes("Content-Length: 10\r\n"));
+                        socket.Send(Encoding.ASCII.GetBytes("Content-Type: application/octet-stream\r\n"));
+                        socket.Send(Encoding.ASCII.GetBytes("\r\n"));
+                        socket.Send(Encoding.ASCII.GetBytes("TOO_FEW"));
+                    }
+                };
+            _proxyServer.Start();
+
+            _server = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.Port));
+            _server.Start();
+        }
+
+        protected override void SetupMocks()
+        {
+            SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+                             .Returns(_clientSocket);
+        }
+
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_server != null)
+            {
+                _server.Dispose();
+            }
+
+            if (_proxyServer != null)
+            {
+                _proxyServer.Dispose();
+            }
+        }
+
+        protected override void Act()
+        {
+            _stopWatch.Start();
+
+            try
+            {
+                Connector.Connect(_connectionInfo);
+                Assert.Fail();
+            }
+            catch (SshOperationTimeoutException ex)
+            {
+                _actualException = ex;
+            }
+            finally
+            {
+                _stopWatch.Stop();
+            }
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveThrownSshOperationTimeoutException()
+        {
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual(string.Format(CultureInfo.InvariantCulture, "Socket read operation has timed out after {0:F0} milliseconds.", _connectionInfo.Timeout.TotalMilliseconds), _actualException.Message);
+        }
+
+        [TestMethod]
+        public void ProxyShouldHaveReceivedExpectedHttpRequest()
+        {
+            Assert.AreEqual(_expectedHttpRequest, Encoding.ASCII.GetString(_bytesReceivedByProxy.ToArray()));
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveRespectedTimeout()
+        {
+            var errorText = string.Format("Elapsed: {0}, Timeout: {1}",
+                                          _stopWatch.ElapsedMilliseconds,
+                                          _connectionInfo.Timeout.TotalMilliseconds);
+
+            Assert.IsTrue(_stopWatch.ElapsedMilliseconds >= _connectionInfo.Timeout.TotalMilliseconds, errorText);
+            Assert.IsTrue(_stopWatch.ElapsedMilliseconds < (_connectionInfo.Timeout.TotalMilliseconds + 100), errorText);
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldNotBeConnected()
+        {
+            Assert.IsTrue(_disconnected);
+            Assert.IsFalse(_clientSocket.Connected);
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldHaveBeenDisposed()
+        {
+            try
+            {
+                _clientSocket.Receive(new byte[0]);
+                Assert.Fail();
+            }
+            catch (ObjectDisposedException)
+            {
+            }
+        }
+
+        [TestMethod]
+        public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce()
+        {
+            SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),
+                                     Times.Once());
+        }
+    }
+}

+ 139 - 0
src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_TimeoutReadingStatusLine.cs

@@ -0,0 +1,139 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Connection;
+using Renci.SshNet.Tests.Common;
+using System;
+using System.Diagnostics;
+using System.Globalization;
+using System.Net;
+using System.Net.Sockets;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class HttpConnectorTest_Connect_TimeoutReadingStatusLine : HttpConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private SshOperationTimeoutException _actualException;
+        private Socket _clientSocket;
+        private AsyncSocketListener _proxyServer;
+        private Stopwatch _stopWatch;
+        private AsyncSocketListener _server;
+        private bool _disconnected;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            var random = new Random();
+
+            _connectionInfo = new ConnectionInfo(IPAddress.Loopback.ToString(),
+                                                 777,
+                                                 "user",
+                                                 ProxyTypes.Http,
+                                                 IPAddress.Loopback.ToString(),
+                                                 8122,
+                                                 "proxyUser",
+                                                 "proxyPwd",
+                                                 new KeyboardInteractiveAuthenticationMethod("user"));
+            _connectionInfo.Timeout = TimeSpan.FromMilliseconds(random.Next(50, 200));
+            _stopWatch = new Stopwatch();
+            _actualException = null;
+
+            _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+
+            _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort));
+            _proxyServer.Disconnected += (socket) => _disconnected = true;
+            _proxyServer.Start();
+
+            _server = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.Port));
+            _server.Start();
+        }
+
+        protected override void SetupMocks()
+        {
+            SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+                             .Returns(_clientSocket);
+        }
+
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_server != null)
+            {
+                _server.Dispose();
+            }
+
+            if (_proxyServer != null)
+            {
+                _proxyServer.Dispose();
+            }
+        }
+
+        protected override void Act()
+        {
+            _stopWatch.Start();
+
+            try
+            {
+                Connector.Connect(_connectionInfo);
+                Assert.Fail();
+            }
+            catch (SshOperationTimeoutException ex)
+            {
+                _actualException = ex;
+            }
+            finally
+            {
+                _stopWatch.Stop();
+            }
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveThrownSshOperationTimeoutException()
+        {
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual(string.Format(CultureInfo.InvariantCulture, "Socket read operation has timed out after {0:F0} milliseconds.", _connectionInfo.Timeout.TotalMilliseconds), _actualException.Message);
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveRespectedTimeout()
+        {
+            var errorText = string.Format("Elapsed: {0}, Timeout: {1}",
+                                          _stopWatch.ElapsedMilliseconds,
+                                          _connectionInfo.Timeout.TotalMilliseconds);
+
+            Assert.IsTrue(_stopWatch.ElapsedMilliseconds >= _connectionInfo.Timeout.TotalMilliseconds, errorText);
+            Assert.IsTrue(_stopWatch.ElapsedMilliseconds < (_connectionInfo.Timeout.TotalMilliseconds + 100), errorText);
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldNotBeConnected()
+        {
+            Assert.IsTrue(_disconnected);
+            Assert.IsFalse(_clientSocket.Connected);
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldHaveBeenDisposed()
+        {
+            try
+            {
+                _clientSocket.Receive(new byte[0]);
+                Assert.Fail();
+            }
+            catch (ObjectDisposedException)
+            {
+            }
+        }
+
+        [TestMethod]
+        public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce()
+        {
+            SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),
+                                     Times.Once());
+        }
+    }
+}

+ 122 - 0
src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ConnectionClosedByServer_NoDataSentByServer.cs

@@ -0,0 +1,122 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Renci.SshNet.Common;
+using Renci.SshNet.Connection;
+using Renci.SshNet.Tests.Common;
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class ProtocolVersionExchangeTest_ConnectionClosedByServer_NoDataSentByServer
+    {
+        private AsyncSocketListener _server;
+        private ProtocolVersionExchange _protocolVersionExchange;
+        private string _clientVersion;
+        private TimeSpan _timeout;
+        private IPEndPoint _serverEndPoint;
+        private List<byte> _dataReceivedByServer;
+        private bool _clientDisconnected;
+        private Socket _client;
+        private SshConnectionException _actualException;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_server != null)
+            {
+                _server.Dispose();
+                _server = null;
+            }
+
+            if (_client != null)
+            {
+                _client.Shutdown(SocketShutdown.Both);
+                _client.Close();
+                _client = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            _clientVersion = "\uD55C";
+            _timeout = TimeSpan.FromSeconds(5);
+            _serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
+            _dataReceivedByServer = new List<byte>();
+
+            _server = new AsyncSocketListener(_serverEndPoint);
+            _server.Start();
+            _server.BytesReceived += (bytes, socket) =>
+                {
+                    _dataReceivedByServer.AddRange(bytes);
+                    socket.Shutdown(SocketShutdown.Send);
+                };
+            _server.Disconnected += (socket) => _clientDisconnected = true;
+
+            _client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+            _client.Connect(_serverEndPoint);
+
+            _protocolVersionExchange = new ProtocolVersionExchange();
+        }
+
+        protected void Act()
+        {
+            try
+            {
+                _protocolVersionExchange.Start(_clientVersion, _client, _timeout);
+                Assert.Fail();
+            }
+            catch (SshConnectionException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void StartShouldHaveThrownSshConnectionException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual(string.Format("The server response does not contain an SSH identification string.{0}" +
+                                          "The connection to the remote server was closed before any data was received.{0}{0}" +
+                                          "More information on the Protocol Version Exchange is available here:{0}" +
+                                          "https://tools.ietf.org/html/rfc4253#section-4.2",
+                                          Environment.NewLine),
+                            _actualException.Message);
+        }
+
+        [TestMethod]
+        public void ClientIdentificationWasSentToServer()
+        {
+            Assert.AreEqual(5, _dataReceivedByServer.Count);
+
+            Assert.AreEqual(0xed, _dataReceivedByServer[0]);
+            Assert.AreEqual(0x95, _dataReceivedByServer[1]);
+            Assert.AreEqual(0x9c, _dataReceivedByServer[2]);
+            Assert.AreEqual(0x0d, _dataReceivedByServer[3]);
+            Assert.AreEqual(0x0a, _dataReceivedByServer[4]);
+        }
+
+        [TestMethod]
+        public void ConnectionIsClosedByServer()
+        {
+            Assert.IsTrue(_client.Connected);
+            Assert.IsFalse(_clientDisconnected);
+
+            var bytesReceived = _client.Receive(new byte[1]);
+            Assert.AreEqual(0, bytesReceived);
+
+            Assert.IsTrue(_client.Connected);
+            Assert.IsFalse(_clientDisconnected);
+        }
+    }
+}

+ 132 - 0
src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseContainsNullCharacter.cs

@@ -0,0 +1,132 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Renci.SshNet.Common;
+using Renci.SshNet.Connection;
+using Renci.SshNet.Tests.Common;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class ProtocolVersionExchangeTest_ServerResponseContainsNullCharacter
+    {
+        private AsyncSocketListener _server;
+        private ProtocolVersionExchange _protocolVersionExchange;
+        private string _clientVersion;
+        private TimeSpan _timeout;
+        private IPEndPoint _serverEndPoint;
+        private List<byte> _dataReceivedByServer;
+        private byte[] _serverIdentification;
+        private bool _clientDisconnected;
+        private Socket _client;
+        private SshConnectionException _actualException;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_server != null)
+            {
+                _server.Dispose();
+                _server = null;
+            }
+
+            if (_client != null)
+            {
+                _client.Shutdown(SocketShutdown.Both);
+                _client.Close();
+                _client = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            _clientVersion = "SSH-2.0-Renci.SshNet.SshClient.0.0.1";
+            _timeout = TimeSpan.FromSeconds(5);
+            _serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
+            _dataReceivedByServer = new List<byte>();
+            _serverIdentification = Encoding.UTF8.GetBytes("\uD55C!\0\uD55CSSH -2.0-Renci.SshNet.SshClient.0.0.1");
+
+            _server = new AsyncSocketListener(_serverEndPoint);
+            _server.Start();
+            _server.Connected += socket => socket.Send(_serverIdentification);
+            _server.BytesReceived += (bytes, socket) =>
+                {
+                    _dataReceivedByServer.AddRange(bytes);
+                };
+            _server.Disconnected += (socket) => _clientDisconnected = true;
+
+            _client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+            _client.Connect(_serverEndPoint);
+
+            _protocolVersionExchange = new ProtocolVersionExchange();
+        }
+
+        protected void Act()
+        {
+            try
+            {
+                _protocolVersionExchange.Start(_clientVersion, _client, _timeout);
+                Assert.Fail();
+            }
+            catch (SshConnectionException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void StartShouldHaveThrownSshConnectionException()
+        {
+            var expectedMessage = string.Format("The server response contains a null character at position 0x00000005:{0}{0}" +
+                                                "  00000000  ED 95 9C 21 00                                   ...!.{0}{0}" +
+                                                "A server must not send a null character before the Protocol Version Exchange is complete.{0}{0}" +
+                                                "More information is available here:{0}" +
+                                                "https://tools.ietf.org/html/rfc4253#section-4.2",
+                                                Environment.NewLine);
+
+            Assert.IsNotNull(_actualException);
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual(expectedMessage, _actualException.Message);
+        }
+
+        [TestMethod]
+        public void ClientIdentificationWasSentToServer()
+        {
+            var expected = Encoding.UTF8.GetBytes(_clientVersion);
+
+            Assert.AreEqual(expected.Length + 2, _dataReceivedByServer.Count);
+
+            Assert.IsTrue(expected.SequenceEqual(_dataReceivedByServer.Take(expected.Length)));
+            Assert.AreEqual(Session.CarriageReturn, _dataReceivedByServer[_dataReceivedByServer.Count - 2]);
+            Assert.AreEqual(Session.LineFeed, _dataReceivedByServer[_dataReceivedByServer.Count - 1]);
+        }
+
+        [TestMethod]
+        public void ClientRemainsConnected()
+        {
+            Assert.IsTrue(_client.Connected);
+            Assert.IsFalse(_clientDisconnected);
+        }
+
+        [TestMethod]
+        public void ClientHasNotReadPastNullCharacter()
+        {
+            var buffer = new byte[1];
+
+            var bytesReceived = _client.Receive(buffer);
+            Assert.AreEqual(1, bytesReceived);
+            Assert.AreEqual(0xed, buffer[0]);
+        }
+    }
+}

+ 128 - 0
src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseInvalid_SshIdentificationOnlyContainsProtocolVersion.cs

@@ -0,0 +1,128 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Renci.SshNet.Common;
+using Renci.SshNet.Connection;
+using Renci.SshNet.Tests.Common;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class ProtocolVersionExchangeTest_ServerResponseInvalid_SshIdentificationOnlyContainsProtocolVersion
+    {
+        private AsyncSocketListener _server;
+        private ProtocolVersionExchange _protocolVersionExchange;
+        private string _clientVersion;
+        private TimeSpan _timeout;
+        private IPEndPoint _serverEndPoint;
+        private List<byte> _dataReceivedByServer;
+        private byte[] _serverIdentification;
+        private bool _clientDisconnected;
+        private Socket _client;
+        private SshConnectionException _actualException;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_server != null)
+            {
+                _server.Dispose();
+                _server = null;
+            }
+
+            if (_client != null)
+            {
+                _client.Shutdown(SocketShutdown.Both);
+                _client.Close();
+                _client = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            _clientVersion = "SSH-2.0-Renci.SshNet.SshClient.0.0.1";
+            _timeout = TimeSpan.FromSeconds(5);
+            _serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
+            _dataReceivedByServer = new List<byte>();
+            _serverIdentification = Encoding.UTF8.GetBytes("SSH-2.0\r\n");
+
+            _server = new AsyncSocketListener(_serverEndPoint);
+            _server.Start();
+            _server.BytesReceived += (bytes, socket) =>
+                {
+                    _dataReceivedByServer.AddRange(bytes);
+                    socket.Send(_serverIdentification);
+                    socket.Shutdown(SocketShutdown.Send);
+                };
+            _server.Disconnected += (socket) => _clientDisconnected = true;
+
+            _client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+            _client.Connect(_serverEndPoint);
+
+            _protocolVersionExchange = new ProtocolVersionExchange();
+        }
+
+        protected void Act()
+        {
+            try
+            {
+                _protocolVersionExchange.Start(_clientVersion, _client, _timeout);
+                Assert.Fail();
+            }
+            catch (SshConnectionException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void StartShouldHaveThrownSshConnectionException()
+        {
+            var expectedMessage = string.Format("The server response does not contain an SSH identification string:{0}{0}" +
+                                                "  00000000  53 53 48 2D 32 2E 30 0D 0A                       SSH-2.0..{0}{0}" +
+                                                "More information on the Protocol Version Exchange is available here:{0}" +
+                                                "https://tools.ietf.org/html/rfc4253#section-4.2",
+                                                Environment.NewLine);
+
+            Assert.IsNotNull(_actualException);
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual(expectedMessage, _actualException.Message);
+        }
+
+        [TestMethod]
+        public void ClientIdentificationWasSentToServer()
+        {
+            var expected = Encoding.UTF8.GetBytes(_clientVersion);
+
+            Assert.AreEqual(expected.Length + 2, _dataReceivedByServer.Count);
+
+            Assert.IsTrue(expected.SequenceEqual(_dataReceivedByServer.Take(expected.Length)));
+            Assert.AreEqual(Session.CarriageReturn, _dataReceivedByServer[_dataReceivedByServer.Count - 2]);
+            Assert.AreEqual(Session.LineFeed, _dataReceivedByServer[_dataReceivedByServer.Count - 1]);
+        }
+
+        [TestMethod]
+        public void ConnectionIsClosedByServer()
+        {
+            Assert.IsTrue(_client.Connected);
+            Assert.IsFalse(_clientDisconnected);
+
+            var bytesReceived = _client.Receive(new byte[1]);
+            Assert.AreEqual(0, bytesReceived);
+
+            Assert.IsTrue(_client.Connected);
+            Assert.IsFalse(_clientDisconnected);
+        }
+    }
+}

+ 119 - 0
src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseValid_Comments.cs

@@ -0,0 +1,119 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Renci.SshNet.Common;
+using Renci.SshNet.Connection;
+using Renci.SshNet.Tests.Common;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class ProtocolVersionExchangeTest_ServerResponseValid_Comments
+    {
+        private AsyncSocketListener _server;
+        private ProtocolVersionExchange _protocolVersionExchange;
+        private string _clientVersion;
+        private TimeSpan _timeout;
+        private IPEndPoint _serverEndPoint;
+        private List<byte> _dataReceivedByServer;
+        private byte[] _serverIdentification;
+        private bool _clientDisconnected;
+        private Socket _client;
+        private SshIdentification _actual;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_server != null)
+            {
+                _server.Dispose();
+                _server = null;
+            }
+
+            if (_client != null)
+            {
+                _client.Shutdown(SocketShutdown.Both);
+                _client.Close();
+                _client = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            _clientVersion = "SSH-2.0-Renci.SshNet.SshClient.0.0.1";
+            _timeout = TimeSpan.FromSeconds(5);
+            _serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
+            _dataReceivedByServer = new List<byte>();
+            _serverIdentification = Encoding.UTF8.GetBytes("\r\nWelcome stranger!\r\n\r\nSSH-ABC2.0-OurSSHAppliance-1.4.7 Use at own risk.\uD55C\r\n!");
+
+            _server = new AsyncSocketListener(_serverEndPoint);
+            _server.Start();
+            _server.BytesReceived += (bytes, socket) =>
+                {
+                    _dataReceivedByServer.AddRange(bytes);
+                    socket.Send(_serverIdentification);
+                    socket.Shutdown(SocketShutdown.Send);
+                };
+            _server.Disconnected += (socket) => _clientDisconnected = true;
+
+            _client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+            _client.Connect(_serverEndPoint);
+
+            _protocolVersionExchange = new ProtocolVersionExchange();
+        }
+
+        protected void Act()
+        {
+            _actual = _protocolVersionExchange.Start(_clientVersion, _client, _timeout);
+        }
+
+        [TestMethod]
+        public void StartShouldReturnIdentificationOfServer()
+        {
+            Assert.IsNotNull(_actual);
+            Assert.AreEqual("ABC2.0", _actual.ProtocolVersion);
+            Assert.AreEqual("OurSSHAppliance-1.4.7", _actual.SoftwareVersion);
+            Assert.AreEqual("Use at own risk.\uD55C", _actual.Comments);
+        }
+
+        [TestMethod]
+        public void ClientIdentificationWasSentToServer()
+        {
+            var expected = Encoding.UTF8.GetBytes(_clientVersion);
+
+            Assert.AreEqual(expected.Length + 2, _dataReceivedByServer.Count);
+
+            Assert.IsTrue(expected.SequenceEqual(_dataReceivedByServer.Take(expected.Length)));
+            Assert.AreEqual(Session.CarriageReturn, _dataReceivedByServer[_dataReceivedByServer.Count - 2]);
+            Assert.AreEqual(Session.LineFeed, _dataReceivedByServer[_dataReceivedByServer.Count - 1]);
+        }
+
+        [TestMethod]
+        public void ClientRemainsConnected()
+        {
+            Assert.IsTrue(_client.Connected);
+            Assert.IsFalse(_clientDisconnected);
+        }
+
+        [TestMethod]
+        public void ClientDidNotReadPastIdentification()
+        {
+            var buffer = new byte[1];
+
+            var bytesReceived = _client.Receive(buffer);
+            Assert.AreEqual(1, bytesReceived);
+            Assert.AreEqual(0x21, buffer[0]);
+        }
+    }
+}

+ 119 - 0
src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseValid_NoComments.cs

@@ -0,0 +1,119 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Renci.SshNet.Common;
+using Renci.SshNet.Connection;
+using Renci.SshNet.Tests.Common;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class ProtocolVersionExchangeTest_ServerResponseValid_NoComments
+    {
+        private AsyncSocketListener _server;
+        private ProtocolVersionExchange _protocolVersionExchange;
+        private string _clientVersion;
+        private TimeSpan _timeout;
+        private IPEndPoint _serverEndPoint;
+        private List<byte> _dataReceivedByServer;
+        private byte[] _serverIdentification;
+        private bool _clientDisconnected;
+        private Socket _client;
+        private SshIdentification _actual;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_server != null)
+            {
+                _server.Dispose();
+                _server = null;
+            }
+
+            if (_client != null)
+            {
+                _client.Shutdown(SocketShutdown.Both);
+                _client.Close();
+                _client = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            _clientVersion = "SSH-2.0-Renci.SshNet.SshClient.0.0.1";
+            _timeout = TimeSpan.FromSeconds(5);
+            _serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
+            _dataReceivedByServer = new List<byte>();
+            _serverIdentification = Encoding.UTF8.GetBytes("Welcome stranger!\r\n\r\nSSH-Zero-OurSSHAppliance\r\n!");
+
+            _server = new AsyncSocketListener(_serverEndPoint);
+            _server.Start();
+            _server.BytesReceived += (bytes, socket) =>
+                {
+                    _dataReceivedByServer.AddRange(bytes);
+                    socket.Send(_serverIdentification);
+                    socket.Shutdown(SocketShutdown.Send);
+                };
+            _server.Disconnected += (socket) => _clientDisconnected = true;
+
+            _client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+            _client.Connect(_serverEndPoint);
+
+            _protocolVersionExchange = new ProtocolVersionExchange();
+        }
+
+        protected void Act()
+        {
+            _actual = _protocolVersionExchange.Start(_clientVersion, _client, _timeout);
+        }
+
+        [TestMethod]
+        public void StartShouldReturnIdentificationOfServer()
+        {
+            Assert.IsNotNull(_actual);
+            Assert.AreEqual("Zero", _actual.ProtocolVersion);
+            Assert.AreEqual("OurSSHAppliance", _actual.SoftwareVersion);
+            Assert.IsNull(_actual.Comments);
+        }
+
+        [TestMethod]
+        public void ClientIdentificationWasSentToServer()
+        {
+            var expected = Encoding.UTF8.GetBytes(_clientVersion);
+
+            Assert.AreEqual(expected.Length + 2, _dataReceivedByServer.Count);
+
+            Assert.IsTrue(expected.SequenceEqual(_dataReceivedByServer.Take(expected.Length)));
+            Assert.AreEqual(Session.CarriageReturn, _dataReceivedByServer[_dataReceivedByServer.Count - 2]);
+            Assert.AreEqual(Session.LineFeed, _dataReceivedByServer[_dataReceivedByServer.Count - 1]);
+        }
+
+        [TestMethod]
+        public void ClientRemainsConnected()
+        {
+            Assert.IsTrue(_client.Connected);
+            Assert.IsFalse(_clientDisconnected);
+        }
+
+        [TestMethod]
+        public void ClientDidNotReadPastIdentification()
+        {
+            var buffer = new byte[1];
+
+            var bytesReceived = _client.Receive(buffer);
+            Assert.AreEqual(1, bytesReceived);
+            Assert.AreEqual(0x21, buffer[0]);
+        }
+    }
+}

+ 113 - 0
src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_TimeoutReadingIdentificationString.cs

@@ -0,0 +1,113 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Renci.SshNet.Common;
+using Renci.SshNet.Connection;
+using Renci.SshNet.Tests.Common;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class ProtocolVersionExchangeTest_TimeoutReadingIdentificationString
+    {
+        private AsyncSocketListener _server;
+        private ProtocolVersionExchange _protocolVersionExchange;
+        private string _clientVersion;
+        private TimeSpan _timeout;
+        private IPEndPoint _serverEndPoint;
+        private List<byte> _dataReceivedByServer;
+        private bool _clientDisconnected;
+        private Socket _client;
+        private SshOperationTimeoutException _actualException;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_server != null)
+            {
+                _server.Dispose();
+                _server = null;
+            }
+
+            if (_client != null)
+            {
+                _client.Close();
+                _client = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            _clientVersion = "SSH-2.0-Renci.SshNet.SshClient.0.0.1";
+            _timeout = TimeSpan.FromMilliseconds(200);
+            _serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
+            _dataReceivedByServer = new List<byte>();
+            _clientDisconnected = false;
+
+            _server = new AsyncSocketListener(_serverEndPoint);
+            _server.Start();
+            _server.BytesReceived += (bytes, socket) =>
+                {
+                    _dataReceivedByServer.AddRange(bytes);
+                    socket.Send(Encoding.UTF8.GetBytes("Welcome!\r\n"));
+                };
+            _server.Disconnected += (socket) => _clientDisconnected = true;
+
+            _client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+            _client.Connect(_serverEndPoint);
+
+            _protocolVersionExchange = new ProtocolVersionExchange();
+        }
+
+        protected void Act()
+        {
+            try
+            {
+                _protocolVersionExchange.Start(_clientVersion, _client, _timeout);
+                Assert.Fail();
+            }
+            catch (SshOperationTimeoutException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void StartShouldHaveThrownSshOperationTimeoutException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual(string.Format("Socket read operation has timed out after {0} milliseconds.", _timeout.TotalMilliseconds), _actualException.Message);
+        }
+
+        [TestMethod]
+        public void ClientIdentificationWasSentToServer()
+        {
+            var expected = Encoding.UTF8.GetBytes(_clientVersion);
+
+            Assert.AreEqual(expected.Length + 2, _dataReceivedByServer.Count);
+
+            Assert.IsTrue(expected.SequenceEqual(_dataReceivedByServer.Take(expected.Length)));
+            Assert.AreEqual(Session.CarriageReturn, _dataReceivedByServer[_dataReceivedByServer.Count - 2]);
+            Assert.AreEqual(Session.LineFeed, _dataReceivedByServer[_dataReceivedByServer.Count - 1]);
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldBeConnected()
+        {
+            Assert.IsTrue(_client.Connected);
+            Assert.IsFalse(_clientDisconnected);
+        }
+    }
+}

+ 49 - 0
src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTestBase.cs

@@ -0,0 +1,49 @@
+using Moq;
+using Renci.SshNet.Connection;
+using Renci.SshNet.Tests.Common;
+using System.Net;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    public abstract class Socks4ConnectorTestBase : TripleATestBase
+    {
+        internal Mock<ISocketFactory> SocketFactoryMock { get; private set; }
+        internal Socks4Connector Connector { get; private set; }
+        internal SocketFactory SocketFactory { get; private set; }
+
+        protected virtual void CreateMocks()
+        {
+            SocketFactoryMock = new Mock<ISocketFactory>(MockBehavior.Strict);
+        }
+
+        protected virtual void SetupData()
+        {
+            Connector = new Socks4Connector(SocketFactoryMock.Object);
+            SocketFactory = new SocketFactory();
+        }
+
+        protected virtual void SetupMocks()
+        {
+        }
+
+        protected sealed override void Arrange()
+        {
+            CreateMocks();
+            SetupData();
+            SetupMocks();
+        }
+
+        protected ConnectionInfo CreateConnectionInfo(string proxyUser, string proxyPassword)
+        {
+            return new ConnectionInfo(IPAddress.Loopback.ToString(),
+                                      777,
+                                      "user",
+                                      ProxyTypes.Socks4,
+                                      IPAddress.Loopback.ToString(),
+                                      8122,
+                                      proxyUser,
+                                      proxyPassword,
+                                      new KeyboardInteractiveAuthenticationMethod("user"));
+        }
+    }
+}

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

@@ -0,0 +1,134 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Tests.Common;
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class Socks4ConnectorTest_Connect_ConnectionRejectedByProxy : Socks4ConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private AsyncSocketListener _proxyServer;
+        private Socket _clientSocket;
+        private List<byte> _bytesReceivedByProxy;
+        private bool _disconnected;
+        private ProxyException _actualException;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _connectionInfo = CreateConnectionInfo("proxyUser", "proxyPwd");
+            _connectionInfo.Timeout = TimeSpan.FromMilliseconds(100);
+            _bytesReceivedByProxy = new List<byte>();
+            _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+            _actualException = null;
+
+            _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort));
+            _proxyServer.Disconnected += socket => _disconnected = true;
+            _proxyServer.BytesReceived += (bytesReceived, socket) =>
+                {
+                    if (_bytesReceivedByProxy.Count == 0)
+                    {
+                        socket.Send(new byte[]
+                            {
+                                // Reply version (null byte)
+                                0x00,
+                                // Connection refused
+                                 0x5b
+                            });
+                    }
+
+                    _bytesReceivedByProxy.AddRange(bytesReceived);
+                };
+            _proxyServer.Start();
+        }
+
+        protected override void SetupMocks()
+        {
+            SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+                             .Returns(_clientSocket);
+        }
+
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_proxyServer != null)
+            {
+                _proxyServer.Dispose();
+            }
+
+            if (_clientSocket != null)
+            {
+                _clientSocket.Dispose();
+            }
+        }
+
+        protected override void Act()
+        {
+            try
+            {
+                Connector.Connect(_connectionInfo);
+                Assert.Fail();
+            }
+            catch (ProxyException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveThrownProxyException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual("SOCKS4: Connection rejected.", _actualException.Message);
+        }
+
+        [TestMethod]
+        public void ConnectionToProxyShouldHaveBeenShutDown()
+        {
+            Assert.IsTrue(_disconnected);
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldHaveBeenDisposed()
+        {
+            try
+            {
+                _clientSocket.Receive(new byte[0]);
+                Assert.Fail();
+            }
+            catch (ObjectDisposedException)
+            {
+            }
+        }
+
+        [TestMethod]
+        public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce()
+        {
+            SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),
+                                     Times.Once());
+        }
+
+        private static byte GetNotSupportedSocksVersion()
+        {
+            var random = new Random();
+
+            while (true)
+            {
+                var socksVersion = random.Next(1, 255);
+                if (socksVersion != 4)
+                {
+                    return (byte) socksVersion;
+                }
+            }
+        }
+    }
+}

+ 166 - 0
src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_ConnectionSucceeded.cs

@@ -0,0 +1,166 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Connection;
+using Renci.SshNet.Tests.Common;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class Socks4ConnectorTest_Connect_ConnectionSucceeded : Socks4ConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private Socket _clientSocket;
+        private AsyncSocketListener _proxyServer;
+        private List<byte> _bytesReceivedByProxy;
+        private bool _disconnected;
+        private Socket _actual;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            var random = new Random();
+
+            _connectionInfo = CreateConnectionInfo("proxyUser", "proxyPwd");
+            _connectionInfo.Timeout = TimeSpan.FromMilliseconds(random.Next(50, 200));
+            _bytesReceivedByProxy = new List<byte>();
+            _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+            _actual = null;
+
+            _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort));
+            _proxyServer.Disconnected += socket => _disconnected = true;
+            _proxyServer.BytesReceived += (bytesReceived, socket) =>
+                {
+                    _bytesReceivedByProxy.AddRange(bytesReceived);
+
+                    if (_bytesReceivedByProxy.Count == bytesReceived.Length)
+                    {
+                        // Send SOCKS response
+                        socket.Send(new byte[]
+                            {
+                                // Reply version (null byte)
+                                0x00,
+                                // Request granted
+                                0x5a,
+                                // Destination address port
+                                0x01,
+                                0xf0,
+                                // Destination address IP
+                                0x01,
+                                0x02,
+                                0x03,
+                                0x04
+                            });
+
+                        // Send extra byte to allow us to verify that connector did not consume too much
+                        socket.Send(new byte[]
+                            {
+                                0xfe
+                            });
+                    }
+                };
+            _proxyServer.Start();
+        }
+
+        protected override void SetupMocks()
+        {
+            SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+                             .Returns(_clientSocket);
+        }
+
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_proxyServer != null)
+            {
+                _proxyServer.Dispose();
+            }
+
+            if (_clientSocket != null)
+            {
+                _clientSocket.Dispose();
+            }
+        }
+
+        protected override void Act()
+        {
+            _actual = Connector.Connect(_connectionInfo);
+        }
+
+        [TestMethod]
+        public void ProxyShouldHaveReceivedExpectedSocksRequest()
+        {
+            var expectedSocksRequest = new byte[]
+                {
+                    // SOCKS version
+                    0x04,
+                    // CONNECT request
+                    0x01,
+                    // Destination port
+                    0x03,
+                    0x09,
+                    // Destination address (IPv4)
+                    0x7f,
+                    0x00,
+                    0x00,
+                    0x01,
+                    // Proxy user
+                    0x70,
+                    0x72,
+                    0x6f,
+                    0x78,
+                    0x79,
+                    0x55,
+                    0x73,
+                    0x65,
+                    0x72,
+                    // Null terminator
+                    0x00
+                };
+
+            var errorText = string.Format("Expected:{0}{1}{0}but was:{0}{2}",
+                                          Environment.NewLine,
+                                          PacketDump.Create(expectedSocksRequest, 2),
+                                          PacketDump.Create(_bytesReceivedByProxy, 2));
+
+            Assert.IsTrue(expectedSocksRequest.SequenceEqual(_bytesReceivedByProxy), errorText);
+        }
+
+        [TestMethod]
+        public void ConnectShouldReturnSocketCreatedUsingSocketFactory()
+        {
+            Assert.IsNotNull(_actual);
+            Assert.AreSame(_clientSocket, _actual);
+        }
+
+        [TestMethod]
+        public void OnlySocksResponseShouldHaveBeenConsumed()
+        {
+            var buffer = new byte[2];
+
+            Assert.AreEqual(1, _actual.Receive(buffer));
+            Assert.AreEqual(0xfe, buffer[0]);
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldBeConnected()
+        {
+            Assert.IsFalse(_disconnected);
+            Assert.IsTrue(_actual.Connected);
+        }
+
+        [TestMethod]
+        public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce()
+        {
+            SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),
+                                     Times.Once());
+        }
+    }
+}

+ 102 - 0
src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_ConnectionToProxyRefused.cs

@@ -0,0 +1,102 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using System;
+using System.Diagnostics;
+using System.Net.Sockets;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class Socks4ConnectorTest_Connect_ConnectionToProxyRefused : Socks4ConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private SocketException _actualException;
+        private Socket _clientSocket;
+        private Stopwatch _stopWatch;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _connectionInfo = CreateConnectionInfo("proxyUser", "proxyPwd");
+            _connectionInfo.Timeout = TimeSpan.FromMilliseconds(5000);
+            _stopWatch = new Stopwatch();
+            _actualException = null;
+
+            _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+        }
+
+        protected override void SetupMocks()
+        {
+            SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+                             .Returns(_clientSocket);
+        }
+
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_clientSocket != null)
+            {
+                _clientSocket.Dispose();
+            }
+        }
+
+        protected override void Act()
+        {
+            _stopWatch.Start();
+
+            try
+            {
+                Connector.Connect(_connectionInfo);
+                Assert.Fail();
+            }
+            catch (SocketException ex)
+            {
+                _actualException = ex;
+            }
+            finally
+            {
+                _stopWatch.Stop();
+            }
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveThrownSocketException()
+        {
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual(SocketError.ConnectionRefused, _actualException.SocketErrorCode);
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveRespectedTimeout()
+        {
+            var errorText = string.Format("Elapsed: {0}, Timeout: {1}",
+                                          _stopWatch.ElapsedMilliseconds,
+                                          _connectionInfo.Timeout.TotalMilliseconds);
+
+            Assert.IsTrue(_stopWatch.ElapsedMilliseconds < _connectionInfo.Timeout.TotalMilliseconds, errorText);
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldHaveBeenDisposed()
+        {
+            try
+            {
+                _clientSocket.Receive(new byte[0]);
+                Assert.Fail();
+            }
+            catch (ObjectDisposedException)
+            {
+            }
+        }
+
+        [TestMethod]
+        public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce()
+        {
+            SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),
+                                     Times.Once());
+        }
+    }
+}

+ 105 - 0
src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutConnectingToProxy.cs

@@ -0,0 +1,105 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using System;
+using System.Diagnostics;
+using System.Globalization;
+using System.Net.Sockets;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class Socks4ConnectorTest_Connect_TimeoutConnectingToProxy : Socks4ConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private SshOperationTimeoutException _actualException;
+        private Socket _clientSocket;
+        private Stopwatch _stopWatch;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            var random = new Random();
+
+            _connectionInfo = CreateConnectionInfo("proxyUser", "proxyPwd");
+            _connectionInfo.Timeout = TimeSpan.FromMilliseconds(random.Next(50, 200));
+            _stopWatch = new Stopwatch();
+            _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+            _actualException = null;
+        }
+
+        protected override void SetupMocks()
+        {
+            SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+                             .Returns(_clientSocket);
+        }
+
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_clientSocket != null)
+            {
+                _clientSocket.Dispose();
+            }
+        }
+
+        protected override void Act()
+        {
+            _stopWatch.Start();
+
+            try
+            {
+                Connector.Connect(_connectionInfo);
+                Assert.Fail();
+            }
+            catch (SshOperationTimeoutException ex)
+            {
+                _actualException = ex;
+            }
+            finally
+            {
+                _stopWatch.Stop();
+            }
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveThrownSshOperationTimeoutException()
+        {
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual(string.Format(CultureInfo.InvariantCulture, "Connection failed to establish within {0} milliseconds.", _connectionInfo.Timeout.TotalMilliseconds), _actualException.Message);
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveRespectedTimeout()
+        {
+            var errorText = string.Format("Elapsed: {0}, Timeout: {1}",
+                                          _stopWatch.ElapsedMilliseconds,
+                                          _connectionInfo.Timeout.TotalMilliseconds);
+
+            Assert.IsTrue(_stopWatch.ElapsedMilliseconds >= _connectionInfo.Timeout.TotalMilliseconds, errorText);
+            Assert.IsTrue(_stopWatch.ElapsedMilliseconds < (_connectionInfo.Timeout.TotalMilliseconds + 100), errorText);
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldHaveBeenDisposed()
+        {
+            try
+            {
+                _clientSocket.Receive(new byte[0]);
+                Assert.Fail();
+            }
+            catch (ObjectDisposedException)
+            {
+            }
+        }
+
+        [TestMethod]
+        public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce()
+        {
+            SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),
+                                     Times.Once());
+        }
+    }
+}

+ 143 - 0
src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingDestinationAddress.cs

@@ -0,0 +1,143 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Connection;
+using Renci.SshNet.Tests.Common;
+using System;
+using System.Diagnostics;
+using System.Globalization;
+using System.Net;
+using System.Net.Sockets;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class Socks4ConnectorTest_Connect_TimeoutReadingDestinationAddress : Socks4ConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private SshOperationTimeoutException _actualException;
+        private Socket _clientSocket;
+        private AsyncSocketListener _proxyServer;
+        private Stopwatch _stopWatch;
+        private AsyncSocketListener _server;
+        private bool _disconnected;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            var random = new Random();
+
+            _connectionInfo = CreateConnectionInfo("proxyUser", "proxyPwd");
+            _connectionInfo.Timeout = TimeSpan.FromMilliseconds(random.Next(50, 200));
+            _stopWatch = new Stopwatch();
+            _actualException = null;
+
+            _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+
+            _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort));
+            _proxyServer.Disconnected += socket => _disconnected = true;
+            _proxyServer.Connected += socket =>
+            {
+                socket.Send(new byte[]
+                    {
+                            // Reply version (null byte)
+                            0x00,
+                            // Request granted
+                            0x5a,
+                            // Incomplete destination address
+                            0x01
+                    });
+            };
+            _proxyServer.Start();
+
+            _server = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.Port));
+            _server.Start();
+        }
+
+        protected override void SetupMocks()
+        {
+            SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+                             .Returns(_clientSocket);
+        }
+
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_server != null)
+            {
+                _server.Dispose();
+            }
+
+            if (_proxyServer != null)
+            {
+                _proxyServer.Dispose();
+            }
+        }
+
+        protected override void Act()
+        {
+            _stopWatch.Start();
+
+            try
+            {
+                Connector.Connect(_connectionInfo);
+                Assert.Fail();
+            }
+            catch (SshOperationTimeoutException ex)
+            {
+                _actualException = ex;
+            }
+            finally
+            {
+                _stopWatch.Stop();
+            }
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveThrownSshOperationTimeoutException()
+        {
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual(string.Format(CultureInfo.InvariantCulture, "Socket read operation has timed out after {0:F0} milliseconds.", _connectionInfo.Timeout.TotalMilliseconds), _actualException.Message);
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveRespectedTimeout()
+        {
+            var errorText = string.Format("Elapsed: {0}, Timeout: {1}",
+                                          _stopWatch.ElapsedMilliseconds,
+                                          _connectionInfo.Timeout.TotalMilliseconds);
+
+            Assert.IsTrue(_stopWatch.ElapsedMilliseconds >= _connectionInfo.Timeout.TotalMilliseconds, errorText);
+            Assert.IsTrue(_stopWatch.ElapsedMilliseconds < (_connectionInfo.Timeout.TotalMilliseconds + 100), errorText);
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldNotBeConnected()
+        {
+            Assert.IsTrue(_disconnected);
+            Assert.IsFalse(_clientSocket.Connected);
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldHaveBeenDisposed()
+        {
+            try
+            {
+                _clientSocket.Receive(new byte[0]);
+                Assert.Fail();
+            }
+            catch (ObjectDisposedException)
+            {
+            }
+        }
+
+        [TestMethod]
+        public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce()
+        {
+            SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),
+                                     Times.Once());
+        }
+    }
+}

+ 139 - 0
src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingReplyCode.cs

@@ -0,0 +1,139 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Connection;
+using Renci.SshNet.Tests.Common;
+using System;
+using System.Diagnostics;
+using System.Globalization;
+using System.Net;
+using System.Net.Sockets;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class Socks4ConnectorTest_Connect_TimeoutReadingReplyCode : Socks4ConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private SshOperationTimeoutException _actualException;
+        private Socket _clientSocket;
+        private AsyncSocketListener _proxyServer;
+        private Stopwatch _stopWatch;
+        private AsyncSocketListener _server;
+        private bool _disconnected;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            var random = new Random();
+
+            _connectionInfo = CreateConnectionInfo("proxyUser", "proxyPwd");
+            _connectionInfo.Timeout = TimeSpan.FromMilliseconds(random.Next(50, 200));
+            _stopWatch = new Stopwatch();
+            _actualException = null;
+
+            _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+
+            _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort));
+            _proxyServer.Disconnected += socket => _disconnected = true;
+            _proxyServer.Connected += socket =>
+                {
+                    socket.Send(new byte[]
+                        {
+                            // Reply version (null byte)
+                            0x00
+                        });
+                };
+            _proxyServer.Start();
+
+            _server = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.Port));
+            _server.Start();
+        }
+
+        protected override void SetupMocks()
+        {
+            SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+                             .Returns(_clientSocket);
+        }
+
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_server != null)
+            {
+                _server.Dispose();
+            }
+
+            if (_proxyServer != null)
+            {
+                _proxyServer.Dispose();
+            }
+        }
+
+        protected override void Act()
+        {
+            _stopWatch.Start();
+
+            try
+            {
+                Connector.Connect(_connectionInfo);
+                Assert.Fail();
+            }
+            catch (SshOperationTimeoutException ex)
+            {
+                _actualException = ex;
+            }
+            finally
+            {
+                _stopWatch.Stop();
+            }
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveThrownSshOperationTimeoutException()
+        {
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual(string.Format(CultureInfo.InvariantCulture, "Socket read operation has timed out after {0:F0} milliseconds.", _connectionInfo.Timeout.TotalMilliseconds), _actualException.Message);
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveRespectedTimeout()
+        {
+            var errorText = string.Format("Elapsed: {0}, Timeout: {1}",
+                                          _stopWatch.ElapsedMilliseconds,
+                                          _connectionInfo.Timeout.TotalMilliseconds);
+
+            Assert.IsTrue(_stopWatch.ElapsedMilliseconds >= _connectionInfo.Timeout.TotalMilliseconds, errorText);
+            Assert.IsTrue(_stopWatch.ElapsedMilliseconds < (_connectionInfo.Timeout.TotalMilliseconds + 100), errorText);
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldNotBeConnected()
+        {
+            Assert.IsTrue(_disconnected);
+            Assert.IsFalse(_clientSocket.Connected);
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldHaveBeenDisposed()
+        {
+            try
+            {
+                _clientSocket.Receive(new byte[0]);
+                Assert.Fail();
+            }
+            catch (ObjectDisposedException)
+            {
+            }
+        }
+
+        [TestMethod]
+        public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce()
+        {
+            SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),
+                                     Times.Once());
+        }
+    }
+}

+ 131 - 0
src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingReplyVersion.cs

@@ -0,0 +1,131 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Connection;
+using Renci.SshNet.Tests.Common;
+using System;
+using System.Diagnostics;
+using System.Globalization;
+using System.Net;
+using System.Net.Sockets;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class Socks4ConnectorTest_Connect_TimeoutReadingReplyVersion : Socks4ConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private SshOperationTimeoutException _actualException;
+        private Socket _clientSocket;
+        private AsyncSocketListener _proxyServer;
+        private Stopwatch _stopWatch;
+        private AsyncSocketListener _server;
+        private bool _disconnected;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            var random = new Random();
+
+            _connectionInfo = CreateConnectionInfo("proxyUser", "proxyPwd");
+            _connectionInfo.Timeout = TimeSpan.FromMilliseconds(random.Next(50, 200));
+            _stopWatch = new Stopwatch();
+            _actualException = null;
+
+            _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+
+            _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort));
+            _proxyServer.Disconnected += socket => _disconnected = true;
+            _proxyServer.Start();
+
+            _server = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.Port));
+            _server.Start();
+        }
+
+        protected override void SetupMocks()
+        {
+            SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+                             .Returns(_clientSocket);
+        }
+
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_server != null)
+            {
+                _server.Dispose();
+            }
+
+            if (_proxyServer != null)
+            {
+                _proxyServer.Dispose();
+            }
+        }
+
+        protected override void Act()
+        {
+            _stopWatch.Start();
+
+            try
+            {
+                Connector.Connect(_connectionInfo);
+                Assert.Fail();
+            }
+            catch (SshOperationTimeoutException ex)
+            {
+                _actualException = ex;
+            }
+            finally
+            {
+                _stopWatch.Stop();
+            }
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveThrownSshOperationTimeoutException()
+        {
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual(string.Format(CultureInfo.InvariantCulture, "Socket read operation has timed out after {0:F0} milliseconds.", _connectionInfo.Timeout.TotalMilliseconds), _actualException.Message);
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveRespectedTimeout()
+        {
+            var errorText = string.Format("Elapsed: {0}, Timeout: {1}",
+                                          _stopWatch.ElapsedMilliseconds,
+                                          _connectionInfo.Timeout.TotalMilliseconds);
+
+            Assert.IsTrue(_stopWatch.ElapsedMilliseconds >= _connectionInfo.Timeout.TotalMilliseconds, errorText);
+            Assert.IsTrue(_stopWatch.ElapsedMilliseconds < (_connectionInfo.Timeout.TotalMilliseconds + 100), errorText);
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldNotBeConnected()
+        {
+            Assert.IsTrue(_disconnected);
+            Assert.IsFalse(_clientSocket.Connected);
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldHaveBeenDisposed()
+        {
+            try
+            {
+                _clientSocket.Receive(new byte[0]);
+                Assert.Fail();
+            }
+            catch (ObjectDisposedException)
+            {
+            }
+        }
+
+        [TestMethod]
+        public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce()
+        {
+            SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),
+                                     Times.Once());
+        }
+    }
+}

+ 68 - 0
src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTestBase.cs

@@ -0,0 +1,68 @@
+using Moq;
+using Renci.SshNet.Connection;
+using Renci.SshNet.Tests.Common;
+using System;
+using System.Net;
+using System.Text;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    public abstract class Socks5ConnectorTestBase : TripleATestBase
+    {
+        internal Mock<ISocketFactory> SocketFactoryMock { get; private set; }
+        internal Socks5Connector Connector { get; private set; }
+        internal SocketFactory SocketFactory { get; private set; }
+
+        protected virtual void CreateMocks()
+        {
+            SocketFactoryMock = new Mock<ISocketFactory>(MockBehavior.Strict);
+        }
+
+        protected virtual void SetupData()
+        {
+            Connector = new Socks5Connector(SocketFactoryMock.Object);
+            SocketFactory = new SocketFactory();
+        }
+
+        protected virtual void SetupMocks()
+        {
+        }
+
+        protected sealed override void Arrange()
+        {
+            CreateMocks();
+            SetupData();
+            SetupMocks();
+        }
+
+        protected ConnectionInfo CreateConnectionInfo(string proxyUser, string proxyPassword)
+        {
+            return new ConnectionInfo(IPAddress.Loopback.ToString(),
+                                      777,
+                                      "user",
+                                      ProxyTypes.Socks5,
+                                      IPAddress.Loopback.ToString(),
+                                      8122,
+                                      proxyUser,
+                                      proxyPassword,
+                                      new KeyboardInteractiveAuthenticationMethod("user"));
+        }
+
+        protected static string GenerateRandomString(int minLength, int maxLength)    
+        {
+            var random = new Random();
+            var length = random.Next(minLength, maxLength);
+
+            var sb = new StringBuilder(length);
+            int offset = 'a';
+
+            for (var i = 0; i < length; i++)
+            {
+                var @char = (char) random.Next(offset, offset + 26);
+                sb.Append(@char);
+            }
+
+            return sb.ToString();
+        }
+    }
+}

+ 103 - 0
src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_ConnectionToProxyRefused.cs

@@ -0,0 +1,103 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using System;
+using System.Diagnostics;
+using System.Net;
+using System.Net.Sockets;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class Socks5ConnectorTest_Connect_ConnectionToProxyRefused : Socks5ConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private SocketException _actualException;
+        private Socket _clientSocket;
+        private Stopwatch _stopWatch;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _connectionInfo = CreateConnectionInfo("proxyUser", "proxyPwd");
+            _connectionInfo.Timeout = TimeSpan.FromMilliseconds(5000);
+            _stopWatch = new Stopwatch();
+            _actualException = null;
+
+            _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+        }
+
+        protected override void SetupMocks()
+        {
+            SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+                             .Returns(_clientSocket);
+        }
+
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_clientSocket != null)
+            {
+                _clientSocket.Dispose();
+            }
+        }
+
+        protected override void Act()
+        {
+            _stopWatch.Start();
+
+            try
+            {
+                Connector.Connect(_connectionInfo);
+                Assert.Fail();
+            }
+            catch (SocketException ex)
+            {
+                _actualException = ex;
+            }
+            finally
+            {
+                _stopWatch.Stop();
+            }
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveThrownSocketException()
+        {
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual(SocketError.ConnectionRefused, _actualException.SocketErrorCode);
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveRespectedTimeout()
+        {
+            var errorText = string.Format("Elapsed: {0}, Timeout: {1}",
+                                          _stopWatch.ElapsedMilliseconds,
+                                          _connectionInfo.Timeout.TotalMilliseconds);
+
+            Assert.IsTrue(_stopWatch.ElapsedMilliseconds < _connectionInfo.Timeout.TotalMilliseconds, errorText);
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldHaveBeenDisposed()
+        {
+            try
+            {
+                _clientSocket.Receive(new byte[0]);
+                Assert.Fail();
+            }
+            catch (ObjectDisposedException)
+            {
+            }
+        }
+
+        [TestMethod]
+        public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce()
+        {
+            SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),
+                                     Times.Once());
+        }
+    }
+}

+ 206 - 0
src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_NoAuthentication_ConnectionSucceeded.cs

@@ -0,0 +1,206 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Tests.Common;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class Socks5Connector_Connect_NoAuthentication_Succeed : Socks5ConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private AsyncSocketListener _proxyServer;
+        private Socket _clientSocket;
+        private List<byte> _bytesReceivedByProxy;
+        private Socket _actual;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _connectionInfo = CreateConnectionInfo(new string('a', 255), new string('b', 255));
+            _connectionInfo.Timeout = TimeSpan.FromMilliseconds(100);
+            _bytesReceivedByProxy = new List<byte>();
+
+            _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+
+            _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort));
+            _proxyServer.BytesReceived += (bytesReceived, socket) =>
+            {
+                _bytesReceivedByProxy.AddRange(bytesReceived);
+
+                if (_bytesReceivedByProxy.Count == 4)
+                {
+                    // We received the greeting
+
+                    socket.Send(new byte[]
+                        {
+                                    // SOCKS version
+                                    0x05,
+                                    // Require no authentication
+                                    0x00
+                        });
+                }
+                else if (_bytesReceivedByProxy.Count == 4 + (1 + 1 + 1 + 1 + 4 + 2))
+                {
+                    // We received the connection request
+
+                    socket.Send(new byte[]
+                        {
+                                    // SOCKS version
+                                    0x05,
+                                    // Connection successful
+                                    0x00,
+                                    // Reserved byte
+                                    0x00,
+                        });
+
+                    // Send server bound address
+                    socket.Send(new byte[]
+                        {
+                                    // IPv6
+                                    0x04,
+                                    // IP address
+                                    0x01,
+                                    0x02,
+                                    0x12,
+                                    0x41,
+                                    0x31,
+                                    0x02,
+                                    0x42,
+                                    0x41,
+                                    0x71,
+                                    0x02,
+                                    0x32,
+                                    0x81,
+                                    0x01,
+                                    0x52,
+                                    0x12,
+                                    0x91,
+                                    // Port
+                                    0x0f,
+                                    0x1b,
+                        });
+
+                    // Send extra byte to allow us to verify that connector did not consume too much
+                    socket.Send(new byte[]
+                        {
+                                0xff
+                        });
+                }
+            };
+            _proxyServer.Start();
+        }
+
+        protected override void SetupMocks()
+        {
+            SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+                             .Returns(_clientSocket);
+        }
+
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_proxyServer != null)
+            {
+                _proxyServer.Dispose();
+            }
+
+            if (_clientSocket != null)
+            {
+                _clientSocket.Shutdown(SocketShutdown.Both);
+                _clientSocket.Dispose();
+            }
+        }
+
+        protected override void Act()
+        {
+            _actual = Connector.Connect(_connectionInfo);
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveReturnedSocketCreatedUsingSocketFactory()
+        {
+            Assert.IsNotNull(_actual);
+            Assert.AreSame(_clientSocket, _actual);
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldBeConnected()
+        {
+            Assert.IsTrue(_clientSocket.Connected);
+        }
+
+        [TestMethod]
+        public void ProxyShouldHaveReceivedExpectedSocksRequest()
+        {
+            var expectedSocksRequest = new byte[]
+                {
+                    //
+                    // Client greeting
+                    //
+
+                    // SOCKS version
+                    0x05,
+                    // Number of authentication methods supported
+                    0x02,
+                    // No authentication
+                    0x00,
+                    // Username/password
+                    0x02,
+
+                    //
+                    // Client connection request
+                    //
+
+                    // SOCKS version
+                    0x05,
+                    // Establish a TCP/IP stream connection
+                    0x01,
+                    // Reserved
+                    0x00,
+                    // Destination address type (IPv4)
+                    0x01,
+                    // Destination address (IPv4)
+                    0x7f,
+                    0x00,
+                    0x00,
+                    0x01,
+                    // Destination port
+                    0x03,
+                    0x09
+
+                };
+
+            var errorText = string.Format("Expected:{0}{1}{0}but was:{0}{2}",
+                                          Environment.NewLine,
+                                          PacketDump.Create(expectedSocksRequest, 2),
+                                          PacketDump.Create(_bytesReceivedByProxy, 2));
+
+            Assert.IsTrue(expectedSocksRequest.SequenceEqual(_bytesReceivedByProxy), errorText);
+        }
+
+        [TestMethod]
+        public void OnlySocksResponseShouldHaveBeenConsumed()
+        {
+            var buffer = new byte[1];
+
+            var bytesRead = _clientSocket.Receive(buffer);
+            Assert.AreEqual(1, bytesRead);
+            Assert.AreEqual(0xff, buffer[0]);
+        }
+
+        [TestMethod]
+        public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce()
+        {
+            SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),
+                                     Times.Once());
+        }
+    }
+}

+ 123 - 0
src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_ProxySocksVersionIsNotSupported.cs

@@ -0,0 +1,123 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Tests.Common;
+using System;
+using System.Net;
+using System.Net.Sockets;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class Socks5ConnectorTest_Connect_ProxySocksVersionIsNotSupported : Socks5ConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private AsyncSocketListener _proxyServer;
+        private Socket _clientSocket;
+        private byte _proxySocksVersion;
+        private bool _disconnected;
+        private ProxyException _actualException;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _connectionInfo = CreateConnectionInfo("proxyUser", "proxyPwd");
+            _connectionInfo.Timeout = TimeSpan.FromMilliseconds(100);
+            _proxySocksVersion = GetNotSupportedSocksVersion();
+            _actualException = null;
+
+            _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+
+            _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort));
+            _proxyServer.Disconnected += socket => _disconnected = true;
+            _proxyServer.BytesReceived += (bytesReceived, socket) =>
+                {
+                    socket.Send(new byte[] { _proxySocksVersion });
+                };
+            _proxyServer.Start();
+        }
+
+        protected override void SetupMocks()
+        {
+            SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+                             .Returns(_clientSocket);
+        }
+
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_proxyServer != null)
+            {
+                _proxyServer.Dispose();
+            }
+
+            if (_clientSocket != null)
+            {
+                _clientSocket.Dispose();
+            }
+        }
+
+        protected override void Act()
+        {
+            try
+            {
+                Connector.Connect(_connectionInfo);
+                Assert.Fail();
+            }
+            catch (ProxyException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveThrownProxyException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual(string.Format("SOCKS Version '{0}' is not supported.", _proxySocksVersion), _actualException.Message);
+        }
+
+        [TestMethod]
+        public void ConnectionToProxyShouldHaveBeenShutDown()
+        {
+            Assert.IsTrue(_disconnected);
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldHaveBeenDisposed()
+        {
+            try
+            {
+                _clientSocket.Receive(new byte[0]);
+                Assert.Fail();
+            }
+            catch (ObjectDisposedException)
+            {
+            }
+        }
+
+        [TestMethod]
+        public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce()
+        {
+            SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),
+                                     Times.Once());
+        }
+
+        private static byte GetNotSupportedSocksVersion()
+        {
+            var random = new Random();
+
+            while (true)
+            {
+                var socksVersion = random.Next(1, 255);
+                if (socksVersion != 5)
+                {
+                    return (byte) socksVersion;
+                }
+            }
+        }
+    }
+}

+ 107 - 0
src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_TimeoutConnectingToProxy.cs

@@ -0,0 +1,107 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using System;
+using System.Diagnostics;
+using System.Globalization;
+using System.Net;
+using System.Net.Sockets;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class Socks5ConnectorTest_Connect_TimeoutConnectingToProxy : Socks5ConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private SshOperationTimeoutException _actualException;
+        private Socket _clientSocket;
+        private Stopwatch _stopWatch;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            var random = new Random();
+
+            _connectionInfo = CreateConnectionInfo("proxyUser", "proxyPwd");
+            _connectionInfo.Timeout = TimeSpan.FromMilliseconds(random.Next(50, 200));
+            _stopWatch = new Stopwatch();
+            _actualException = null;
+
+            _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+        }
+
+        protected override void SetupMocks()
+        {
+            SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+                             .Returns(_clientSocket);
+        }
+
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_clientSocket != null)
+            {
+                _clientSocket.Dispose();
+            }
+        }
+
+        protected override void Act()
+        {
+            _stopWatch.Start();
+
+            try
+            {
+                Connector.Connect(_connectionInfo);
+                Assert.Fail();
+            }
+            catch (SshOperationTimeoutException ex)
+            {
+                _actualException = ex;
+            }
+            finally
+            {
+                _stopWatch.Stop();
+            }
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveThrownSshOperationTimeoutException()
+        {
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual(string.Format(CultureInfo.InvariantCulture, "Connection failed to establish within {0} milliseconds.", _connectionInfo.Timeout.TotalMilliseconds), _actualException.Message);
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveRespectedTimeout()
+        {
+            var errorText = string.Format("Elapsed: {0}, Timeout: {1}",
+                                          _stopWatch.ElapsedMilliseconds,
+                                          _connectionInfo.Timeout.TotalMilliseconds);
+
+            Assert.IsTrue(_stopWatch.ElapsedMilliseconds >= _connectionInfo.Timeout.TotalMilliseconds, errorText);
+            Assert.IsTrue(_stopWatch.ElapsedMilliseconds < (_connectionInfo.Timeout.TotalMilliseconds + 100), errorText);
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldHaveBeenDisposed()
+        {
+            try
+            {
+                _clientSocket.Receive(new byte[0]);
+                Assert.Fail();
+            }
+            catch (ObjectDisposedException)
+            {
+            }
+        }
+
+        [TestMethod]
+        public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce()
+        {
+            SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),
+                                     Times.Once());
+        }
+    }
+}

+ 177 - 0
src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_AuthenticationFailed.cs

@@ -0,0 +1,177 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Tests.Common;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_AuthenticationFailed : Socks5ConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private AsyncSocketListener _proxyServer;
+        private Socket _clientSocket;
+        private List<byte> _bytesReceivedByProxy;
+        private bool _disconnected;
+        private ProxyException _actualException;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _connectionInfo = CreateConnectionInfo("aa", "bbbb");
+            _connectionInfo.Timeout = TimeSpan.FromMilliseconds(100);
+            _bytesReceivedByProxy = new List<byte>();
+
+            _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+
+            _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort));
+            _proxyServer.Disconnected += socket => _disconnected = true;
+            _proxyServer.BytesReceived += (bytesReceived, socket) =>
+            {
+                _bytesReceivedByProxy.AddRange(bytesReceived);
+
+                if (_bytesReceivedByProxy.Count == 4)
+                {
+                    // We received the greeting
+
+                    socket.Send(new byte[]
+                        {
+                            // SOCKS version
+                            0x05,
+                            // Require username/password authentication
+                            0x02
+                        });
+                }
+                else if (_bytesReceivedByProxy.Count == 4 + (1 + 1 + 2 + 1 + 4))
+                {
+                    // We received the username/password authentication request
+
+                    socket.Send(new byte[]
+                        {
+                            // Authentication version
+                            0x01,
+                            // Authentication failed
+                            0x01
+                        });
+                }
+            };
+            _proxyServer.Start();
+        }
+
+        protected override void SetupMocks()
+        {
+            SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+                             .Returns(_clientSocket);
+        }
+
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_proxyServer != null)
+            {
+                _proxyServer.Dispose();
+            }
+
+            if (_clientSocket != null)
+            {
+                _clientSocket.Dispose();
+            }
+        }
+
+        protected override void Act()
+        {
+            try
+            {
+                Connector.Connect(_connectionInfo);
+                Assert.Fail();
+            }
+            catch (ProxyException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveThrownProxyException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual("SOCKS5: Username/Password authentication failed.", _actualException.Message);
+        }
+
+        [TestMethod]
+        public void ProxyShouldHaveReceivedExpectedSocksRequest()
+        {
+            var expectedSocksRequest = new List<byte>();
+
+            //
+            // Client greeting
+            //
+
+            // SOCKS version
+            expectedSocksRequest.Add(0x05);
+            // Number of authentication methods supported
+            expectedSocksRequest.Add(0x02);
+            // No authentication
+            expectedSocksRequest.Add(0x00);
+            // Username/password
+            expectedSocksRequest.Add(0x02);
+
+            //
+            // Username/password authentication request
+            //
+
+            // Version of the negotiation
+            expectedSocksRequest.Add(0x01);
+            // Length of the username
+            expectedSocksRequest.Add((byte) _connectionInfo.ProxyUsername.Length);
+            // Username
+            expectedSocksRequest.AddRange(Encoding.ASCII.GetBytes(_connectionInfo.ProxyUsername));
+            // Length of the password
+            expectedSocksRequest.Add((byte) _connectionInfo.ProxyPassword.Length);
+            // Password
+            expectedSocksRequest.AddRange(Encoding.ASCII.GetBytes(_connectionInfo.ProxyPassword));
+
+            var errorText = string.Format("Expected:{0}{1}{0}but was:{0}{2}",
+                                          Environment.NewLine,
+                                          PacketDump.Create(expectedSocksRequest, 2),
+                                          PacketDump.Create(_bytesReceivedByProxy, 2));
+
+            Assert.IsTrue(expectedSocksRequest.SequenceEqual(_bytesReceivedByProxy), errorText);
+        }
+
+        [TestMethod]
+        public void ConnectionToProxyShouldHaveBeenShutDown()
+        {
+            Assert.IsTrue(_disconnected);
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldHaveBeenDisposed()
+        {
+            try
+            {
+                _clientSocket.Receive(new byte[0]);
+                Assert.Fail();
+            }
+            catch (ObjectDisposedException)
+            {
+            }
+        }
+
+        [TestMethod]
+        public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce()
+        {
+            SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),
+                                     Times.Once());
+        }
+    }
+}

+ 220 - 0
src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_ConnectionSucceeded.cs

@@ -0,0 +1,220 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Tests.Common;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_ConnectionSucceeded : Socks5ConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private AsyncSocketListener _proxyServer;
+        private Socket _clientSocket;
+        private List<byte> _bytesReceivedByProxy;
+        private Socket _actual;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _connectionInfo = CreateConnectionInfo(GenerateRandomString(0, 255), GenerateRandomString(0, 255));
+            _connectionInfo.Timeout = TimeSpan.FromMilliseconds(100);
+            _bytesReceivedByProxy = new List<byte>();
+
+            _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+
+            _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort));
+            _proxyServer.BytesReceived += (bytesReceived, socket) =>
+                {
+                    _bytesReceivedByProxy.AddRange(bytesReceived);
+
+                    if (_bytesReceivedByProxy.Count == 4)
+                    {
+                        // We received the greeting
+
+                        socket.Send(new byte[]
+                            {
+                                    // SOCKS version
+                                    0x05,
+                                    // Require username/password authentication
+                                    0x02
+                            });
+                    }
+                    else if (_bytesReceivedByProxy.Count == 4 + (1 + 1 + _connectionInfo.ProxyUsername.Length + 1 + _connectionInfo.ProxyPassword.Length))
+                    {
+                        // We received the username/password authentication request
+
+                        socket.Send(new byte[]
+                            {
+                                    // Authentication version
+                                    0x01,
+                                    // Authentication successful
+                                    0x00
+                            });
+                    }
+                    else if (_bytesReceivedByProxy.Count == 4 + (1 + 1 + _connectionInfo.ProxyUsername.Length + 1 + _connectionInfo.ProxyPassword.Length) + (1 + 1 + 1 + 1 + 4 + 2))
+                    {
+                        // We received the connection request
+
+                        socket.Send(new byte[]
+                            {
+                                    // SOCKS version
+                                    0x05,
+                                    // Connection successful
+                                    0x00,
+                                    // Reserved byte
+                                    0x00,
+                            });
+
+                        // Send server bound address
+                        socket.Send(new byte[]
+                            {
+                                    // IPv4
+                                    0x01,
+                                    // IP address
+                                    0x01,
+                                    0x02,
+                                    0x12,
+                                    0x41,
+                                    // Port
+                                    0x01,
+                                    0x02,
+                            });
+
+                        // Send extra byte to allow us to verify that connector did not consume too much
+                        socket.Send(new byte[]
+                            {
+                                0xfe
+                            });
+                    }
+                };
+            _proxyServer.Start();
+        }
+
+        protected override void SetupMocks()
+        {
+            SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+                             .Returns(_clientSocket);
+        }
+
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_proxyServer != null)
+            {
+                _proxyServer.Dispose();
+            }
+
+            if (_clientSocket != null)
+            {
+                _clientSocket.Shutdown(SocketShutdown.Both);
+                _clientSocket.Dispose();
+            }
+        }
+
+        protected override void Act()
+        {
+            _actual = Connector.Connect(_connectionInfo);
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveReturnedSocketCreatedUsingSocketFactory()
+        {
+            Assert.IsNotNull(_actual);
+            Assert.AreSame(_clientSocket, _actual);
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldBeConnected()
+        {
+            Assert.IsTrue(_clientSocket.Connected);
+        }
+
+        [TestMethod]
+        public void ProxyShouldHaveReceivedExpectedSocksRequest()
+        {
+            var expectedSocksRequest = new List<byte>();
+
+            //
+            // Client greeting
+            //
+
+            // SOCKS version
+            expectedSocksRequest.Add(0x05);
+            // Number of authentication methods supported
+            expectedSocksRequest.Add(0x02);
+            // No authentication
+            expectedSocksRequest.Add(0x00);
+            // Username/password
+            expectedSocksRequest.Add(0x02);
+
+            //
+            // Username/password authentication request
+            //
+
+            // Version of the negotiation
+            expectedSocksRequest.Add(0x01);
+            // Length of the username
+            expectedSocksRequest.Add((byte)_connectionInfo.ProxyUsername.Length);
+            // Username
+            expectedSocksRequest.AddRange(Encoding.ASCII.GetBytes(_connectionInfo.ProxyUsername));
+            // Length of the password
+            expectedSocksRequest.Add((byte)_connectionInfo.ProxyPassword.Length);
+            // Password
+            expectedSocksRequest.AddRange(Encoding.ASCII.GetBytes(_connectionInfo.ProxyPassword));
+
+            //
+            // Client connection request
+            //
+
+            // SOCKS version
+            expectedSocksRequest.Add(0x05);
+            // Establish a TCP/IP stream connection
+            expectedSocksRequest.Add(0x01);
+            // Reserved
+            expectedSocksRequest.Add(0x00);
+            // Destination address type (IPv4)
+            expectedSocksRequest.Add(0x01);
+            // Destination address (IPv4)
+            expectedSocksRequest.Add(0x7f);
+            expectedSocksRequest.Add(0x00);
+            expectedSocksRequest.Add(0x00);
+            expectedSocksRequest.Add(0x01);
+            // Destination port
+            expectedSocksRequest.Add(0x03);
+            expectedSocksRequest.Add(0x09);
+
+            var errorText = string.Format("Expected:{0}{1}{0}but was:{0}{2}",
+                                          Environment.NewLine,
+                                          PacketDump.Create(expectedSocksRequest, 2),
+                                          PacketDump.Create(_bytesReceivedByProxy, 2));
+
+            Assert.IsTrue(expectedSocksRequest.SequenceEqual(_bytesReceivedByProxy), errorText);
+        }
+
+        [TestMethod]
+        public void OnlySocksResponseShouldHaveBeenConsumed()
+        {
+            var buffer = new byte[1];
+
+            var bytesRead = _clientSocket.Receive(buffer);
+            Assert.AreEqual(1, bytesRead);
+            Assert.AreEqual(0xfe, buffer[0]);
+        }
+
+        [TestMethod]
+        public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce()
+        {
+            SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),
+                                     Times.Once());
+        }
+    }
+}

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

@@ -0,0 +1,149 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Tests.Common;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_PasswordExceedsMaximumLength : Socks5ConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private AsyncSocketListener _proxyServer;
+        private Socket _clientSocket;
+        private List<byte> _bytesReceivedByProxy;
+        private bool _disconnected;
+        private ProxyException _actualException;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _connectionInfo = CreateConnectionInfo(new string('a', 255), new string('b', 256));
+            _connectionInfo.Timeout = TimeSpan.FromMilliseconds(100);
+            _bytesReceivedByProxy = new List<byte>();
+            _actualException = null;
+
+            _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+
+            _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort));
+            _proxyServer.Disconnected += socket => _disconnected = true;
+            _proxyServer.BytesReceived += (bytesReceived, socket) =>
+            {
+                _bytesReceivedByProxy.AddRange(bytesReceived);
+
+                // Wait until we received the greeting
+                if (_bytesReceivedByProxy.Count == 4)
+                {
+                    socket.Send(new byte[]
+                        {
+                                // SOCKS version
+                                0x05,
+                                // Username/password authentication
+                                0x02
+                        });
+                }
+            };
+            _proxyServer.Start();
+        }
+
+        protected override void SetupMocks()
+        {
+            SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+                             .Returns(_clientSocket);
+        }
+
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_proxyServer != null)
+            {
+                _proxyServer.Dispose();
+            }
+
+            if (_clientSocket != null)
+            {
+                _clientSocket.Dispose();
+            }
+        }
+
+        protected override void Act()
+        {
+            try
+            {
+                Connector.Connect(_connectionInfo);
+                Assert.Fail();
+            }
+            catch (ProxyException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveThrownProxyException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual("Proxy password is too long.", _actualException.Message);
+        }
+
+        [TestMethod]
+        public void ProxyShouldHaveReceivedExpectedSocksRequest()
+        {
+            var expectedSocksRequest = new List<byte>();
+
+            //
+            // Client greeting
+            //
+
+            // SOCKS version
+            expectedSocksRequest.Add(0x05);
+            // Number of authentication methods supported
+            expectedSocksRequest.Add(0x02);
+            // No authentication
+            expectedSocksRequest.Add(0x00);
+            // Username/password
+            expectedSocksRequest.Add(0x02);
+
+            var errorText = string.Format("Expected:{0}{1}{0}but was:{0}{2}",
+                                          Environment.NewLine,
+                                          PacketDump.Create(expectedSocksRequest, 2),
+                                          PacketDump.Create(_bytesReceivedByProxy, 2));
+
+            Assert.IsTrue(expectedSocksRequest.SequenceEqual(_bytesReceivedByProxy), errorText);
+        }
+
+        [TestMethod]
+        public void ConnectionToProxyShouldHaveBeenShutDown()
+        {
+            Assert.IsTrue(_disconnected);
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldHaveBeenDisposed()
+        {
+            try
+            {
+                _clientSocket.Receive(new byte[0]);
+                Assert.Fail();
+            }
+            catch (ObjectDisposedException)
+            {
+            }
+        }
+
+        [TestMethod]
+        public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce()
+        {
+            SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),
+                                     Times.Once());
+        }
+    }
+}

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

@@ -0,0 +1,149 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Tests.Common;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_UserNameExceedsMaximumLength : Socks5ConnectorTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private AsyncSocketListener _proxyServer;
+        private Socket _clientSocket;
+        private List<byte> _bytesReceivedByProxy;
+        private bool _disconnected;
+        private ProxyException _actualException;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _connectionInfo = CreateConnectionInfo(new string('a', 256), new string('b', 255));
+            _connectionInfo.Timeout = TimeSpan.FromMilliseconds(100);
+            _bytesReceivedByProxy = new List<byte>();
+            _actualException = null;
+
+            _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+
+            _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort));
+            _proxyServer.Disconnected += socket => _disconnected = true;
+            _proxyServer.BytesReceived += (bytesReceived, socket) =>
+                {
+                    _bytesReceivedByProxy.AddRange(bytesReceived);
+
+                    // Wait until we received the greeting
+                    if (_bytesReceivedByProxy.Count == 4)
+                    {
+                        socket.Send(new byte[]
+                            {
+                                // SOCKS version
+                                0x05,
+                                // Username/password authentication
+                                0x02
+                            });
+                    }
+                };
+            _proxyServer.Start();
+        }
+
+        protected override void SetupMocks()
+        {
+            SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+                             .Returns(_clientSocket);
+        }
+
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_proxyServer != null)
+            {
+                _proxyServer.Dispose();
+            }
+
+            if (_clientSocket != null)
+            {
+                _clientSocket.Dispose();
+            }
+        }
+
+        protected override void Act()
+        {
+            try
+            {
+                Connector.Connect(_connectionInfo);
+                Assert.Fail();
+            }
+            catch (ProxyException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void ConnectShouldHaveThrownProxyException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual("Proxy username is too long.", _actualException.Message);
+        }
+
+        [TestMethod]
+        public void ProxyShouldHaveReceivedExpectedSocksRequest()
+        {
+            var expectedSocksRequest = new List<byte>();
+
+            //
+            // Client greeting
+            //
+
+            // SOCKS version
+            expectedSocksRequest.Add(0x05);
+            // Number of authentication methods supported
+            expectedSocksRequest.Add(0x02);
+            // No authentication
+            expectedSocksRequest.Add(0x00);
+            // Username/password
+            expectedSocksRequest.Add(0x02);
+
+            var errorText = string.Format("Expected:{0}{1}{0}but was:{0}{2}",
+                                          Environment.NewLine,
+                                          PacketDump.Create(expectedSocksRequest, 2),
+                                          PacketDump.Create(_bytesReceivedByProxy, 2));
+
+            Assert.IsTrue(expectedSocksRequest.SequenceEqual(_bytesReceivedByProxy), errorText);
+        }
+
+        [TestMethod]
+        public void ConnectionToProxyShouldHaveBeenShutDown()
+        {
+            Assert.IsTrue(_disconnected);
+        }
+
+        [TestMethod]
+        public void ClientSocketShouldHaveBeenDisposed()
+        {
+            try
+            {
+                _clientSocket.Receive(new byte[0]);
+                Assert.Fail();
+            }
+            catch (ObjectDisposedException)
+            {
+            }
+        }
+
+        [TestMethod]
+        public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce()
+        {
+            SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),
+                                     Times.Once());
+        }
+    }
+}

+ 139 - 0
src/Renci.SshNet.Tests/Classes/Connection/SshIdentificationTest.cs

@@ -0,0 +1,139 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Renci.SshNet.Connection;
+using System;
+
+namespace Renci.SshNet.Tests.Classes.Connection
+{
+    [TestClass]
+    public class SshIdentificationTest
+    {
+        [TestMethod]
+        public void Ctor_ProtocolVersionAndSoftwareVersion()
+        {
+            const string protocolVersion = "1.5";
+            const string softwareVersion = "SSH.NET_2020.0.0";
+
+            var sshIdentification = new SshIdentification(protocolVersion, softwareVersion);
+            Assert.AreSame(protocolVersion, sshIdentification.ProtocolVersion);
+            Assert.AreSame(softwareVersion, sshIdentification.SoftwareVersion);
+            Assert.IsNull(sshIdentification.Comments);
+        }
+
+        [TestMethod]
+        public void Ctor_ProtocolVersionAndSoftwareVersion_ProtocolVersionIsNull()
+        {
+            const string protocolVersion = null;
+            const string softwareVersion = "SSH.NET_2020.0.0";
+
+            try
+            {
+                new SshIdentification(protocolVersion, softwareVersion);
+                Assert.Fail();
+            }
+            catch (ArgumentNullException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+                Assert.AreEqual("protocolVersion", ex.ParamName);
+            }
+        }
+
+        [TestMethod]
+        public void Ctor_ProtocolVersionAndSoftwareVersion_SoftwareVersionIsNull()
+        {
+            const string protocolVersion = "2.0";
+            const string softwareVersion = null;
+
+            try
+            {
+                new SshIdentification(protocolVersion, softwareVersion);
+                Assert.Fail();
+            }
+            catch (ArgumentNullException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+                Assert.AreEqual("softwareVersion", ex.ParamName);
+            }
+        }
+
+        [TestMethod]
+        public void Ctor_ProtocolVersionAndSoftwareVersionAndComments()
+        {
+            const string protocolVersion = "1.5";
+            const string softwareVersion = "SSH.NET_2020.0.0";
+            const string comments = "Beware, dangerous!";
+
+            var sshIdentification = new SshIdentification(protocolVersion, softwareVersion, comments);
+            Assert.AreSame(protocolVersion, sshIdentification.ProtocolVersion);
+            Assert.AreSame(softwareVersion, sshIdentification.SoftwareVersion);
+            Assert.AreSame(comments, sshIdentification.Comments);
+        }
+
+        [TestMethod]
+        public void Ctor_ProtocolVersionAndSoftwareVersionAndComments_CommentsIsNull()
+        {
+            const string protocolVersion = "1.5";
+            const string softwareVersion = "SSH.NET_2020.0.0";
+            const string comments = null;
+
+            var sshIdentification = new SshIdentification(protocolVersion, softwareVersion, comments);
+            Assert.AreSame(protocolVersion, sshIdentification.ProtocolVersion);
+            Assert.AreSame(softwareVersion, sshIdentification.SoftwareVersion);
+            Assert.IsNull(comments, sshIdentification.Comments);
+        }
+
+        [TestMethod]
+        public void Ctor_ProtocolVersionAndSoftwareVersionAndComments_ProtocolVersionIsNull()
+        {
+            const string protocolVersion = null;
+            const string softwareVersion = "SSH.NET_2020.0.0";
+            const string comments = "Beware!";
+
+            try
+            {
+                new SshIdentification(protocolVersion, softwareVersion, comments);
+                Assert.Fail();
+            }
+            catch (ArgumentNullException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+                Assert.AreEqual("protocolVersion", ex.ParamName);
+            }
+        }
+
+        [TestMethod]
+        public void Ctor_ProtocolVersionAndSoftwareVersionAndComments_SoftwareVersionIsNull()
+        {
+            const string protocolVersion = "2.0";
+            const string softwareVersion = null;
+            const string comments = "Beware!";
+
+            try
+            {
+                new SshIdentification(protocolVersion, softwareVersion, comments);
+                Assert.Fail();
+            }
+            catch (ArgumentNullException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+                Assert.AreEqual("softwareVersion", ex.ParamName);
+            }
+        }
+
+        [TestMethod]
+        public void ToString_Comments()
+        {
+            var sshIdentification = new SshIdentification("2.0", "SSH.NET", "Beware, dangerous");
+            Assert.AreEqual("SSH-2.0-SSH.NET Beware, dangerous", sshIdentification.ToString());
+        }
+
+        [TestMethod]
+        public void ToString_CommentsIsNull()
+        {
+            var sshIdentification = new SshIdentification("2.0", "SSH.NET_2020.0.0");
+            Assert.AreEqual("SSH-2.0-SSH.NET_2020.0.0", sshIdentification.ToString());
+
+            sshIdentification = new SshIdentification("2.0", "SSH.NET_2020.0.0", null);
+            Assert.AreEqual("SSH-2.0-SSH.NET_2020.0.0", sshIdentification.ToString());
+        }
+    }
+}

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Messages/Connection/ChannelExtendedDataMessageTest.cs

@@ -30,7 +30,7 @@ namespace Renci.SshNet.Tests.Classes.Messages.Connection
         [Ignore] // placeholder
         public void ChannelExtendedDataMessageConstructorTest1()
         {
-            uint localChannelNumber = 0; // TODO: Initialize to an appropriate value
+            //uint localChannelNumber = 0; // TODO: Initialize to an appropriate value
             //ChannelExtendedDataMessage target = new ChannelExtendedDataMessage(localChannelNumber, null, null);
             Assert.Inconclusive("TODO: Implement code to verify target");
         }

+ 17 - 0
src/Renci.SshNet.Tests/Classes/NetConfClientTestBase.cs

@@ -0,0 +1,17 @@
+using Moq;
+using Renci.SshNet.NetConf;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    public abstract class NetConfClientTestBase : BaseClientTestBase
+    {
+        internal Mock<INetConfSession> _netConfSessionMock { get; private set; }
+
+        protected override void CreateMocks()
+        {
+            base.CreateMocks();
+
+            _netConfSessionMock = new Mock<INetConfSession>(MockBehavior.Strict);
+        }
+    }
+}

+ 9 - 31
src/Renci.SshNet.Tests/Classes/NetConfClientTest_Connect_NetConfSessionConnectFailure.cs

@@ -10,51 +10,29 @@ using Renci.SshNet.Security;
 namespace Renci.SshNet.Tests.Classes
 {
     [TestClass]
-    public class NetConfClientTest_Connect_NetConfSessionConnectFailure
+    public class NetConfClientTest_Connect_NetConfSessionConnectFailure : NetConfClientTestBase
     {
-        private Mock<IServiceFactory> _serviceFactoryMock;
-        private Mock<ISession> _sessionMock;
-        private Mock<INetConfSession> _netConfSessionMock;
         private ConnectionInfo _connectionInfo;
         private ApplicationException _netConfSessionConnectionException;
         private NetConfClient _netConfClient;
         private ApplicationException _actualException;
 
-        [TestInitialize]
-        public void Setup()
-        {
-            Arrange();
-            Act();
-        }
-
-        private void Arrange()
-        {
-            SetupData();
-            CreateMocks();
-            SetupMocks();
-
-            _netConfClient = new NetConfClient(_connectionInfo, false, _serviceFactoryMock.Object);
-        }
-
-        private void SetupData()
+        protected override void SetupData()
         {
             _connectionInfo = new ConnectionInfo("host", "user", new NoneAuthenticationMethod("userauth"));
             _netConfSessionConnectionException = new ApplicationException();
+            _netConfClient = new NetConfClient(_connectionInfo, false, _serviceFactoryMock.Object);
         }
 
-        private void CreateMocks()
-        {
-            _serviceFactoryMock = new Mock<IServiceFactory>(MockBehavior.Strict);
-            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
-            _netConfSessionMock = new Mock<INetConfSession>(MockBehavior.Strict);
-        }
-
-        private void SetupMocks()
+        protected override void SetupMocks()
         {
             var sequence = new MockSequence();
 
             _serviceFactoryMock.InSequence(sequence)
-                               .Setup(p => p.CreateSession(_connectionInfo))
+                               .Setup(p => p.CreateSocketFactory())
+                               .Returns(_socketFactoryMock.Object);
+            _serviceFactoryMock.InSequence(sequence)
+                               .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object))
                                .Returns(_sessionMock.Object);
             _sessionMock.InSequence(sequence)
                         .Setup(p => p.Connect());
@@ -70,7 +48,7 @@ namespace Renci.SshNet.Tests.Classes
                         .Setup(p => p.Dispose());
         }
 
-        private void Act()
+        protected override void Act()
         {
             try
             {

+ 39 - 33
src/Renci.SshNet.Tests/Classes/NetConfClientTest_Dispose_Connected.cs

@@ -6,56 +6,55 @@ using System;
 namespace Renci.SshNet.Tests.Classes
 {
     [TestClass]
-    public class NetConfClientTest_Dispose_Connected
+    public class NetConfClientTest_Dispose_Connected : NetConfClientTestBase
     {
-        private Mock<IServiceFactory> _serviceFactoryMock;
-        private Mock<ISession> _sessionMock;
-        private Mock<INetConfSession> _netConfSessionMock;
         private NetConfClient _netConfClient;
         private ConnectionInfo _connectionInfo;
         private int _operationTimeout;
 
-        [TestInitialize]
-        public void Setup()
+        protected override void SetupData()
         {
-            Arrange();
-            Act();
-        }
-
-        [TestCleanup]
-        public void Cleanup()
-        {
-        }
-
-        protected void Arrange()
-        {
-            _serviceFactoryMock = new Mock<IServiceFactory>(MockBehavior.Strict);
-            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
-            _netConfSessionMock = new Mock<INetConfSession>(MockBehavior.Strict);
-
             _connectionInfo = new ConnectionInfo("host", "user", new NoneAuthenticationMethod("userauth"));
             _operationTimeout = new Random().Next(1000, 10000);
             _netConfClient = new NetConfClient(_connectionInfo, false, _serviceFactoryMock.Object);
             _netConfClient.OperationTimeout = TimeSpan.FromMilliseconds(_operationTimeout);
+        }
 
+        protected override void SetupMocks()
+        {
             var sequence = new MockSequence();
+
+            _serviceFactoryMock.InSequence(sequence)
+                               .Setup(p => p.CreateSocketFactory())
+                               .Returns(_socketFactoryMock.Object);
             _serviceFactoryMock.InSequence(sequence)
-                .Setup(p => p.CreateSession(_connectionInfo))
-                .Returns(_sessionMock.Object);
-            _sessionMock.InSequence(sequence).Setup(p => p.Connect());
+                               .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object))
+                               .Returns(_sessionMock.Object);
+            _sessionMock.InSequence(sequence)
+                        .Setup(p => p.Connect());
             _serviceFactoryMock.InSequence(sequence)
-                .Setup(p => p.CreateNetConfSession(_sessionMock.Object, _operationTimeout))
-                .Returns(_netConfSessionMock.Object);
-            _netConfSessionMock.InSequence(sequence).Setup(p => p.Connect());
-            _sessionMock.InSequence(sequence).Setup(p => p.OnDisconnecting());
-            _netConfSessionMock.InSequence(sequence).Setup(p => p.Disconnect());
-            _sessionMock.InSequence(sequence).Setup(p => p.Dispose());
-            _netConfSessionMock.InSequence(sequence).Setup(p => p.Dispose());
+                               .Setup(p => p.CreateNetConfSession(_sessionMock.Object, _operationTimeout))
+                               .Returns(_netConfSessionMock.Object);
+            _netConfSessionMock.InSequence(sequence)
+                               .Setup(p => p.Connect());
+            _sessionMock.InSequence(sequence)
+                        .Setup(p => p.OnDisconnecting());
+            _netConfSessionMock.InSequence(sequence)
+                               .Setup(p => p.Disconnect());
+            _sessionMock.InSequence(sequence)
+                        .Setup(p => p.Dispose());
+            _netConfSessionMock.InSequence(sequence)
+                               .Setup(p => p.Dispose());
+        }
+
+        protected override void Arrange()
+        {
+            base.Arrange();
 
             _netConfClient.Connect();
         }
 
-        protected void Act()
+        protected override void Act()
         {
             _netConfClient.Dispose();
         }
@@ -66,10 +65,17 @@ namespace Renci.SshNet.Tests.Classes
             _serviceFactoryMock.Verify(p => p.CreateNetConfSession(_sessionMock.Object, _operationTimeout), Times.Once);
         }
 
+        [TestMethod]
+        public void CreateSocketFactoryOnServiceFactoryShouldBeInvokedOnce()
+        {
+            _serviceFactoryMock.Verify(p => p.CreateSocketFactory(), Times.Once);
+        }
+
         [TestMethod]
         public void CreateSessionOnServiceFactoryShouldBeInvokedOnce()
         {
-            _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo), Times.Once);
+            _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object),
+                                       Times.Once);
         }
 
         [TestMethod]

+ 27 - 15
src/Renci.SshNet.Tests/Classes/NetConfClientTest_Dispose_Disconnected.cs

@@ -6,11 +6,8 @@ using System;
 namespace Renci.SshNet.Tests.Classes
 {
     [TestClass]
-    public class NetConfClientTest_Dispose_Disconnected
+    public class NetConfClientTest_Dispose_Disconnected : NetConfClientTestBase
     {
-        private Mock<IServiceFactory> _serviceFactoryMock;
-        private Mock<ISession> _sessionMock;
-        private Mock<INetConfSession> _netConfSessionMock;
         private NetConfClient _netConfClient;
         private ConnectionInfo _connectionInfo;
         private int _operationTimeout;
@@ -27,37 +24,45 @@ namespace Renci.SshNet.Tests.Classes
         {
         }
 
-        protected void Arrange()
+        protected override void SetupData()
         {
-            _serviceFactoryMock = new Mock<IServiceFactory>(MockBehavior.Strict);
-            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
-            _netConfSessionMock = new Mock<INetConfSession>(MockBehavior.Strict);
-
             _connectionInfo = new ConnectionInfo("host", "user", new NoneAuthenticationMethod("userauth"));
             _operationTimeout = new Random().Next(1000, 10000);
             _netConfClient = new NetConfClient(_connectionInfo, false, _serviceFactoryMock.Object);
             _netConfClient.OperationTimeout = TimeSpan.FromMilliseconds(_operationTimeout);
+        }
 
+        protected override void SetupMocks()
+        {
             var sequence = new MockSequence();
+
+            _serviceFactoryMock.InSequence(sequence)
+                               .Setup(p => p.CreateSocketFactory())
+                               .Returns(_socketFactoryMock.Object);
             _serviceFactoryMock.InSequence(sequence)
-                .Setup(p => p.CreateSession(_connectionInfo))
-                .Returns(_sessionMock.Object);
+                               .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object))
+                               .Returns(_sessionMock.Object);
             _sessionMock.InSequence(sequence).Setup(p => p.Connect());
             _serviceFactoryMock.InSequence(sequence)
-                .Setup(p => p.CreateNetConfSession(_sessionMock.Object, _operationTimeout))
-                .Returns(_netConfSessionMock.Object);
+                               .Setup(p => p.CreateNetConfSession(_sessionMock.Object, _operationTimeout))
+                               .Returns(_netConfSessionMock.Object);
             _netConfSessionMock.InSequence(sequence).Setup(p => p.Connect());
             _sessionMock.InSequence(sequence).Setup(p => p.OnDisconnecting());
             _netConfSessionMock.InSequence(sequence).Setup(p => p.Disconnect());
             _sessionMock.InSequence(sequence).Setup(p => p.Dispose());
             _netConfSessionMock.InSequence(sequence).Setup(p => p.Disconnect());
             _netConfSessionMock.InSequence(sequence).Setup(p => p.Dispose());
+        }
+
+        protected override void Arrange()
+        {
+            base.Arrange();
 
             _netConfClient.Connect();
             _netConfClient.Disconnect();
         }
 
-        protected void Act()
+        protected override void Act()
         {
             _netConfClient.Dispose();
         }
@@ -68,10 +73,17 @@ namespace Renci.SshNet.Tests.Classes
             _serviceFactoryMock.Verify(p => p.CreateNetConfSession(_sessionMock.Object, _operationTimeout), Times.Once);
         }
 
+        [TestMethod]
+        public void CreateSocketFactoryOnServiceFactoryShouldBeInvokedOnce()
+        {
+            _serviceFactoryMock.Verify(p => p.CreateSocketFactory(), Times.Once);
+        }
+
         [TestMethod]
         public void CreateSessionOnServiceFactoryShouldBeInvokedOnce()
         {
-            _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo), Times.Once);
+            _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object),
+                                       Times.Once);
         }
 
         [TestMethod]

+ 27 - 27
src/Renci.SshNet.Tests/Classes/NetConfClientTest_Dispose_Disposed.cs

@@ -6,57 +6,50 @@ using System;
 namespace Renci.SshNet.Tests.Classes
 {
     [TestClass]
-    public class NetConfClientTest_Dispose_Disposed
+    public class NetConfClientTest_Dispose_Disposed : NetConfClientTestBase
     {
-        private Mock<IServiceFactory> _serviceFactoryMock;
-        private Mock<ISession> _sessionMock;
-        private Mock<INetConfSession> _netConfSessionMock;
         private NetConfClient _netConfClient;
         private ConnectionInfo _connectionInfo;
         private int _operationTimeout;
 
-        [TestInitialize]
-        public void Setup()
+        protected override void SetupData()
         {
-            Arrange();
-            Act();
-        }
-
-        [TestCleanup]
-        public void Cleanup()
-        {
-        }
-
-        protected void Arrange()
-        {
-            _serviceFactoryMock = new Mock<IServiceFactory>(MockBehavior.Strict);
-            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
-            _netConfSessionMock = new Mock<INetConfSession>(MockBehavior.Strict);
-
             _connectionInfo = new ConnectionInfo("host", "user", new NoneAuthenticationMethod("userauth"));
             _operationTimeout = new Random().Next(1000, 10000);
             _netConfClient = new NetConfClient(_connectionInfo, false, _serviceFactoryMock.Object);
             _netConfClient.OperationTimeout = TimeSpan.FromMilliseconds(_operationTimeout);
+        }
 
+        protected override void SetupMocks()
+        {
             var sequence = new MockSequence();
+
+            _serviceFactoryMock.InSequence(sequence)
+                               .Setup(p => p.CreateSocketFactory())
+                               .Returns(_socketFactoryMock.Object);
             _serviceFactoryMock.InSequence(sequence)
-                .Setup(p => p.CreateSession(_connectionInfo))
-                .Returns(_sessionMock.Object);
+                               .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object))
+                               .Returns(_sessionMock.Object);
             _sessionMock.InSequence(sequence).Setup(p => p.Connect());
             _serviceFactoryMock.InSequence(sequence)
-                .Setup(p => p.CreateNetConfSession(_sessionMock.Object, _operationTimeout))
-                .Returns(_netConfSessionMock.Object);
+                               .Setup(p => p.CreateNetConfSession(_sessionMock.Object, _operationTimeout))
+                               .Returns(_netConfSessionMock.Object);
             _netConfSessionMock.InSequence(sequence).Setup(p => p.Connect());
             _sessionMock.InSequence(sequence).Setup(p => p.OnDisconnecting());
             _netConfSessionMock.InSequence(sequence).Setup(p => p.Disconnect());
             _sessionMock.InSequence(sequence).Setup(p => p.Dispose());
             _netConfSessionMock.InSequence(sequence).Setup(p => p.Dispose());
+        }
+
+        protected override void Arrange()
+        {
+            base.Arrange();
 
             _netConfClient.Connect();
             _netConfClient.Dispose();
         }
 
-        protected void Act()
+        protected override void Act()
         {
             _netConfClient.Dispose();
         }
@@ -67,10 +60,17 @@ namespace Renci.SshNet.Tests.Classes
             _serviceFactoryMock.Verify(p => p.CreateNetConfSession(_sessionMock.Object, _operationTimeout), Times.Once);
         }
 
+        [TestMethod]
+        public void CreateSocketFactoryOnServiceFactoryShouldBeInvokedOnce()
+        {
+            _serviceFactoryMock.Verify(p => p.CreateSocketFactory(), Times.Once);
+        }
+
         [TestMethod]
         public void CreateSessionOnServiceFactoryShouldBeInvokedOnce()
         {
-            _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo), Times.Once);
+            _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object),
+                                       Times.Once);
         }
 
         [TestMethod]

+ 40 - 29
src/Renci.SshNet.Tests/Classes/NetConfClientTest_Finalize_Connected.cs

@@ -1,59 +1,58 @@
 using System;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using Moq;
-using Renci.SshNet.NetConf;
 
 namespace Renci.SshNet.Tests.Classes
 {
     [TestClass]
-    public class NetConfClientTest_Finalize_Connected
+    public class NetConfClientTest_Finalize_Connected : NetConfClientTestBase
     {
-        private Mock<IServiceFactory> _serviceFactoryMock;
-        private Mock<ISession> _sessionMock;
-        private Mock<INetConfSession> _netConfSessionMock;
         private NetConfClient _netConfClient;
         private ConnectionInfo _connectionInfo;
         private int _operationTimeout;
+        private WeakReference _netConfClientWeakRefence;
 
-        [TestInitialize]
-        public void Setup()
+        protected override void SetupData()
         {
-            Arrange();
-            Act();
-        }
-
-        protected void Arrange()
-        {
-            _serviceFactoryMock = new Mock<IServiceFactory>(MockBehavior.Loose);
-            _sessionMock = new Mock<ISession>(MockBehavior.Loose);
-            _netConfSessionMock = new Mock<INetConfSession>(MockBehavior.Loose);
-
             _connectionInfo = new ConnectionInfo("host", "user", new NoneAuthenticationMethod("userauth"));
             _operationTimeout = new Random().Next(1000, 10000);
             _netConfClient = new NetConfClient(_connectionInfo, false, _serviceFactoryMock.Object);
             _netConfClient.OperationTimeout = TimeSpan.FromMilliseconds(_operationTimeout);
+            _netConfClientWeakRefence = new WeakReference(_netConfClient);
+        }
 
+        protected override void SetupMocks()
+        {
             var sequence = new MockSequence();
+
+            _serviceFactoryMock.InSequence(sequence)
+                               .Setup(p => p.CreateSocketFactory())
+                               .Returns(_socketFactoryMock.Object);
             _serviceFactoryMock.InSequence(sequence)
-                .Setup(p => p.CreateSession(_connectionInfo))
-                .Returns(_sessionMock.Object);
-            _sessionMock.InSequence(sequence).Setup(p => p.Connect());
+                               .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object))
+                               .Returns(_sessionMock.Object);
+            _sessionMock.InSequence(sequence)
+                        .Setup(p => p.Connect());
             _serviceFactoryMock.InSequence(sequence)
-                .Setup(p => p.CreateNetConfSession(_sessionMock.Object, _operationTimeout))
-                .Returns(_netConfSessionMock.Object);
-            _netConfSessionMock.InSequence(sequence).Setup(p => p.Connect());
+                               .Setup(p => p.CreateNetConfSession(_sessionMock.Object, _operationTimeout))
+                               .Returns(_netConfSessionMock.Object);
+            _netConfSessionMock.InSequence(sequence)
+                               .Setup(p => p.Connect());
+        }
+
+        protected override void Arrange()
+        {
+            base.Arrange();
 
             _netConfClient.Connect();
             _netConfClient = null;
 
-            // we need to dereference all other mocks as they might otherwise hold the target alive
-            _sessionMock = null;
-            _connectionInfo = null;
-            _serviceFactoryMock = null;
-
+            // We need to dereference all mocks as they might otherwise hold the target alive
+            //(through recorded invocations?)
+            CreateMocks();
         }
 
-        protected void Act()
+        protected override void Act()
         {
             GC.Collect();
             GC.WaitForPendingFinalizers();
@@ -62,13 +61,25 @@ namespace Renci.SshNet.Tests.Classes
         [TestMethod]
         public void DisconnectOnNetConfSessionShouldBeInvokedOnce()
         {
+            // Since we recreated the mocks, this test has no value
+            // We'll leaving ths test just in case we have a solution that does not require us
+            // to recreate the mocks
             _netConfSessionMock.Verify(p => p.Disconnect(), Times.Never);
         }
 
         [TestMethod]
         public void DisposeOnNetConfSessionShouldBeInvokedOnce()
         {
+            // Since we recreated the mocks, this test has no value
+            // We'll leaving ths test just in case we have a solution that does not require us
+            // to recreate the mocks
             _netConfSessionMock.Verify(p => p.Dispose(), Times.Never);
         }
+
+        [TestMethod]
+        public void NetConfClientShouldHaveBeenFinalized()
+        {
+            Assert.IsNull(_netConfClientWeakRefence.Target);
+        }
     }
 }

+ 5 - 28
src/Renci.SshNet.Tests/Classes/ScpClientTestBase.cs

@@ -1,45 +1,22 @@
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-using Moq;
+using Moq;
 using Renci.SshNet.Channels;
 using Renci.SshNet.Common;
 
 namespace Renci.SshNet.Tests.Classes
 {
-    public abstract class ScpClientTestBase
+    public abstract class ScpClientTestBase : BaseClientTestBase
     {
-        internal Mock<IServiceFactory> _serviceFactoryMock;
         internal Mock<IRemotePathTransformation> _remotePathTransformationMock;
-        internal Mock<ISession> _sessionMock;
         internal Mock<IChannelSession> _channelSessionMock;
         internal Mock<PipeStream> _pipeStreamMock;
 
-        protected abstract void SetupData();
-
-        protected void CreateMocks()
+        protected override void CreateMocks()
         {
-            _serviceFactoryMock = new Mock<IServiceFactory>(MockBehavior.Strict);
+            base.CreateMocks();
+
             _remotePathTransformationMock = new Mock<IRemotePathTransformation>(MockBehavior.Strict);
-            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
             _channelSessionMock = new Mock<IChannelSession>(MockBehavior.Strict);
             _pipeStreamMock = new Mock<PipeStream>(MockBehavior.Strict);
         }
-
-        protected abstract void SetupMocks();
-
-        protected virtual void Arrange()
-        {
-            SetupData();
-            CreateMocks();
-            SetupMocks();
-        }
-
-        [TestInitialize]
-        public void Initialize()
-        {
-            Arrange();
-            Act();
-        }
-
-        protected abstract void Act();
     }
 }

+ 8 - 1
src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndDirectoryInfo_SendExecRequestReturnsFalse.cs

@@ -38,7 +38,10 @@ namespace Renci.SshNet.Tests.Classes
                                .Setup(p => p.CreateRemotePathDoubleQuoteTransformation())
                                .Returns(_remotePathTransformationMock.Object);
             _serviceFactoryMock.InSequence(sequence)
-                               .Setup(p => p.CreateSession(_connectionInfo))
+                               .Setup(p => p.CreateSocketFactory())
+                               .Returns(_socketFactoryMock.Object);
+            _serviceFactoryMock.InSequence(sequence)
+                               .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object))
                                .Returns(_sessionMock.Object);
             _sessionMock.InSequence(sequence).Setup(p => p.Connect());
             _serviceFactoryMock.InSequence(sequence).Setup(p => p.CreatePipeStream()).Returns(_pipeStreamMock.Object);
@@ -52,6 +55,10 @@ namespace Renci.SshNet.Tests.Classes
                                .Returns(false);
             _channelSessionMock.InSequence(sequence).Setup(p => p.Dispose());
             _pipeStreamMock.As<IDisposable>().InSequence(sequence).Setup(p => p.Dispose());
+
+            // On .NET Core, Dispose() in turn invokes Close() and since we're not mocking
+            // an interface, we need to expect this call as well
+            _pipeStreamMock.Setup(p => p.Close());
         }
 
         protected override void Arrange()

+ 8 - 1
src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndFileInfo_SendExecRequestReturnsFalse.cs

@@ -38,7 +38,10 @@ namespace Renci.SshNet.Tests.Classes
                                .Setup(p => p.CreateRemotePathDoubleQuoteTransformation())
                                .Returns(_remotePathTransformationMock.Object);
             _serviceFactoryMock.InSequence(sequence)
-                               .Setup(p => p.CreateSession(_connectionInfo))
+                               .Setup(p => p.CreateSocketFactory())
+                               .Returns(_socketFactoryMock.Object);
+            _serviceFactoryMock.InSequence(sequence)
+                               .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object))
                                .Returns(_sessionMock.Object);
             _sessionMock.InSequence(sequence).Setup(p => p.Connect());
             _serviceFactoryMock.InSequence(sequence).Setup(p => p.CreatePipeStream()).Returns(_pipeStreamMock.Object);
@@ -51,6 +54,10 @@ namespace Renci.SshNet.Tests.Classes
                 .Setup(p => p.SendExecRequest(string.Format("scp -pf {0}", _transformedPath))).Returns(false);
             _channelSessionMock.InSequence(sequence).Setup(p => p.Dispose());
             _pipeStreamMock.As<IDisposable>().InSequence(sequence).Setup(p => p.Dispose());
+
+            // On .NET Core, Dispose() in turn invokes Close() and since we're not mocking
+            // an interface, we need to expect this call as well
+            _pipeStreamMock.Setup(p => p.Close());
         }
 
         protected override void Arrange()

+ 18 - 10
src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndStream_SendExecRequestReturnsFalse.cs

@@ -19,15 +19,6 @@ namespace Renci.SshNet.Tests.Classes
         private IList<ScpUploadEventArgs> _uploadingRegister;
         private SshException _actualException;
 
-        [TestCleanup]
-        public void Cleanup()
-        {
-            if (_destination != null)
-            {
-                _destination.Dispose();
-            }
-        }
-
         protected override void SetupData()
         {
             var random = new Random();
@@ -47,7 +38,10 @@ namespace Renci.SshNet.Tests.Classes
                                .Setup(p => p.CreateRemotePathDoubleQuoteTransformation())
                                .Returns(_remotePathTransformationMock.Object);
             _serviceFactoryMock.InSequence(sequence)
-                               .Setup(p => p.CreateSession(_connectionInfo))
+                               .Setup(p => p.CreateSocketFactory())
+                               .Returns(_socketFactoryMock.Object);
+            _serviceFactoryMock.InSequence(sequence)
+                               .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object))
                                .Returns(_sessionMock.Object);
             _sessionMock.InSequence(sequence).Setup(p => p.Connect());
             _serviceFactoryMock.InSequence(sequence).Setup(p => p.CreatePipeStream()).Returns(_pipeStreamMock.Object);
@@ -61,6 +55,10 @@ namespace Renci.SshNet.Tests.Classes
                                .Returns(false);
             _channelSessionMock.InSequence(sequence).Setup(p => p.Dispose());
             _pipeStreamMock.As<IDisposable>().InSequence(sequence).Setup(p => p.Dispose());
+
+            // On .NET Core, Dispose() in turn invokes Close() and since we're not mocking
+            // an interface, we need to expect this call as well
+            _pipeStreamMock.Setup(p => p.Close());
         }
 
         protected override void Arrange()
@@ -72,6 +70,16 @@ namespace Renci.SshNet.Tests.Classes
             _scpClient.Connect();
         }
 
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_destination != null)
+            {
+                _destination.Dispose();
+            }
+        }
+
         protected override void Act()
         {
             try

+ 8 - 1
src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_DirectoryInfoAndPath_SendExecRequestReturnsFalse.cs

@@ -37,7 +37,10 @@ namespace Renci.SshNet.Tests.Classes
                                .Setup(p => p.CreateRemotePathDoubleQuoteTransformation())
                                .Returns(_remotePathTransformationMock.Object);
             _serviceFactoryMock.InSequence(sequence)
-                               .Setup(p => p.CreateSession(_connectionInfo))
+                               .Setup(p => p.CreateSocketFactory())
+                               .Returns(_socketFactoryMock.Object);
+            _serviceFactoryMock.InSequence(sequence)
+                               .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object))
                                .Returns(_sessionMock.Object);
             _sessionMock.InSequence(sequence).Setup(p => p.Connect());
             _serviceFactoryMock.InSequence(sequence).Setup(p => p.CreatePipeStream()).Returns(_pipeStreamMock.Object);
@@ -51,6 +54,10 @@ namespace Renci.SshNet.Tests.Classes
                                .Returns(false);
             _channelSessionMock.InSequence(sequence).Setup(p => p.Dispose());
             _pipeStreamMock.As<IDisposable>().InSequence(sequence).Setup(p => p.Dispose());
+
+            // On .NET Core, Dispose() in turn invokes Close() and since we're not mocking
+            // an interface, we need to expect this call as well
+            _pipeStreamMock.Setup(p => p.Close());
         }
 
         protected override void Arrange()

+ 20 - 12
src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_SendExecRequestReturnsFalse.cs

@@ -21,16 +21,6 @@ namespace Renci.SshNet.Tests.Classes
         private IList<ScpUploadEventArgs> _uploadingRegister;
         private SshException _actualException;
 
-        [TestCleanup]
-        public void Cleanup()
-        {
-            if (_fileName != null)
-            {
-                File.Delete(_fileName);
-                _fileName = null;
-            }
-        }
-
         protected override void SetupData()
         {
             var random = new Random();
@@ -53,7 +43,10 @@ namespace Renci.SshNet.Tests.Classes
                                .Setup(p => p.CreateRemotePathDoubleQuoteTransformation())
                                .Returns(_remotePathTransformationMock.Object);
             _serviceFactoryMock.InSequence(sequence)
-                               .Setup(p => p.CreateSession(_connectionInfo))
+                               .Setup(p => p.CreateSocketFactory())
+                               .Returns(_socketFactoryMock.Object);
+            _serviceFactoryMock.InSequence(sequence)
+                               .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object))
                                .Returns(_sessionMock.Object);
             _sessionMock.InSequence(sequence).Setup(p => p.Connect());
             _serviceFactoryMock.InSequence(sequence).Setup(p => p.CreatePipeStream()).Returns(_pipeStreamMock.Object);
@@ -67,7 +60,11 @@ namespace Renci.SshNet.Tests.Classes
                                .Returns(false);
             _channelSessionMock.InSequence(sequence).Setup(p => p.Dispose());
             _pipeStreamMock.As<IDisposable>().InSequence(sequence).Setup(p => p.Dispose());
-        }
+
+            // On .NET Core, Dispose() in turn invokes Close() and since we're not mocking
+            // an interface, we need to expect this call as well
+            _pipeStreamMock.Setup(p => p.Close());
+}
 
         protected override void Arrange()
         {
@@ -78,6 +75,17 @@ namespace Renci.SshNet.Tests.Classes
             _scpClient.Connect();
         }
 
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_fileName != null)
+            {
+                File.Delete(_fileName);
+                _fileName = null;
+            }
+        }
+
         protected override void Act()
         {
             try

+ 19 - 11
src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_Success.cs

@@ -25,16 +25,6 @@ namespace Renci.SshNet.Tests.Classes
         private int _fileSize;
         private IList<ScpUploadEventArgs> _uploadingRegister;
 
-        [TestCleanup]
-        public void Cleanup()
-        {
-            if (_fileName != null)
-            {
-                File.Delete(_fileName);
-                _fileName = null;
-            }
-        }
-
         protected override void SetupData()
         {
             var random = new Random();
@@ -60,7 +50,10 @@ namespace Renci.SshNet.Tests.Classes
                                .Setup(p => p.CreateRemotePathDoubleQuoteTransformation())
                                .Returns(_remotePathTransformationMock.Object);
             _serviceFactoryMock.InSequence(sequence)
-                               .Setup(p => p.CreateSession(_connectionInfo))
+                               .Setup(p => p.CreateSocketFactory())
+                               .Returns(_socketFactoryMock.Object);
+            _serviceFactoryMock.InSequence(sequence)
+                               .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object))
                                .Returns(_sessionMock.Object);
             _sessionMock.InSequence(sequence).Setup(p => p.Connect());
             _serviceFactoryMock.InSequence(sequence).Setup(p => p.CreatePipeStream()).Returns(_pipeStreamMock.Object);
@@ -92,6 +85,10 @@ namespace Renci.SshNet.Tests.Classes
             _pipeStreamMock.InSequence(sequence).Setup(p => p.ReadByte()).Returns(0);
             _channelSessionMock.InSequence(sequence).Setup(p => p.Dispose());
             _pipeStreamMock.As<IDisposable>().InSequence(sequence).Setup(p => p.Dispose());
+
+            // On .NET Core, Dispose() in turn invokes Close() and since we're not mocking
+            // an interface, we need to expect this call as well
+            _pipeStreamMock.Setup(p => p.Close());
         }
 
         protected override void Arrange()
@@ -106,6 +103,17 @@ namespace Renci.SshNet.Tests.Classes
             _scpClient.Connect();
         }
 
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_fileName != null)
+            {
+                File.Delete(_fileName);
+                _fileName = null;
+            }
+        }
+
         protected override void Act()
         {
             _scpClient.Upload(_fileInfo, _remotePath);

+ 18 - 10
src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_StreamAndPath_SendExecRequestReturnsFalse.cs

@@ -20,15 +20,6 @@ namespace Renci.SshNet.Tests.Classes
         private IList<ScpUploadEventArgs> _uploadingRegister;
         private SshException _actualException;
 
-        [TestCleanup]
-        public void Cleanup()
-        {
-            if (_source != null)
-            {
-                _source.Dispose();
-            }
-        }
-
         protected override void SetupData()
         {
             var random = new Random();
@@ -50,7 +41,10 @@ namespace Renci.SshNet.Tests.Classes
                                .Setup(p => p.CreateRemotePathDoubleQuoteTransformation())
                                .Returns(_remotePathTransformationMock.Object);
             _serviceFactoryMock.InSequence(sequence)
-                               .Setup(p => p.CreateSession(_connectionInfo))
+                               .Setup(p => p.CreateSocketFactory())
+                               .Returns(_socketFactoryMock.Object);
+            _serviceFactoryMock.InSequence(sequence)
+                               .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object))
                                .Returns(_sessionMock.Object);
             _sessionMock.InSequence(sequence).Setup(p => p.Connect());
             _serviceFactoryMock.InSequence(sequence).Setup(p => p.CreatePipeStream()).Returns(_pipeStreamMock.Object);
@@ -64,6 +58,10 @@ namespace Renci.SshNet.Tests.Classes
                                .Returns(false);
             _channelSessionMock.InSequence(sequence).Setup(p => p.Dispose());
             _pipeStreamMock.As<IDisposable>().InSequence(sequence).Setup(p => p.Dispose());
+
+            // On .NET Core, Dispose() in turn invokes Close() and since we're not mocking
+            // an interface, we need to expect this call as well
+            _pipeStreamMock.Setup(p => p.Close());
         }
 
         protected override void Arrange()
@@ -75,6 +73,16 @@ namespace Renci.SshNet.Tests.Classes
             _scpClient.Connect();
         }
 
+        protected override void TearDown()
+        {
+            base.TearDown();
+
+            if (_source != null)
+            {
+                _source.Dispose();
+            }
+        }
+
         protected override void Act()
         {
             try

+ 140 - 0
src/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateConnector.cs

@@ -0,0 +1,140 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Connection;
+using System;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ServiceFactoryTest_CreateConnector
+    {
+        private ServiceFactory _serviceFactory;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private Mock<ISocketFactory> _socketFactoryMock;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            _serviceFactory = new ServiceFactory();
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+            _socketFactoryMock = new Mock<ISocketFactory>(MockBehavior.Strict);
+        }
+
+        [TestMethod]
+        public void ConnectionInfoIsNull()
+        {
+            const IConnectionInfo connectionInfo = null;
+
+            try
+            {
+                _serviceFactory.CreateConnector(connectionInfo, _socketFactoryMock.Object);
+                Assert.Fail();
+            }
+            catch (ArgumentNullException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+                Assert.AreEqual("connectionInfo", ex.ParamName);
+            }
+        }
+
+        [TestMethod]
+        public void SocketFactoryIsNull()
+        {
+            const ISocketFactory socketFactory = null;
+
+            try
+            {
+                _serviceFactory.CreateConnector(_connectionInfoMock.Object, socketFactory);
+                Assert.Fail();
+            }
+            catch (ArgumentNullException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+                Assert.AreEqual("socketFactory", ex.ParamName);
+            }
+        }
+
+        [TestMethod]
+        public void ProxyType_Http()
+        {
+            _connectionInfoMock.Setup(p => p.ProxyType).Returns(ProxyTypes.Http);
+
+            var actual = _serviceFactory.CreateConnector(_connectionInfoMock.Object, _socketFactoryMock.Object);
+
+            Assert.IsNotNull(actual);
+            Assert.AreEqual(typeof(HttpConnector), actual.GetType());
+
+            var httpConnector = (HttpConnector) actual;
+            Assert.AreSame(_socketFactoryMock.Object, httpConnector.SocketFactory);
+
+            _connectionInfoMock.Verify(p => p.ProxyType, Times.Once);
+        }
+
+        [TestMethod]
+        public void ProxyType_None()
+        {
+            _connectionInfoMock.Setup(p => p.ProxyType).Returns(ProxyTypes.None);
+
+            var actual = _serviceFactory.CreateConnector(_connectionInfoMock.Object, _socketFactoryMock.Object);
+
+            Assert.IsNotNull(actual);
+            Assert.AreEqual(typeof(DirectConnector), actual.GetType());
+
+            var directConnector = (DirectConnector) actual;
+            Assert.AreSame(_socketFactoryMock.Object, directConnector.SocketFactory);
+
+            _connectionInfoMock.Verify(p => p.ProxyType, Times.Once);
+        }
+
+        [TestMethod]
+        public void ProxyType_Socks4()
+        {
+            _connectionInfoMock.Setup(p => p.ProxyType).Returns(ProxyTypes.Socks4);
+
+            var actual = _serviceFactory.CreateConnector(_connectionInfoMock.Object, _socketFactoryMock.Object);
+
+            Assert.IsNotNull(actual);
+            Assert.AreEqual(typeof(Socks4Connector), actual.GetType());
+
+            var socks4Connector = (Socks4Connector) actual;
+            Assert.AreSame(_socketFactoryMock.Object, socks4Connector.SocketFactory);
+
+            _connectionInfoMock.Verify(p => p.ProxyType, Times.Once);
+        }
+
+        [TestMethod]
+        public void ProxyType_Socks5()
+        {
+            _connectionInfoMock.Setup(p => p.ProxyType).Returns(ProxyTypes.Socks5);
+
+            var actual = _serviceFactory.CreateConnector(_connectionInfoMock.Object, _socketFactoryMock.Object);
+
+            Assert.IsNotNull(actual);
+            Assert.AreEqual(typeof(Socks5Connector), actual.GetType());
+
+            var socks5Connector = (Socks5Connector) actual;
+            Assert.AreSame(_socketFactoryMock.Object, socks5Connector.SocketFactory);
+
+            _connectionInfoMock.Verify(p => p.ProxyType, Times.Once);
+        }
+
+        [TestMethod]
+        public void ProxyType_Undefined()
+        {
+            _connectionInfoMock.Setup(p => p.ProxyType).Returns((ProxyTypes) 666);
+
+            try
+            {
+                _serviceFactory.CreateConnector(_connectionInfoMock.Object, _socketFactoryMock.Object);
+                Assert.Fail();
+            }
+            catch (NotSupportedException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+                Assert.AreEqual("ProxyTypes '666' is not supported.", ex.Message);
+            }
+
+            _connectionInfoMock.Verify(p => p.ProxyType, Times.Exactly(2));
+        }
+    }
+}

+ 2 - 229
src/Renci.SshNet.Tests/Classes/SessionTest.HttpProxy.cs

@@ -5,240 +5,13 @@ using System.Text;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using Renci.SshNet.Common;
 using Renci.SshNet.Tests.Common;
+using Renci.SshNet.Connection;
 
 namespace Renci.SshNet.Tests.Classes
 {
     public partial class SessionTest
     {
-        [TestMethod]
-        public void ConnectShouldThrowProxyExceptionWhenHttpProxyResponseDoesNotContainStatusLine()
-        {
-            var proxyEndPoint = new IPEndPoint(IPAddress.Loopback, 8123);
-            var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
-
-            using (var proxyStub = new HttpProxyStub(proxyEndPoint))
-            {
-                proxyStub.Responses.Add(Encoding.ASCII.GetBytes("Whatever\r\n"));
-                proxyStub.Start();
-
-                using (var session = new Session(CreateConnectionInfoWithProxy(proxyEndPoint, serverEndPoint, "anon"), _serviceFactoryMock.Object))
-                {
-                    try
-                    {
-                        session.Connect();
-                        Assert.Fail();
-                    }
-                    catch (ProxyException ex)
-                    {
-                        Assert.IsNull(ex.InnerException);
-                        Assert.AreEqual("HTTP response does not contain status line.", ex.Message);
-                    }
-                }
-            }
-        }
-
-        [TestMethod]
-        public void ConnectShouldThrowProxyExceptionWhenHttpProxyReturnsHttpStatusOtherThan200()
-        {
-            var proxyEndPoint = new IPEndPoint(IPAddress.Loopback, 8123);
-            var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
-
-            using (var proxyStub = new HttpProxyStub(proxyEndPoint))
-            {
-                proxyStub.Responses.Add(Encoding.ASCII.GetBytes("HTTP/1.0 501 Custom\r\n"));
-                proxyStub.Start();
-
-                using (var session = new Session(CreateConnectionInfoWithProxy(proxyEndPoint, serverEndPoint, "anon"), _serviceFactoryMock.Object))
-                {
-                    try
-                    {
-                        session.Connect();
-                        Assert.Fail();
-                    }
-                    catch (ProxyException ex)
-                    {
-                        Assert.IsNull(ex.InnerException);
-                        Assert.AreEqual("HTTP: Status code 501, \"Custom\"", ex.Message);
-                    }
-                }
-            }
-        }
-
-        [TestMethod]
-        public void ConnectShouldSkipHeadersWhenHttpProxyReturnsHttpStatus200()
-        {
-            var proxyEndPoint = new IPEndPoint(IPAddress.Loopback, 8123);
-            var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
-
-            using (var proxyStub = new HttpProxyStub(proxyEndPoint))
-            {
-                proxyStub.Responses.Add(Encoding.ASCII.GetBytes("HTTP/1.0 200 OK\r\n"));
-                proxyStub.Responses.Add(Encoding.ASCII.GetBytes("Content-Type: application/octet-stream\r\n"));
-                proxyStub.Responses.Add(Encoding.ASCII.GetBytes("\r\n"));
-                proxyStub.Responses.Add(Encoding.ASCII.GetBytes("SSH-666-SshStub"));
-                proxyStub.Start();
-
-                using (var session = new Session(CreateConnectionInfoWithProxy(proxyEndPoint, serverEndPoint, "anon"), _serviceFactoryMock.Object))
-                {
-                    try
-                    {
-                        session.Connect();
-                        Assert.Fail();
-                    }
-                    catch (SshConnectionException ex)
-                    {
-                        Assert.IsNull(ex.InnerException);
-                        Assert.AreEqual("Server version '666' is not supported.", ex.Message);
-                    }
-                }
-            }
-        }
-
-        [TestMethod]
-        public void ConnectShouldSkipContentWhenHttpProxyReturnsHttpStatus200()
-        {
-            var proxyEndPoint = new IPEndPoint(IPAddress.Loopback, 8123);
-            var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
-
-            using (var proxyStub = new HttpProxyStub(proxyEndPoint))
-            {
-                proxyStub.Responses.Add(Encoding.ASCII.GetBytes("HTTP/1.0 200 OK\r\n"));
-                proxyStub.Responses.Add(Encoding.ASCII.GetBytes("Content-Length: 13\r\n"));
-                proxyStub.Responses.Add(Encoding.ASCII.GetBytes("Content-Type: application/octet-stream\r\n"));
-                proxyStub.Responses.Add(Encoding.ASCII.GetBytes("\r\n"));
-                proxyStub.Responses.Add(Encoding.ASCII.GetBytes("DUMMY_CONTENT"));
-                proxyStub.Responses.Add(Encoding.ASCII.GetBytes("SSH-666-SshStub"));
-                proxyStub.Start();
-
-                using (var session = new Session(CreateConnectionInfoWithProxy(proxyEndPoint, serverEndPoint, "anon"), _serviceFactoryMock.Object))
-                {
-                    try
-                    {
-                        session.Connect();
-                        Assert.Fail();
-                    }
-                    catch (SshConnectionException ex)
-                    {
-                        Assert.IsNull(ex.InnerException);
-                        Assert.AreEqual("Server version '666' is not supported.", ex.Message);
-                    }
-                }
-            }
-        }
-
-        [TestMethod]
-        public void ConnectShouldWriteConnectMethodToHttpProxy()
-        {
-            var proxyEndPoint = new IPEndPoint(IPAddress.Loopback, 8123);
-            var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
-
-            using (var proxyStub = new HttpProxyStub(proxyEndPoint))
-            {
-                proxyStub.Responses.Add(Encoding.ASCII.GetBytes("HTTP/1.0 501 Custom\r\n"));
-                proxyStub.Start();
-
-                using (var session = new Session(CreateConnectionInfoWithProxy(proxyEndPoint, serverEndPoint, "anon"), _serviceFactoryMock.Object))
-                {
-                    try
-                    {
-                        session.Connect();
-                        Assert.Fail();
-                    }
-                    catch (ProxyException)
-                    {
-                    }
-                }
-
-                Assert.AreEqual(string.Format("CONNECT {0} HTTP/1.0", serverEndPoint), proxyStub.HttpRequest.RequestLine);
-            }
-        }
-
-        [TestMethod]
-        public void ConnectShouldWriteProxyAuthorizationToHttpProxyWhenProxyUserNameIsNotNullAndNotEmpty()
-        {
-            var proxyEndPoint = new IPEndPoint(IPAddress.Loopback, 8123);
-            var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
-
-            using (var proxyStub = new HttpProxyStub(proxyEndPoint))
-            {
-                proxyStub.Responses.Add(Encoding.ASCII.GetBytes("HTTP/1.0 501 Custom\r\n"));
-                proxyStub.Start();
-
-                var connectionInfo = CreateConnectionInfoWithProxy(proxyEndPoint, serverEndPoint, "anon");
-                using (var session = new Session(connectionInfo, _serviceFactoryMock.Object))
-                {
-                    try
-                    {
-                        session.Connect();
-                        Assert.Fail();
-                    }
-                    catch (ProxyException)
-                    {
-                    }
-                }
-
-                var expectedProxyAuthorizationHeader = CreateProxyAuthorizationHeader(connectionInfo);
-                Assert.IsNotNull(proxyStub.HttpRequest.Headers.SingleOrDefault(p => p == expectedProxyAuthorizationHeader));
-            }
-        }
-
-        [TestMethod]
-        public void ConnectShouldNotWriteProxyAuthorizationToHttpProxyWhenProxyUserNameIsEmpty()
-        {
-            var proxyEndPoint = new IPEndPoint(IPAddress.Loopback, 8123);
-            var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
-
-            using (var proxyStub = new HttpProxyStub(proxyEndPoint))
-            {
-                proxyStub.Responses.Add(Encoding.ASCII.GetBytes("HTTP/1.0 501 Custom\r\n"));
-                proxyStub.Start();
-
-                var connectionInfo = CreateConnectionInfoWithProxy(proxyEndPoint, serverEndPoint, string.Empty);
-                using (var session = new Session(connectionInfo, _serviceFactoryMock.Object))
-                {
-                    try
-                    {
-                        session.Connect();
-                        Assert.Fail();
-                    }
-                    catch (ProxyException)
-                    {
-                    }
-                }
-
-                Assert.IsFalse(proxyStub.HttpRequest.Headers.Any(p => p.StartsWith("Proxy-Authorization:")));
-            }
-        }
-
-        [TestMethod]
-        public void ConnectShouldNotWriteProxyAuthorizationToHttpProxyWhenProxyUserNameIsNull()
-        {
-            var proxyEndPoint = new IPEndPoint(IPAddress.Loopback, 8123);
-            var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
-
-            using (var proxyStub = new HttpProxyStub(proxyEndPoint))
-            {
-                proxyStub.Responses.Add(Encoding.ASCII.GetBytes("HTTP/1.0 501 Custom\r\n"));
-                proxyStub.Start();
-
-                var connectionInfo = CreateConnectionInfoWithProxy(proxyEndPoint, serverEndPoint, null);
-                using (var session = new Session(connectionInfo, _serviceFactoryMock.Object))
-                {
-                    try
-                    {
-                        session.Connect();
-                        Assert.Fail();
-                    }
-                    catch (ProxyException)
-                    {
-                    }
-                }
-
-                Assert.IsFalse(proxyStub.HttpRequest.Headers.Any(p => p.StartsWith("Proxy-Authorization:")));
-            }
-        }
-
-        private static ConnectionInfo CreateConnectionInfoWithProxy(IPEndPoint proxyEndPoint, IPEndPoint serverEndPoint, string proxyUserName)
+        private static ConnectionInfo CreateConnectionInfoWithHttpProxy(IPEndPoint proxyEndPoint, IPEndPoint serverEndPoint, string proxyUserName)
         {
             return new ConnectionInfo(
                 serverEndPoint.Address.ToString(),

+ 16 - 331
src/Renci.SshNet.Tests/Classes/SessionTest.cs

@@ -1,15 +1,9 @@
 using System;
-using System.Globalization;
 using System.Net;
-using System.Net.Sockets;
-using System.Text;
-using System.Threading;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using Moq;
-using Renci.SshNet.Common;
-using Renci.SshNet.Messages.Transport;
+using Renci.SshNet.Connection;
 using Renci.SshNet.Tests.Common;
-using Renci.SshNet.Tests.Properties;
 
 namespace Renci.SshNet.Tests.Classes
 {
@@ -20,23 +14,28 @@ namespace Renci.SshNet.Tests.Classes
     public partial class SessionTest : TestBase
     {
         private Mock<IServiceFactory> _serviceFactoryMock;
+        private Mock<ISocketFactory> _socketFactoryMock;
+        private Mock<IConnector> _connectorMock;
+        private Mock<IProtocolVersionExchange> _protocolVersionExchangeMock;
 
         protected override void OnInit()
         {
             base.OnInit();
 
             _serviceFactoryMock = new Mock<IServiceFactory>(MockBehavior.Strict);
+            _socketFactoryMock = new Mock<ISocketFactory>(MockBehavior.Strict);
+            _connectorMock = new Mock<IConnector>(MockBehavior.Strict);
+            _protocolVersionExchangeMock = new Mock<IProtocolVersionExchange>(MockBehavior.Strict);
         }
 
         [TestMethod]
         public void ConstructorShouldThrowArgumentNullExceptionWhenConnectionInfoIsNull()
         {
-            ConnectionInfo connectionInfo = null;
-            var serviceFactory = new Mock<IServiceFactory>(MockBehavior.Strict).Object;
+            const ConnectionInfo connectionInfo = null;
 
             try
             {
-                new Session(connectionInfo, serviceFactory);
+                new Session(connectionInfo, _serviceFactoryMock.Object, _socketFactoryMock.Object);
                 Assert.Fail();
             }
             catch (ArgumentNullException ex)
@@ -55,7 +54,7 @@ namespace Renci.SshNet.Tests.Classes
 
             try
             {
-                new Session(connectionInfo, serviceFactory);
+                new Session(connectionInfo, serviceFactory, _socketFactoryMock.Object);
                 Assert.Fail();
             }
             catch (ArgumentNullException ex)
@@ -66,338 +65,24 @@ namespace Renci.SshNet.Tests.Classes
         }
 
         [TestMethod]
-        public void ConnectShouldSkipLinesBeforeProtocolIdentificationString()
+        public void ConstructorShouldThrowArgumentNullExceptionWhenSocketFactoryIsNull()
         {
             var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
             var connectionInfo = CreateConnectionInfo(serverEndPoint, TimeSpan.FromSeconds(5));
-
-            using (var serverStub = new AsyncSocketListener(serverEndPoint))
-            {
-                serverStub.Connected += socket =>
-                    {
-                        socket.Send(Encoding.ASCII.GetBytes("\r\n"));
-                        socket.Send(Encoding.ASCII.GetBytes("WELCOME banner\r\n"));
-                        socket.Send(Encoding.ASCII.GetBytes("SSH-666-SshStub\r\n"));
-                        socket.Shutdown(SocketShutdown.Send);
-                    };
-                serverStub.Start();
-
-                using (var session = new Session(connectionInfo, _serviceFactoryMock.Object))
-                {
-                    try
-                    {
-                        session.Connect();
-                        Assert.Fail();
-                    }
-                    catch (SshConnectionException ex)
-                    {
-                        Assert.IsNull(ex.InnerException);
-                        Assert.AreEqual("Server version '666' is not supported.", ex.Message);
-
-                        Assert.AreEqual("SSH-666-SshStub", connectionInfo.ServerVersion);
-                    }
-                }
-            }
-        }
-
-        [TestMethod]
-        public void ConnectShouldImmediatelySendIdentificationStringWhenConnectionHasBeenEstablised()
-        {
-            var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
-            var connectionInfo = CreateConnectionInfo(serverEndPoint, TimeSpan.FromSeconds(5));
-
-            using (var serverStub = new AsyncSocketListener(serverEndPoint))
-            {
-                serverStub.Connected += socket =>
-                    {
-                        var identificationBytes = new byte[2048];
-                        var bytesReceived = socket.Receive(identificationBytes);
-
-                        if (bytesReceived > 0)
-                        {
-                            var identificationSttring = Encoding.ASCII.GetString(identificationBytes, 0, bytesReceived);
-                            Console.WriteLine("STRING=" + identificationSttring);
-                            Console.WriteLine("DONE");
-
-                            socket.Send(Encoding.ASCII.GetBytes("\r\n"));
-                            socket.Send(Encoding.ASCII.GetBytes("WELCOME banner\r\n"));
-                            socket.Send(Encoding.ASCII.GetBytes("SSH-666-SshStub\r\n"));
-                        }
-
-                        socket.Shutdown(SocketShutdown.Send);
-                    };
-                serverStub.Start();
-
-                using (var session = new Session(connectionInfo, _serviceFactoryMock.Object))
-                {
-                    try
-                    {
-                        session.Connect();
-                        Assert.Fail();
-                    }
-                    catch (SshConnectionException ex)
-                    {
-                        Assert.IsNull(ex.InnerException);
-                        Assert.AreEqual("Server version '666' is not supported.", ex.Message);
-
-                        Assert.AreEqual("SSH-666-SshStub", connectionInfo.ServerVersion);
-                    }
-                }
-            }
-        }
-
-
-        [TestMethod]
-        public void ConnectShouldSupportProtocolIdentificationStringThatDoesNotEndWithCrlf()
-        {
-            var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
-            var connectionInfo = CreateConnectionInfo(serverEndPoint, TimeSpan.FromSeconds(5));
-
-            using (var serverStub = new AsyncSocketListener(serverEndPoint))
-            {
-                serverStub.Connected += socket =>
-                    {
-                        socket.Send(Encoding.ASCII.GetBytes("\r\n"));
-                        socket.Send(Encoding.ASCII.GetBytes("WELCOME banner\r\n"));
-                        socket.Send(Encoding.ASCII.GetBytes("SSH-666-SshStub"));
-                        socket.Shutdown(SocketShutdown.Send);
-                    };
-                serverStub.Start();
-
-                using (var session = new Session(connectionInfo, _serviceFactoryMock.Object))
-                {
-                    try
-                    {
-                        session.Connect();
-                        Assert.Fail();
-                    }
-                    catch (SshConnectionException ex)
-                    {
-                        Assert.IsNull(ex.InnerException);
-                        Assert.AreEqual("Server version '666' is not supported.", ex.Message);
-
-                        Assert.AreEqual("SSH-666-SshStub", connectionInfo.ServerVersion);
-                    }
-                }
-            }
-        }
-
-        [TestMethod]
-        public void ConnectShouldThrowSshOperationExceptionWhenServerDoesNotRespondWithinConnectionTimeout()
-        {
-            var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
-            var timeout = TimeSpan.FromMilliseconds(500);
-            Socket clientSocket = null;
-
-            using (var serverStub = new AsyncSocketListener(serverEndPoint))
-            {
-                serverStub.Connected += socket =>
-                    {
-                        socket.Send(Encoding.ASCII.GetBytes("\r\n"));
-                        socket.Send(Encoding.ASCII.GetBytes("WELCOME banner\r\n"));
-                        clientSocket = socket;
-                    };
-                serverStub.Start();
-
-                using (var session = new Session(CreateConnectionInfo(serverEndPoint, TimeSpan.FromMilliseconds(500)), _serviceFactoryMock.Object))
-                {
-                    try
-                    {
-                        session.Connect();
-                        Assert.Fail();
-                    }
-                    catch (SshOperationTimeoutException ex)
-                    {
-                        Assert.IsNull(ex.InnerException);
-                        Assert.AreEqual(string.Format(CultureInfo.InvariantCulture, "Socket read operation has timed out after {0:F0} milliseconds.", timeout.TotalMilliseconds), ex.Message);
-
-                        Assert.IsNotNull(clientSocket);
-                        Assert.IsTrue(clientSocket.Connected);
-
-                        // shut down socket
-                        clientSocket.Shutdown(SocketShutdown.Send);
-                    }
-                }
-            }
-        }
-
-        [TestMethod]
-        public void ConnectShouldSshConnectionExceptionWhenServerResponseDoesNotContainProtocolIdentificationString()
-        {
-            var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
-
-            // response ends with CRLF
-            using (var serverStub = new AsyncSocketListener(serverEndPoint))
-            {
-                serverStub.Connected += socket =>
-                    {
-                        socket.Send(Encoding.ASCII.GetBytes("\r\n"));
-                        socket.Send(Encoding.ASCII.GetBytes("WELCOME banner\r\n"));
-                        socket.Shutdown(SocketShutdown.Send);
-                    };
-                serverStub.Start();
-
-                using (var session = new Session(CreateConnectionInfo(serverEndPoint, TimeSpan.FromSeconds(5)), _serviceFactoryMock.Object))
-                {
-                    try
-                    {
-                        session.Connect();
-                        Assert.Fail();
-                    }
-                    catch (SshConnectionException ex)
-                    {
-                        Assert.IsNull(ex.InnerException);
-                        Assert.AreEqual("Server response does not contain SSH protocol identification.", ex.Message);
-                    }
-                }
-            }
-
-            // response does not end with CRLF
-            using (var serverStub = new AsyncSocketListener(serverEndPoint))
-            {
-                serverStub.Connected += socket =>
-                    {
-                        socket.Send(Encoding.ASCII.GetBytes("\r\n"));
-                        socket.Send(Encoding.ASCII.GetBytes("WELCOME banner"));
-                        socket.Shutdown(SocketShutdown.Send);
-                    };
-                serverStub.Start();
-
-                using (var session = new Session(CreateConnectionInfo(serverEndPoint, TimeSpan.FromSeconds(5)), _serviceFactoryMock.Object))
-                {
-                    try
-                    {
-                        session.Connect();
-                        Assert.Fail();
-                    }
-                    catch (SshConnectionException ex)
-                    {
-                        Assert.IsNull(ex.InnerException);
-                        Assert.AreEqual("Server response does not contain SSH protocol identification.", ex.Message);
-                    }
-                }
-            }
-
-            // last line is empty
-            using (var serverStub = new AsyncSocketListener(serverEndPoint))
-            {
-                serverStub.Connected += socket =>
-                    {
-                        socket.Send(Encoding.ASCII.GetBytes("\r\n"));
-                        socket.Send(Encoding.ASCII.GetBytes("WELCOME banner\r\n"));
-                        socket.Send(Encoding.ASCII.GetBytes("\r\n"));
-                        socket.Shutdown(SocketShutdown.Send);
-                    };
-                serverStub.Start();
-
-                using (var session = new Session(CreateConnectionInfo(serverEndPoint, TimeSpan.FromSeconds(5)), _serviceFactoryMock.Object))
-                {
-                    try
-                    {
-                        session.Connect();
-                        Assert.Fail();
-                    }
-                    catch (SshConnectionException ex)
-                    {
-                        Assert.IsNull(ex.InnerException);
-                        Assert.AreEqual("Server response does not contain SSH protocol identification.", ex.Message);
-                    }
-                }
-            }
-        }
-
-        [TestMethod]
-        public void Connect_HostNameInvalid_ShouldThrowSocketExceptionWithErrorCodeHostNotFound()
-        {
-            var connectionInfo = new ConnectionInfo("invalid.", 40, "user",
-                new KeyboardInteractiveAuthenticationMethod("user"));
-            var session = new Session(connectionInfo, _serviceFactoryMock.Object);
-
-            try
-            {
-                session.Connect();
-                Assert.Fail();
-            }
-            catch (SocketException ex)
-            {
-                Assert.AreEqual(ex.ErrorCode, (int)SocketError.HostNotFound);
-            }
-        }
-
-        [TestMethod]
-        public void Connect_ProxyHostNameInvalid_ShouldThrowSocketExceptionWithErrorCodeHostNotFound()
-        {
-            var connectionInfo = new ConnectionInfo("localhost", 40, "user", ProxyTypes.Http, "invalid.", 80,
-                "proxyUser", "proxyPwd", new KeyboardInteractiveAuthenticationMethod("user"));
-            var session = new Session(connectionInfo, _serviceFactoryMock.Object);
-
-            try
-            {
-                session.Connect();
-                Assert.Fail();
-            }
-            catch (SocketException ex)
-            {
-                Assert.AreEqual(ex.ErrorCode, (int)SocketError.HostNotFound);
-            }
-        }
-
-        [TestMethod]
-        public void DisconnectShouldNotThrowExceptionWhenSocketIsNotConnected()
-        {
-            var connectionInfo = new ConnectionInfo("localhost", 6767, Resources.USERNAME,
-                new KeyboardInteractiveAuthenticationMethod(Resources.USERNAME));
-            var session = new Session(connectionInfo, _serviceFactoryMock.Object);
+            const ISocketFactory socketFactory = null;
 
             try
             {
-                session.Connect();
+                new Session(connectionInfo, _serviceFactoryMock.Object, socketFactory);
                 Assert.Fail();
             }
-            catch (SocketException)
-            {
-                session.Disconnect();
-            }
-        }
-
-        [TestMethod]
-        public void DisconnectShouldNotThrowExceptionWhenConnectHasNotBeenInvoked()
-        {
-            var connectionInfo = new ConnectionInfo("localhost", 6767, Resources.USERNAME,
-                new KeyboardInteractiveAuthenticationMethod(Resources.USERNAME));
-            var session = new Session(connectionInfo, _serviceFactoryMock.Object);
-
-            session.Disconnect();
-        }
-
-        [TestMethod]
-        public void DisposeShouldNotThrowExceptionWhenSocketIsNotConnected()
-        {
-            var connectionInfo = new ConnectionInfo("localhost", 6767, Resources.USERNAME,
-                new KeyboardInteractiveAuthenticationMethod(Resources.USERNAME));
-            var session = new Session(connectionInfo, _serviceFactoryMock.Object);
-
-            try
-            {
-                session.Connect();
-                Assert.Fail();
-            }
-            catch (SocketException)
+            catch (ArgumentNullException ex)
             {
-                session.Dispose();
+                Assert.IsNull(ex.InnerException);
+                Assert.AreEqual("socketFactory", ex.ParamName);
             }
         }
 
-        [TestMethod]
-        public void DisposeShouldNotThrowExceptionWhenConenectHasNotBeenInvoked()
-        {
-            var connectionInfo = new ConnectionInfo("localhost", 6767, Resources.USERNAME,
-                new KeyboardInteractiveAuthenticationMethod(Resources.USERNAME));
-            var session = new Session(connectionInfo, _serviceFactoryMock.Object);
-
-            session.Disconnect();
-        }
-
         private static ConnectionInfo CreateConnectionInfo(IPEndPoint serverEndPoint, TimeSpan timeout)
         {
             var connectionInfo = new ConnectionInfo(

+ 35 - 0
src/Renci.SshNet.Tests/Classes/SessionTestBase.cs

@@ -0,0 +1,35 @@
+using Moq;
+using Renci.SshNet.Connection;
+using Renci.SshNet.Tests.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    public abstract class SessionTestBase : TripleATestBase
+    {
+        internal Mock<IServiceFactory> _serviceFactoryMock { get; private set; }
+        internal Mock<ISocketFactory> _socketFactoryMock { get; private set; }
+        internal Mock<IConnector> _connectorMock { get; private set; }
+
+        protected virtual void CreateMocks()
+        {
+            _serviceFactoryMock = new Mock<IServiceFactory>(MockBehavior.Strict);
+            _socketFactoryMock = new Mock<ISocketFactory>(MockBehavior.Strict);
+            _connectorMock = new Mock<IConnector>(MockBehavior.Strict);
+        }
+
+        protected virtual void SetupData()
+        {
+        }
+
+        protected virtual void SetupMocks()
+        {
+        }
+
+        protected override void Arrange()
+        {
+            CreateMocks();
+            SetupData();
+            SetupMocks();
+        }
+    }
+}

+ 265 - 0
src/Renci.SshNet.Tests/Classes/SessionTest_ConnectToServerFails.cs

@@ -0,0 +1,265 @@
+using System;
+using System.Net;
+using System.Threading;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Transport;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class SessionTest_ConnectToServerFails : SessionTestBase
+    {
+        private ConnectionInfo _connectionInfo;
+        private Session _session;
+        private SshConnectionException _connectException;
+        private SshConnectionException _actualException;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
+            _connectionInfo = CreateConnectionInfo(serverEndPoint, TimeSpan.FromSeconds(5));
+            _session = new Session(_connectionInfo, _serviceFactoryMock.Object, _socketFactoryMock.Object);
+            _connectException = new SshConnectionException();
+        }
+
+        protected override void SetupMocks()
+        {
+            base.SetupMocks();
+
+            _serviceFactoryMock.Setup(p => p.CreateConnector(_connectionInfo, _socketFactoryMock.Object))
+                               .Returns(_connectorMock.Object);
+            _connectorMock.Setup(p => p.Connect(_connectionInfo))
+                          .Throws(_connectException);
+        }
+
+        protected override void Act()
+        {
+            try
+            {
+                _session.Connect();
+                Assert.Fail();
+            }
+            catch (SshConnectionException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void ClientVersionIsRenciSshNet()
+        {
+            Assert.AreEqual("SSH-2.0-Renci.SshNet.SshClient.0.0.1", _session.ClientVersion);
+        }
+
+        [TestMethod]
+        public void ConnectionInfoShouldReturnConnectionInfoPassedThroughConstructor()
+        {
+            Assert.AreSame(_connectionInfo, _session.ConnectionInfo);
+        }
+
+        public void ConnectShouldHaveRethrownException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.AreSame(_connectException, _actualException);
+        }
+
+        [TestMethod]
+        public void DisconnectShouldNotThrowAnException()
+        {
+            _session.Disconnect();
+        }
+
+        [TestMethod]
+        public void DisposeShouldNotThrowException()
+        {
+            _session.Dispose();
+        }
+
+        [TestMethod]
+        public void IsConnectedShouldReturnFalse()
+        {
+            Assert.IsFalse(_session.IsConnected);
+        }
+
+        [TestMethod]
+        public void SendMessageShouldThrowShhConnectionException()
+        {
+            try
+            {
+                _session.SendMessage(new IgnoreMessage());
+                Assert.Fail();
+            }
+            catch (SshConnectionException ex)
+            {
+                Assert.AreEqual(DisconnectReason.None, ex.DisconnectReason);
+                Assert.IsNull(ex.InnerException);
+                Assert.AreEqual("Client not connected.", ex.Message);
+            }
+        }
+
+        [TestMethod]
+        public void SessionIdShouldReturnNull()
+        {
+            Assert.IsNull(_session.SessionId);
+        }
+
+        [TestMethod]
+        public void ServerVersionShouldReturnNull()
+        {
+            Assert.IsNull(_session.ServerVersion);
+        }
+
+        [TestMethod]
+        public void WaitOnHandle_WaitOnHandle_WaitHandle_ShouldThrowArgumentNullExceptionWhenWaitHandleIsNull()
+        {
+            const WaitHandle waitHandle = null;
+
+            try
+            {
+                _session.WaitOnHandle(waitHandle);
+                Assert.Fail();
+            }
+            catch (ArgumentNullException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+                Assert.AreEqual("waitHandle", ex.ParamName);
+            }
+        }
+
+        [TestMethod]
+        public void WaitOnHandle_WaitOnHandle_WaitHandleAndTimeout_ShouldThrowArgumentNullExceptionWhenWaitHandleIsNull()
+        {
+            const WaitHandle waitHandle = null;
+            var timeout = TimeSpan.FromMinutes(5);
+
+            try
+            {
+                _session.WaitOnHandle(waitHandle, timeout);
+                Assert.Fail();
+            }
+            catch (ArgumentNullException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+                Assert.AreEqual("waitHandle", ex.ParamName);
+            }
+        }
+
+        [TestMethod]
+        public void ISession_TryWait_WaitHandleAndTimeout_ShouldReturnDisconnected()
+        {
+            var session = (ISession)_session;
+            var waitHandle = new ManualResetEvent(false);
+
+            var result = session.TryWait(waitHandle, Session.InfiniteTimeSpan);
+
+            Assert.AreEqual(WaitResult.Disconnected, result);
+        }
+
+        [TestMethod]
+        public void ISession_TryWait_WaitHandleAndTimeoutAndException_ShouldReturnDisconnected()
+        {
+            var session = (ISession)_session;
+            var waitHandle = new ManualResetEvent(false);
+            Exception exception;
+
+            var result = session.TryWait(waitHandle, Session.InfiniteTimeSpan, out exception);
+
+            Assert.AreEqual(WaitResult.Disconnected, result);
+            Assert.IsNull(exception);
+        }
+
+        [TestMethod]
+        public void ISession_ConnectionInfoShouldReturnConnectionInfoPassedThroughConstructor()
+        {
+            var session = (ISession)_session;
+            Assert.AreSame(_connectionInfo, session.ConnectionInfo);
+        }
+
+        [TestMethod]
+        public void ISession_MessageListenerCompletedShouldBeSignaled()
+        {
+            var session = (ISession)_session;
+
+            Assert.IsNotNull(session.MessageListenerCompleted);
+            Assert.IsTrue(session.MessageListenerCompleted.WaitOne(0));
+        }
+
+        [TestMethod]
+        public void ISession_SendMessageShouldThrowShhConnectionException()
+        {
+            var session = (ISession)_session;
+
+            try
+            {
+                session.SendMessage(new IgnoreMessage());
+                Assert.Fail();
+            }
+            catch (SshConnectionException ex)
+            {
+                Assert.AreEqual(DisconnectReason.None, ex.DisconnectReason);
+                Assert.IsNull(ex.InnerException);
+                Assert.AreEqual("Client not connected.", ex.Message);
+            }
+        }
+
+        [TestMethod]
+        public void ISession_TrySendMessageShouldReturnFalse()
+        {
+            var session = (ISession)_session;
+
+            var actual = session.TrySendMessage(new IgnoreMessage());
+
+            Assert.IsFalse(actual);
+        }
+
+        [TestMethod]
+        public void ISession_WaitOnHandle_WaitHandle_ShouldThrowArgumentNullExceptionWhenWaitHandleIsNull()
+        {
+            const WaitHandle waitHandle = null;
+            var session = (ISession)_session;
+
+            try
+            {
+                session.WaitOnHandle(waitHandle);
+                Assert.Fail();
+            }
+            catch (ArgumentNullException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+                Assert.AreEqual("waitHandle", ex.ParamName);
+            }
+        }
+
+        [TestMethod]
+        public void ISession_WaitOnHandle_WaitHandleAndTimeout_ShouldThrowArgumentNullExceptionWhenWaitHandleIsNull()
+        {
+            const WaitHandle waitHandle = null;
+            var session = (ISession)_session;
+
+            try
+            {
+                session.WaitOnHandle(waitHandle, Session.InfiniteTimeSpan);
+                Assert.Fail();
+            }
+            catch (ArgumentNullException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+                Assert.AreEqual("waitHandle", ex.ParamName);
+            }
+        }
+
+        private static ConnectionInfo CreateConnectionInfo(IPEndPoint serverEndPoint, TimeSpan timeout)
+        {
+            var connectionInfo = new ConnectionInfo(
+                serverEndPoint.Address.ToString(),
+                serverEndPoint.Port,
+                "eric",
+                new NoneAuthenticationMethod("eric"));
+            connectionInfo.Timeout = timeout;
+            return connectionInfo;
+        }
+    }
+}

+ 21 - 1
src/Renci.SshNet.Tests/Classes/SessionTest_Connected.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Threading;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
 using Renci.SshNet.Messages.Transport;
 
 namespace Renci.SshNet.Tests.Classes
@@ -65,7 +66,7 @@ namespace Renci.SshNet.Tests.Classes
         public void ServerVersionShouldNotReturnNull()
         {
             Assert.IsNotNull(Session.ServerVersion);
-            Assert.AreEqual("SSH-2.0-SshStub", Session.ServerVersion);
+            Assert.AreEqual("SSH-2.0-OurServerStub", Session.ServerVersion);
         }
 
         [TestMethod]
@@ -251,5 +252,24 @@ namespace Renci.SshNet.Tests.Classes
                 Assert.IsNull(exception);
             }
         }
+
+        [TestMethod]
+        public void ClientSocketShouldBeConnected()
+        {
+            Assert.IsNotNull(ClientSocket);
+            Assert.IsTrue(ClientSocket.Connected);
+        }
+
+        [TestMethod]
+        public void CreateConnectorOnServiceFactoryShouldHaveBeenInvokedOnce()
+        {
+            ServiceFactoryMock.Verify(p => p.CreateConnector(ConnectionInfo, SocketFactoryMock.Object), Times.Once());
+        }
+
+        [TestMethod]
+        public void ConnectorOnConnectorShouldHaveBeenInvokedOnce()
+        {
+            ConnectorMock.Verify(p => p.Connect(ConnectionInfo), Times.Once());
+        }
     }
 }

+ 63 - 37
src/Renci.SshNet.Tests/Classes/SessionTest_ConnectedBase.cs

@@ -9,6 +9,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
 using Moq;
 using Renci.SshNet.Common;
 using Renci.SshNet.Compression;
+using Renci.SshNet.Connection;
 using Renci.SshNet.Messages;
 using Renci.SshNet.Messages.Transport;
 using Renci.SshNet.Security;
@@ -20,11 +21,17 @@ namespace Renci.SshNet.Tests.Classes
     [TestClass]
     public abstract class SessionTest_ConnectedBase
     {
-        private Mock<IServiceFactory> _serviceFactoryMock;
+        internal Mock<IServiceFactory> ServiceFactoryMock { get; private set; }
+        internal Mock<ISocketFactory> SocketFactoryMock { get; private set; }
+        internal Mock<IConnector> ConnectorMock { get; private set; }
+
+        private Mock<IProtocolVersionExchange> _protocolVersionExchangeMock;
         private Mock<IKeyExchange> _keyExchangeMock;
         private Mock<IClientAuthentication> _clientAuthenticationMock;
         private IPEndPoint _serverEndPoint;
         private string _keyExchangeAlgorithm;
+        private bool _authenticationStarted;
+        private SocketFactory _socketFactory;
 
         protected Random Random { get; private set; }
         protected byte[] SessionId { get; private set; }
@@ -35,7 +42,9 @@ namespace Renci.SshNet.Tests.Classes
         protected AsyncSocketListener ServerListener { get; private set; }
         protected IList<byte[]> ServerBytesReceivedRegister { get; private set; }
         protected Session Session { get; private set; }
+        protected Socket ClientSocket { get; private set; }
         protected Socket ServerSocket { get; private set; }
+        internal SshIdentification ServerIdentification { get; private set; }
 
         [TestInitialize]
         public void Setup()
@@ -64,6 +73,12 @@ namespace Renci.SshNet.Tests.Classes
                 Session.Dispose();
                 Session = null;
             }
+
+            if (ClientSocket != null && ClientSocket.Connected)
+            {
+                ClientSocket.Shutdown(SocketShutdown.Both);
+                ClientSocket.Dispose();
+            }
         }
 
         protected virtual void SetupData()
@@ -84,8 +99,11 @@ namespace Renci.SshNet.Tests.Classes
             DisconnectReceivedRegister = new List<MessageEventArgs<DisconnectMessage>>();
             ErrorOccurredRegister = new List<ExceptionEventArgs>();
             ServerBytesReceivedRegister = new List<byte[]>();
+            ServerIdentification = new SshIdentification("2.0", "OurServerStub");
+            _authenticationStarted = false;
+            _socketFactory = new SocketFactory();
 
-            Session = new Session(ConnectionInfo, _serviceFactoryMock.Object);
+            Session = new Session(ConnectionInfo, ServiceFactoryMock.Object, SocketFactoryMock.Object);
             Session.Disconnected += (sender, args) => DisconnectedRegister.Add(args);
             Session.DisconnectReceived += (sender, args) => DisconnectReceivedRegister.Add(args);
             Session.ErrorOccured += (sender, args) => ErrorOccurredRegister.Add(args);
@@ -102,56 +120,65 @@ namespace Renci.SshNet.Tests.Classes
                 {
                     ServerSocket = socket;
 
-                    socket.Send(Encoding.ASCII.GetBytes("\r\n"));
-                    socket.Send(Encoding.ASCII.GetBytes("WELCOME banner\r\n"));
-                    socket.Send(Encoding.ASCII.GetBytes("SSH-2.0-SshStub\r\n"));
+                    // Since we're mocking the protocol version exchange, we'll immediately stat KEX upon
+                    // having established the connection instead of when the client has been identified
+
+                    var keyExchangeInitMessage = new KeyExchangeInitMessage
+                        {
+                            CompressionAlgorithmsClientToServer = new string[0],
+                            CompressionAlgorithmsServerToClient = new string[0],
+                            EncryptionAlgorithmsClientToServer = new string[0],
+                            EncryptionAlgorithmsServerToClient = new string[0],
+                            KeyExchangeAlgorithms = new[] { _keyExchangeAlgorithm },
+                            LanguagesClientToServer = new string[0],
+                            LanguagesServerToClient = new string[0],
+                            MacAlgorithmsClientToServer = new string[0],
+                            MacAlgorithmsServerToClient = new string[0],
+                            ServerHostKeyAlgorithms = new string[0]
+                        };
+                    var keyExchangeInit = keyExchangeInitMessage.GetPacket(8, null);
+                    ServerSocket.Send(keyExchangeInit, 4, keyExchangeInit.Length - 4, SocketFlags.None);
                 };
-
-            var counter = 0;
-
             ServerListener.BytesReceived += (received, socket) =>
                 {
                     ServerBytesReceivedRegister.Add(received);
 
-                    switch (counter++)
+                    if (!_authenticationStarted)
                     {
-                        case 0:
-                            var keyExchangeInitMessage = new KeyExchangeInitMessage
-                            {
-                                CompressionAlgorithmsClientToServer = new string[0],
-                                CompressionAlgorithmsServerToClient = new string[0],
-                                EncryptionAlgorithmsClientToServer = new string[0],
-                                EncryptionAlgorithmsServerToClient = new string[0],
-                                KeyExchangeAlgorithms = new[] {_keyExchangeAlgorithm},
-                                LanguagesClientToServer = new string[0],
-                                LanguagesServerToClient = new string[0],
-                                MacAlgorithmsClientToServer = new string[0],
-                                MacAlgorithmsServerToClient = new string[0],
-                                ServerHostKeyAlgorithms = new string[0]
-                            };
-                            var keyExchangeInit = keyExchangeInitMessage.GetPacket(8, null);
-                            ServerSocket.Send(keyExchangeInit, 4, keyExchangeInit.Length - 4, SocketFlags.None);
-                            break;
-                        case 1:
-                            var serviceAcceptMessage =
-                                ServiceAcceptMessageBuilder.Create(ServiceName.UserAuthentication)
-                                    .Build();
-                            ServerSocket.Send(serviceAcceptMessage, 0, serviceAcceptMessage.Length, SocketFlags.None);
-                            break;
+                        var serviceAcceptMessage = ServiceAcceptMessageBuilder.Create(ServiceName.UserAuthentication)
+                                                                              .Build();
+                        ServerSocket.Send(serviceAcceptMessage, 0, serviceAcceptMessage.Length, SocketFlags.None);
+
+                        _authenticationStarted = true;
                     }
                 };
+            ServerListener.Start();
+
+            ClientSocket = new DirectConnector(_socketFactory).Connect(ConnectionInfo);
         }
 
         private void CreateMocks()
         {
-            _serviceFactoryMock = new Mock<IServiceFactory>(MockBehavior.Strict);
+            ServiceFactoryMock = new Mock<IServiceFactory>(MockBehavior.Strict);
+            SocketFactoryMock = new Mock<ISocketFactory>(MockBehavior.Strict);
+            ConnectorMock = new Mock<IConnector>(MockBehavior.Strict);
+            _protocolVersionExchangeMock = new Mock<IProtocolVersionExchange>(MockBehavior.Strict);
             _keyExchangeMock = new Mock<IKeyExchange>(MockBehavior.Strict);
             _clientAuthenticationMock = new Mock<IClientAuthentication>(MockBehavior.Strict);
         }
 
         private void SetupMocks()
         {
-            _serviceFactoryMock.Setup(
+            ServiceFactoryMock.Setup(p => p.CreateConnector(ConnectionInfo, SocketFactoryMock.Object))
+                              .Returns(ConnectorMock.Object);
+            ConnectorMock.Setup(p => p.Connect(ConnectionInfo))
+                         .Returns(ClientSocket);
+            ServiceFactoryMock.Setup(p => p.CreateProtocolVersionExchange())
+                              .Returns(_protocolVersionExchangeMock.Object);
+            _protocolVersionExchangeMock.Setup(p => p.Start(Session.ClientVersion, ClientSocket, ConnectionInfo.Timeout))
+                                        .Returns(ServerIdentification);
+
+            ServiceFactoryMock.Setup(
                 p =>
                     p.CreateKeyExchange(ConnectionInfo.KeyExchangeAlgorithms, new[] { _keyExchangeAlgorithm })).Returns(_keyExchangeMock.Object);
             _keyExchangeMock.Setup(p => p.Name).Returns(_keyExchangeAlgorithm);
@@ -164,19 +191,18 @@ namespace Renci.SshNet.Tests.Classes
             _keyExchangeMock.Setup(p => p.CreateCompressor()).Returns((Compressor) null);
             _keyExchangeMock.Setup(p => p.CreateDecompressor()).Returns((Compressor) null);
             _keyExchangeMock.Setup(p => p.Dispose());
-            _serviceFactoryMock.Setup(p => p.CreateClientAuthentication())
+            ServiceFactoryMock.Setup(p => p.CreateClientAuthentication())
                 .Callback(ClientAuthentication_Callback)
                 .Returns(_clientAuthenticationMock.Object);
             _clientAuthenticationMock.Setup(p => p.Authenticate(ConnectionInfo, Session));
         }
 
-        protected virtual void Arrange()
+        protected void Arrange()
         {
             CreateMocks();
             SetupData();
             SetupMocks();
 
-            ServerListener.Start();
             Session.Connect();
         }
 

+ 9 - 1
src/Renci.SshNet.Tests/Classes/SessionTest_Connected_Disconnect.cs

@@ -5,6 +5,7 @@ using System.Threading;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using Renci.SshNet.Common;
 using Renci.SshNet.Messages.Transport;
+using Renci.SshNet.Tests.Common;
 
 namespace Renci.SshNet.Tests.Classes
 {
@@ -49,7 +50,7 @@ namespace Renci.SshNet.Tests.Classes
         [TestMethod]
         public void ErrorOccurredIsNeverRaised()
         {
-            Assert.AreEqual(0, ErrorOccurredRegister.Count);
+            Assert.AreEqual(0, ErrorOccurredRegister.Count, ErrorOccurredRegister.AsString());
         }
 
         [TestMethod]
@@ -188,5 +189,12 @@ namespace Renci.SshNet.Tests.Classes
             Assert.AreEqual(WaitResult.Disconnected, result);
             Assert.IsNull(exception);
         }
+
+        [TestMethod]
+        public void ClientSocketShouldNotBeConnected()
+        {
+            Assert.IsNotNull(ClientSocket);
+            Assert.IsFalse(ClientSocket.Connected);
+        }
     }
 }

+ 2 - 1
src/Renci.SshNet.Tests/Classes/SessionTest_Connected_GlobalRequestMessageAfterAuthenticationRace.cs

@@ -2,6 +2,7 @@
 using System.Text;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using Renci.SshNet.Messages.Connection;
+using Renci.SshNet.Tests.Common;
 
 namespace Renci.SshNet.Tests.Classes
 {
@@ -33,7 +34,7 @@ namespace Renci.SshNet.Tests.Classes
         [TestMethod]
         public void ErrorOccurredShouldNotBeRaised()
         {
-            Assert.AreEqual(0, ErrorOccurredRegister.Count);
+            Assert.AreEqual(0, ErrorOccurredRegister.Count, ErrorOccurredRegister.AsString());
         }
     }
 }

+ 55 - 31
src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerAndClientDisconnectRace.cs

@@ -4,11 +4,11 @@ using System.Globalization;
 using System.Net;
 using System.Net.Sockets;
 using System.Security.Cryptography;
-using System.Text;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using Moq;
 using Renci.SshNet.Common;
 using Renci.SshNet.Compression;
+using Renci.SshNet.Connection;
 using Renci.SshNet.Messages;
 using Renci.SshNet.Messages.Transport;
 using Renci.SshNet.Security;
@@ -21,11 +21,16 @@ namespace Renci.SshNet.Tests.Classes
     public class SessionTest_Connected_ServerAndClientDisconnectRace
     {
         private Mock<IServiceFactory> _serviceFactoryMock;
+        private Mock<ISocketFactory> _socketFactoryMock;
+        private Mock<IConnector> _connectorMock;
+        private Mock<IProtocolVersionExchange> _protocolVersionExchangeMock;
         private Mock<IKeyExchange> _keyExchangeMock;
         private Mock<IClientAuthentication> _clientAuthenticationMock;
         private IPEndPoint _serverEndPoint;
         private string _keyExchangeAlgorithm;
         private DisconnectMessage _disconnectMessage;
+        private SocketFactory _socketFactory;
+        private bool _authenticationStarted;
 
         protected Random Random { get; private set; }
         protected byte[] SessionId { get; private set; }
@@ -36,7 +41,9 @@ namespace Renci.SshNet.Tests.Classes
         protected AsyncSocketListener ServerListener { get; private set; }
         protected IList<byte[]> ServerBytesReceivedRegister { get; private set; }
         protected Session Session { get; private set; }
+        protected Socket ClientSocket { get; private set; }
         protected Socket ServerSocket { get; private set; }
+        internal SshIdentification ServerIdentification { get; private set; }
 
         private void TearDown()
         {
@@ -49,6 +56,12 @@ namespace Renci.SshNet.Tests.Classes
             {
                 Session.Dispose();
             }
+
+            if (ClientSocket != null && ClientSocket.Connected)
+            {
+                ClientSocket.Shutdown(SocketShutdown.Both);
+                ClientSocket.Dispose();
+            }
         }
 
         protected virtual void SetupData()
@@ -69,39 +82,31 @@ namespace Renci.SshNet.Tests.Classes
             DisconnectReceivedRegister = new List<MessageEventArgs<DisconnectMessage>>();
             ErrorOccurredRegister = new List<ExceptionEventArgs>();
             ServerBytesReceivedRegister = new List<byte[]>();
+            ServerIdentification = new SshIdentification("2.0", "OurServerStub");
+            _authenticationStarted = false;
             _disconnectMessage = new DisconnectMessage(DisconnectReason.ServiceNotAvailable, "Not today!");
+            _socketFactory = new SocketFactory();
 
-            Session = new Session(ConnectionInfo, _serviceFactoryMock.Object);
+            Session = new Session(ConnectionInfo, _serviceFactoryMock.Object, _socketFactoryMock.Object);
             Session.Disconnected += (sender, args) => DisconnectedRegister.Add(args);
             Session.DisconnectReceived += (sender, args) => DisconnectReceivedRegister.Add(args);
             Session.ErrorOccured += (sender, args) => ErrorOccurredRegister.Add(args);
             Session.KeyExchangeInitReceived += (sender, args) =>
-            {
-                var newKeysMessage = new NewKeysMessage();
-                var newKeys = newKeysMessage.GetPacket(8, null);
-                ServerSocket.Send(newKeys, 4, newKeys.Length - 4, SocketFlags.None);
-            };
+                {
+                    var newKeysMessage = new NewKeysMessage();
+                    var newKeys = newKeysMessage.GetPacket(8, null);
+                    ServerSocket.Send(newKeys, 4, newKeys.Length - 4, SocketFlags.None);
+                };
 
             ServerListener = new AsyncSocketListener(_serverEndPoint);
             ServerListener.Connected += socket =>
-            {
-                ServerSocket = socket;
-
-                socket.Send(Encoding.ASCII.GetBytes("\r\n"));
-                socket.Send(Encoding.ASCII.GetBytes("WELCOME banner\r\n"));
-                socket.Send(Encoding.ASCII.GetBytes("SSH-2.0-SshStub\r\n"));
-            };
-
-            var counter = 0;
+                {
+                    ServerSocket = socket;
 
-            ServerListener.BytesReceived += (received, socket) =>
-            {
-                ServerBytesReceivedRegister.Add(received);
+                    // Since we're mocking the protocol version exchange, we'll immediately stat KEX upon
+                    // having established the connection instead of when the client has been identified
 
-                switch (counter++)
-                {
-                    case 0:
-                        var keyExchangeInitMessage = new KeyExchangeInitMessage
+                    var keyExchangeInitMessage = new KeyExchangeInitMessage
                         {
                             CompressionAlgorithmsClientToServer = new string[0],
                             CompressionAlgorithmsServerToClient = new string[0],
@@ -114,26 +119,46 @@ namespace Renci.SshNet.Tests.Classes
                             MacAlgorithmsServerToClient = new string[0],
                             ServerHostKeyAlgorithms = new string[0]
                         };
-                        var keyExchangeInit = keyExchangeInitMessage.GetPacket(8, null);
-                        ServerSocket.Send(keyExchangeInit, 4, keyExchangeInit.Length - 4, SocketFlags.None);
-                        break;
-                    case 1:
+                    var keyExchangeInit = keyExchangeInitMessage.GetPacket(8, null);
+                    ServerSocket.Send(keyExchangeInit, 4, keyExchangeInit.Length - 4, SocketFlags.None);
+                };
+            ServerListener.BytesReceived += (received, socket) =>
+                {
+                    ServerBytesReceivedRegister.Add(received);
+
+                    if (!_authenticationStarted)
+                    {
                         var serviceAcceptMessage =ServiceAcceptMessageBuilder.Create(ServiceName.UserAuthentication).Build();
                         ServerSocket.Send(serviceAcceptMessage, 0, serviceAcceptMessage.Length, SocketFlags.None);
-                        break;
-                }
-            };
+                        _authenticationStarted = true;
+                    }
+                };
+
+            ServerListener.Start();
+
+            ClientSocket = new DirectConnector(_socketFactory).Connect(ConnectionInfo);
         }
 
         private void CreateMocks()
         {
             _serviceFactoryMock = new Mock<IServiceFactory>(MockBehavior.Strict);
+            _socketFactoryMock = new Mock<ISocketFactory>(MockBehavior.Strict);
+            _connectorMock = new Mock<IConnector>(MockBehavior.Strict);
+            _protocolVersionExchangeMock = new Mock<IProtocolVersionExchange>(MockBehavior.Strict);
             _keyExchangeMock = new Mock<IKeyExchange>(MockBehavior.Strict);
             _clientAuthenticationMock = new Mock<IClientAuthentication>(MockBehavior.Strict);
         }
 
         private void SetupMocks()
         {
+            _serviceFactoryMock.Setup(p => p.CreateConnector(ConnectionInfo, _socketFactoryMock.Object))
+                               .Returns(_connectorMock.Object);
+            _connectorMock.Setup(p => p.Connect(ConnectionInfo))
+                          .Returns(ClientSocket);
+            _serviceFactoryMock.Setup(p => p.CreateProtocolVersionExchange())
+                               .Returns(_protocolVersionExchangeMock.Object);
+            _protocolVersionExchangeMock.Setup(p => p.Start(Session.ClientVersion, ClientSocket, ConnectionInfo.Timeout))
+                                        .Returns(ServerIdentification);
             _serviceFactoryMock.Setup(
                 p =>
                     p.CreateKeyExchange(ConnectionInfo.KeyExchangeAlgorithms, new[] { _keyExchangeAlgorithm })).Returns(_keyExchangeMock.Object);
@@ -157,7 +182,6 @@ namespace Renci.SshNet.Tests.Classes
             SetupData();
             SetupMocks();
 
-            ServerListener.Start();
             Session.Connect();
         }
 

+ 2 - 1
src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerSendsDisconnectMessage.cs

@@ -6,6 +6,7 @@ using System.Threading;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using Renci.SshNet.Common;
 using Renci.SshNet.Messages.Transport;
+using Renci.SshNet.Tests.Common;
 
 namespace Renci.SshNet.Tests.Classes
 {
@@ -70,7 +71,7 @@ namespace Renci.SshNet.Tests.Classes
         [TestMethod]
         public void ErrorOccurredIsNeverRaised()
         {
-            Assert.AreEqual(0, ErrorOccurredRegister.Count);
+            Assert.AreEqual(0, ErrorOccurredRegister.Count, ErrorOccurredRegister.AsString());
         }
 
         [TestMethod]

+ 2 - 1
src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerSendsDisconnectMessageAndShutsDownSocket.cs

@@ -6,6 +6,7 @@ using System.Threading;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using Renci.SshNet.Common;
 using Renci.SshNet.Messages.Transport;
+using Renci.SshNet.Tests.Common;
 
 namespace Renci.SshNet.Tests.Classes
 {
@@ -73,7 +74,7 @@ namespace Renci.SshNet.Tests.Classes
         [TestMethod]
         public void ErrorOccurredIsNeverRaised()
         {
-            Assert.AreEqual(0, ErrorOccurredRegister.Count);
+            Assert.AreEqual(0, ErrorOccurredRegister.Count, ErrorOccurredRegister.AsString());
         }
 
         [TestMethod]

+ 2 - 1
src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerSendsUnsupportedMessageType.cs

@@ -5,6 +5,7 @@ using System.Threading;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using Renci.SshNet.Common;
 using Renci.SshNet.Messages.Transport;
+using Renci.SshNet.Tests.Common;
 
 namespace Renci.SshNet.Tests.Classes
 {
@@ -66,7 +67,7 @@ namespace Renci.SshNet.Tests.Classes
         [TestMethod]
         public void ErrorOccurredIsRaisedOnce()
         {
-            Assert.AreEqual(1, ErrorOccurredRegister.Count);
+            Assert.AreEqual(1, ErrorOccurredRegister.Count, ErrorOccurredRegister.AsString());
 
             var errorOccurred = ErrorOccurredRegister[0];
             Assert.IsNotNull(errorOccurred);

+ 18 - 13
src/Renci.SshNet.Tests/Classes/SessionTest_NotConnected.cs

@@ -4,34 +4,27 @@ using System.Threading;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using Moq;
 using Renci.SshNet.Common;
+using Renci.SshNet.Connection;
 using Renci.SshNet.Messages.Transport;
+using Renci.SshNet.Tests.Common;
 
 namespace Renci.SshNet.Tests.Classes
 {
     [TestClass]
-    public class SessionTest_NotConnected
+    public class SessionTest_NotConnected : SessionTestBase
     {
         private ConnectionInfo _connectionInfo;
-        private IServiceFactory _serviceFactory;
         private Session _session;
 
-        [TestInitialize]
-        public void Setup()
-        {
-            Arrange();
-            Act();
-        }
-
-        protected void Arrange()
+        protected override void SetupData()
         {
             var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
             _connectionInfo = CreateConnectionInfo(serverEndPoint, TimeSpan.FromSeconds(5));
-            _serviceFactory = new Mock<IServiceFactory>(MockBehavior.Strict).Object;
         }
 
-        protected void Act()
+        protected override void Act()
         {
-            _session = new Session(_connectionInfo, _serviceFactory);
+            _session = new Session(_connectionInfo, _serviceFactoryMock.Object, _socketFactoryMock.Object);
         }
 
         [TestMethod]
@@ -46,6 +39,18 @@ namespace Renci.SshNet.Tests.Classes
             Assert.AreSame(_connectionInfo, _session.ConnectionInfo);
         }
 
+        [TestMethod]
+        public void DisconnectShouldNotThrowException()
+        {
+            _session.Disconnect();
+        }
+
+        [TestMethod]
+        public void DisposeShouldNotThrowException()
+        {
+            _session.Dispose();
+        }
+
         [TestMethod]
         public void IsConnectedShouldReturnFalse()
         {

+ 44 - 12
src/Renci.SshNet.Tests/Classes/SessionTest_SocketConnected_BadPacketAndDispose.cs

@@ -1,10 +1,10 @@
 using System;
 using System.Net;
 using System.Net.Sockets;
-using System.Text;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using Moq;
 using Renci.SshNet.Common;
+using Renci.SshNet.Connection;
 using Renci.SshNet.Messages.Transport;
 using Renci.SshNet.Tests.Common;
 
@@ -14,12 +14,17 @@ namespace Renci.SshNet.Tests.Classes
     public class SessionTest_SocketConnected_BadPacketAndDispose
     {
         private Mock<IServiceFactory> _serviceFactoryMock;
+        private Mock<ISocketFactory> _socketFactoryMock;
+        private Mock<IConnector> _connectorMock;
+        private Mock<IProtocolVersionExchange> _protocolVersionExchangeMock;
         private ConnectionInfo _connectionInfo;
         private Session _session;
         private AsyncSocketListener _serverListener;
         private IPEndPoint _serverEndPoint;
         private Socket _serverSocket;
+        private Socket _clientSocket;
         private SshConnectionException _actualException;
+        private SocketFactory _socketFactory;
 
         [TestInitialize]
         public void Setup()
@@ -37,7 +42,15 @@ namespace Renci.SshNet.Tests.Classes
             }
         }
 
-        protected void Arrange()
+        protected void CreateMocks()
+        {
+            _serviceFactoryMock = new Mock<IServiceFactory>(MockBehavior.Strict);
+            _socketFactoryMock = new Mock<ISocketFactory>(MockBehavior.Strict);
+            _connectorMock = new Mock<IConnector>(MockBehavior.Strict);
+            _protocolVersionExchangeMock = new Mock<IProtocolVersionExchange>(MockBehavior.Strict);
+        }
+
+        protected void SetupData()
         {
             _serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
             _connectionInfo = new ConnectionInfo(
@@ -47,32 +60,51 @@ namespace Renci.SshNet.Tests.Classes
                 new PasswordAuthenticationMethod("user", "password"));
             _connectionInfo.Timeout = TimeSpan.FromMilliseconds(200);
             _actualException = null;
-
-            _serviceFactoryMock = new Mock<IServiceFactory>(MockBehavior.Strict);
+            _socketFactory = new SocketFactory();
 
             _serverListener = new AsyncSocketListener(_serverEndPoint);
             _serverListener.Connected += (socket) =>
                 {
                     _serverSocket = socket;
 
-                    socket.Send(Encoding.ASCII.GetBytes("\r\n"));
-                    socket.Send(Encoding.ASCII.GetBytes("WELCOME banner\r\n"));
-                    socket.Send(Encoding.ASCII.GetBytes("SSH-2.0-SshStub\r\n"));
-                };
-            _serverListener.BytesReceived += (received, socket) =>
-                {
-                    var badPacket = new byte[] {0x0a, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05};
+                    // Since we're mocking the protocol version exchange, we can immediately send the bad
+                    // packet upon establishing the connection
+
+                    var badPacket = new byte[] { 0x0a, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05 };
                     _serverSocket.Send(badPacket, 0, badPacket.Length, SocketFlags.None);
                     _serverSocket.Shutdown(SocketShutdown.Send);
                 };
             _serverListener.Start();
+
+            _session = new Session(_connectionInfo, _serviceFactoryMock.Object, _socketFactoryMock.Object);
+
+            _clientSocket = new DirectConnector(_socketFactory).Connect(_connectionInfo);
+        }
+
+        protected void SetupMocks()
+        {
+            _serviceFactoryMock.Setup(p => p.CreateConnector(_connectionInfo, _socketFactoryMock.Object))
+                               .Returns(_connectorMock.Object);
+            _connectorMock.Setup(p => p.Connect(_connectionInfo))
+                          .Returns(_clientSocket);
+            _serviceFactoryMock.Setup(p => p.CreateProtocolVersionExchange())
+                               .Returns(_protocolVersionExchangeMock.Object);
+            _protocolVersionExchangeMock.Setup(p => p.Start(_session.ClientVersion, _clientSocket, _connectionInfo.Timeout))
+                                        .Returns(new SshIdentification("2.0", "XXX"));
+        }
+
+        protected void Arrange()
+        {
+            CreateMocks();
+            SetupData();
+            SetupMocks();
         }
 
         protected virtual void Act()
         {
             try
             {
-                using (_session = new Session(_connectionInfo, _serviceFactoryMock.Object))
+
                 {
                     _session.Connect();
                 }

+ 2 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileReaderTestBase.cs

@@ -38,7 +38,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp
 
         protected static SftpFileAttributes CreateSftpFileAttributes(long size)
         {
-            return new SftpFileAttributes(default(DateTime), default(DateTime), size, default(int), default(int), default(uint), null);
+            var utcDefault = DateTime.SpecifyKind(default(DateTime), DateTimeKind.Utc);
+            return new SftpFileAttributes(utcDefault, utcDefault, size, default(int), default(int), default(uint), null);
         }
 
         protected static byte[] CreateByteArray(Random random, int length)

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanRead_Closed_FileAccessRead.cs

@@ -24,7 +24,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             var random = new Random();
             _path = random.Next().ToString();
             _handle = GenerateRandom(3, random);
-            _bufferSize = (uint) random.Next(0, 1000);
+            _bufferSize = (uint) random.Next(1, 1000);
             _readBufferSize = (uint) random.Next(0, 1000);
             _writeBufferSize = (uint) random.Next(0, 1000);
         }

+ 2 - 2
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Ctor_FileModeAppend_FileAccessWrite.cs

@@ -34,8 +34,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _readBufferSize = (uint) _random.Next(5, 1000);
             _writeBufferSize = (uint) _random.Next(5, 1000);
             _handle = GenerateRandom(_random.Next(1, 10), _random);
-            _fileAttributes = new SftpFileAttributesBuilder().WithLastAccessTime(DateTime.Now.AddSeconds(_random.Next()))
-                                                             .WithLastWriteTime(DateTime.Now.AddSeconds(_random.Next()))
+            _fileAttributes = new SftpFileAttributesBuilder().WithLastAccessTime(DateTime.UtcNow.AddSeconds(_random.Next()))
+                                                             .WithLastWriteTime(DateTime.UtcNow.AddSeconds(_random.Next()))
                                                              .WithSize(_random.Next())
                                                              .WithUserId(_random.Next())
                                                              .WithGroupId(_random.Next())

+ 3 - 3
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthGreatherThanPosition.cs

@@ -53,8 +53,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _fileAttributes = new SftpFileAttributesBuilder().WithExtension("X", "ABC")
                                                              .WithExtension("V", "VValue")
                                                              .WithGroupId(random.Next())
-                                                             .WithLastAccessTime(DateTime.Now.AddSeconds(random.Next()))
-                                                             .WithLastWriteTime(DateTime.Now.AddSeconds(random.Next()))
+                                                             .WithLastAccessTime(DateTime.UtcNow.AddSeconds(random.Next()))
+                                                             .WithLastWriteTime(DateTime.UtcNow.AddSeconds(random.Next()))
                                                              .WithPermissions((uint) random.Next())
                                                              .WithSize(_length + 100)
                                                              .WithUserId(random.Next())
@@ -201,4 +201,4 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(4));
         }
     }
-}
+}

+ 2 - 2
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthLessThanPosition.cs

@@ -50,8 +50,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _fileAttributes = new SftpFileAttributesBuilder().WithExtension("X", "ABC")
                                                              .WithExtension("V", "VValue")
                                                              .WithGroupId(random.Next())
-                                                             .WithLastAccessTime(DateTime.Now.AddSeconds(random.Next()))
-                                                             .WithLastWriteTime(DateTime.Now.AddSeconds(random.Next()))
+                                                             .WithLastAccessTime(DateTime.UtcNow.AddSeconds(random.Next()))
+                                                             .WithLastWriteTime(DateTime.UtcNow.AddSeconds(random.Next()))
                                                              .WithPermissions((uint)random.Next())
                                                              .WithSize(_length + 100)
                                                              .WithUserId(random.Next())

+ 2 - 2
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthGreatherThanPosition.cs

@@ -54,8 +54,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _fileAttributes = new SftpFileAttributesBuilder().WithExtension("X", "ABC")
                                                              .WithExtension("V", "VValue")
                                                              .WithGroupId(random.Next())
-                                                             .WithLastAccessTime(DateTime.Now.AddSeconds(random.Next()))
-                                                             .WithLastWriteTime(DateTime.Now.AddSeconds(random.Next()))
+                                                             .WithLastAccessTime(DateTime.UtcNow.AddSeconds(random.Next()))
+                                                             .WithLastWriteTime(DateTime.UtcNow.AddSeconds(random.Next()))
                                                              .WithPermissions((uint)random.Next())
                                                              .WithSize(_length + 100)
                                                              .WithUserId(random.Next())

+ 2 - 2
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthLessThanPosition.cs

@@ -54,8 +54,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _fileAttributes = new SftpFileAttributesBuilder().WithExtension("X", "ABC")
                                                              .WithExtension("V", "VValue")
                                                              .WithGroupId(random.Next())
-                                                             .WithLastAccessTime(DateTime.Now.AddSeconds(random.Next()))
-                                                             .WithLastWriteTime(DateTime.Now.AddSeconds(random.Next()))
+                                                             .WithLastAccessTime(DateTime.UtcNow.AddSeconds(random.Next()))
+                                                             .WithLastWriteTime(DateTime.UtcNow.AddSeconds(random.Next()))
                                                              .WithPermissions((uint) random.Next())
                                                              .WithSize(_length + 100)
                                                              .WithUserId(random.Next())

部分文件因为文件数量过多而无法显示