From 60be75d85cca86d7a97e7f22845b75e121fefe4c Mon Sep 17 00:00:00 2001 From: drieseng Date: Tue, 29 Dec 2020 20:01:28 +0100 Subject: [PATCH] Complete refactoring of connection process, and greatly improve test coverage. --- .../Classes/BaseClientTestBase.cs | 35 ++ ...Test_Connect_OnConnectedThrowsException.cs | 61 ++- ...Connected_KeepAliveInterval_NegativeOne.cs | 83 ++-- ...nected_KeepAliveInterval_NotNegativeOne.cs | 81 ++-- ...Connected_KeepAlivesNotSentConcurrently.cs | 89 ++-- .../BaseClientTest_Disconnected_Connect.cs | 107 +++++ ...nected_KeepAliveInterval_NotNegativeOne.cs | 77 ++-- ...nected_KeepAliveInterval_NotNegativeOne.cs | 55 +-- .../Connection/DirectConnectorTestBase.cs | 44 ++ ...rTest_Connect_ConnectionRefusedByServer.cs | 103 +++++ ...nnectorTest_Connect_ConnectionSucceeded.cs | 118 +++++ ...ctConnectorTest_Connect_HostNameInvalid.cs | 42 ++ ...rTest_Connect_TimeoutConnectingToServer.cs | 108 +++++ .../Connection/HttpConnectorTestBase.cs | 35 ++ ...orTest_Connect_ConnectionToProxyRefused.cs | 111 +++++ ...yClosesConnectionBeforeStatusLineIsSent.cs | 115 +++++ ...pConnectorTest_Connect_ProxyHostInvalid.cs | 49 +++ ...nectorTest_Connect_ProxyPasswordIsEmpty.cs | 131 ++++++ ...nnectorTest_Connect_ProxyPasswordIsNull.cs | 131 ++++++ ...oxyResponseDoesNotContainHttpStatusLine.cs | 120 ++++++ ...seStatusIs200_ExtraTextBeforeStatusLine.cs | 135 ++++++ ...xyResponseStatusIs200_HeadersAndContent.cs | 135 ++++++ ...ct_ProxyResponseStatusIs200_OnlyHeaders.cs | 133 ++++++ ...est_Connect_ProxyResponseStatusIsNot200.cs | 120 ++++++ ...nectorTest_Connect_ProxyUserNameIsEmpty.cs | 132 ++++++ ...nnect_ProxyUserNameIsNotNullAndNotEmpty.cs | 128 ++++++ ...nnectorTest_Connect_ProxyUserNameIsNull.cs | 132 ++++++ ...orTest_Connect_TimeoutConnectingToProxy.cs | 115 +++++ ...rTest_Connect_TimeoutReadingHttpContent.cs | 169 ++++++++ ...orTest_Connect_TimeoutReadingStatusLine.cs | 139 ++++++ ...changeTest_ServerResponseValid_Comments.cs | 12 +- .../Connection/Socks4ConnectorTestBase.cs | 49 +++ ...rTest_Connect_ConnectionRejectedByProxy.cs | 134 ++++++ ...nnectorTest_Connect_ConnectionSucceeded.cs | 166 +++++++ ...orTest_Connect_ConnectionToProxyRefused.cs | 102 +++++ ...orTest_Connect_TimeoutConnectingToProxy.cs | 105 +++++ ...onnect_TimeoutReadingDestinationAddress.cs | 143 ++++++ ...torTest_Connect_TimeoutReadingReplyCode.cs | 139 ++++++ ...Test_Connect_TimeoutReadingReplyVersion.cs | 131 ++++++ .../Connection/Socks5ConnectorTestBase.cs | 68 +++ ...orTest_Connect_ConnectionToProxyRefused.cs | 103 +++++ ...ct_NoAuthentication_ConnectionSucceeded.cs | 206 +++++++++ ...Connect_ProxySocksVersionIsNotSupported.cs | 123 ++++++ ...orTest_Connect_TimeoutConnectingToProxy.cs | 107 +++++ ...wordAuthentication_AuthenticationFailed.cs | 177 ++++++++ ...swordAuthentication_ConnectionSucceeded.cs | 220 ++++++++++ ...entication_PasswordExceedsMaximumLength.cs | 149 +++++++ ...entication_UserNameExceedsMaximumLength.cs | 149 +++++++ .../Classes/NetConfClientTestBase.cs | 17 + ...st_Connect_NetConfSessionConnectFailure.cs | 40 +- .../NetConfClientTest_Dispose_Connected.cs | 72 ++-- .../NetConfClientTest_Dispose_Disconnected.cs | 42 +- .../NetConfClientTest_Dispose_Disposed.cs | 54 +-- .../NetConfClientTest_Finalize_Connected.cs | 69 +-- .../Classes/ScpClientTestBase.cs | 33 +- ...rectoryInfo_SendExecRequestReturnsFalse.cs | 5 +- ...AndFileInfo_SendExecRequestReturnsFalse.cs | 5 +- ...thAndStream_SendExecRequestReturnsFalse.cs | 5 +- ...InfoAndPath_SendExecRequestReturnsFalse.cs | 5 +- ...InfoAndPath_SendExecRequestReturnsFalse.cs | 5 +- ...ientTest_Upload_FileInfoAndPath_Success.cs | 5 +- ...reamAndPath_SendExecRequestReturnsFalse.cs | 5 +- .../ServiceFactoryTest_CreateConnector.cs | 59 ++- .../Classes/SessionTest.HttpProxy.cs | 280 ------------ src/Renci.SshNet.Tests/Classes/SessionTest.cs | 406 +----------------- .../Classes/SessionTestBase.cs | 35 ++ .../SessionTest_ConnectToServerFails.cs | 265 ++++++++++++ .../Classes/SessionTest_Connected.cs | 11 +- .../Classes/SessionTest_ConnectedBase.cs | 88 ++-- .../SessionTest_Connected_Disconnect.cs | 10 +- ...alRequestMessageAfterAuthenticationRace.cs | 3 +- ...Connected_ServerAndClientDisconnectRace.cs | 108 +++-- ..._Connected_ServerSendsDisconnectMessage.cs | 3 +- ...endsDisconnectMessageAndShutsDownSocket.cs | 3 +- ...ected_ServerSendsUnsupportedMessageType.cs | 3 +- .../Classes/SessionTest_NotConnected.cs | 31 +- ...est_SocketConnected_BadPacketAndDispose.cs | 34 +- .../Classes/SftpClientTestBase.cs | 19 + ...tTest_Connect_SftpSessionConnectFailure.cs | 49 +-- .../SftpClientTest_Dispose_Connected.cs | 52 ++- .../SftpClientTest_Dispose_Disconnected.cs | 47 +- .../SftpClientTest_Dispose_Disposed.cs | 52 ++- .../SftpClientTest_Finalize_Connected.cs | 57 +-- ...AndBufferSizeAndTerminalModes_Connected.cs | 35 +- ...ndWidthAndHeightAndBufferSize_Connected.cs | 34 +- ...entTest_Disconnect_ForwardedPortStarted.cs | 39 +- .../SshClientTest_Dispose_Connected.cs | 48 ++- .../SshClientTest_Dispose_Disconnected.cs | 48 ++- .../Classes/SshClientTest_Dispose_Disposed.cs | 48 ++- ...ClientTest_Dispose_ForwardedPortStarted.cs | 40 +- .../Common/AsyncSocketListener.cs | 82 +++- .../Common/TripleATestBase.cs | 29 ++ src/Renci.SshNet/BaseClient.cs | 2 +- src/Renci.SshNet/Connection/ConnectorBase.cs | 64 ++- .../Connection/DirectConnector.cs | 4 + src/Renci.SshNet/Connection/HttpConnector.cs | 59 ++- src/Renci.SshNet/Connection/ISocketFactory.cs | 9 + src/Renci.SshNet/Connection/SocketFactory.cs | 12 + .../Connection/Socks4Connector.cs | 62 ++- .../Connection/Socks5Connector.cs | 41 +- src/Renci.SshNet/IServiceFactory.cs | 25 +- src/Renci.SshNet/ServiceFactory.cs | 37 +- src/Renci.SshNet/Session.cs | 13 +- 103 files changed, 6449 insertions(+), 1575 deletions(-) create mode 100644 src/Renci.SshNet.Tests/Classes/BaseClientTestBase.cs create mode 100644 src/Renci.SshNet.Tests/Classes/BaseClientTest_Disconnected_Connect.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTestBase.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_ConnectionRefusedByServer.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_ConnectionSucceeded.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_HostNameInvalid.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_TimeoutConnectingToServer.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTestBase.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ConnectionToProxyRefused.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyClosesConnectionBeforeStatusLineIsSent.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyHostInvalid.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyPasswordIsEmpty.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyPasswordIsNull.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseDoesNotContainHttpStatusLine.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseStatusIs200_ExtraTextBeforeStatusLine.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseStatusIs200_HeadersAndContent.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseStatusIs200_OnlyHeaders.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseStatusIsNot200.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyUserNameIsEmpty.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyUserNameIsNotNullAndNotEmpty.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyUserNameIsNull.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_TimeoutConnectingToProxy.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_TimeoutReadingHttpContent.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_TimeoutReadingStatusLine.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTestBase.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_ConnectionRejectedByProxy.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_ConnectionSucceeded.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_ConnectionToProxyRefused.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutConnectingToProxy.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingDestinationAddress.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingReplyCode.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingReplyVersion.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTestBase.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_ConnectionToProxyRefused.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_NoAuthentication_ConnectionSucceeded.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_ProxySocksVersionIsNotSupported.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_TimeoutConnectingToProxy.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_AuthenticationFailed.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_ConnectionSucceeded.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_PasswordExceedsMaximumLength.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_UserNameExceedsMaximumLength.cs create mode 100644 src/Renci.SshNet.Tests/Classes/NetConfClientTestBase.cs create mode 100644 src/Renci.SshNet.Tests/Classes/SessionTestBase.cs create mode 100644 src/Renci.SshNet.Tests/Classes/SessionTest_ConnectToServerFails.cs create mode 100644 src/Renci.SshNet.Tests/Classes/SftpClientTestBase.cs create mode 100644 src/Renci.SshNet.Tests/Common/TripleATestBase.cs create mode 100644 src/Renci.SshNet/Connection/ISocketFactory.cs create mode 100644 src/Renci.SshNet/Connection/SocketFactory.cs diff --git a/src/Renci.SshNet.Tests/Classes/BaseClientTestBase.cs b/src/Renci.SshNet.Tests/Classes/BaseClientTestBase.cs new file mode 100644 index 00000000..2cce2e91 --- /dev/null +++ b/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 _serviceFactoryMock { get; private set; } + internal Mock _socketFactoryMock { get; private set; } + internal Mock _sessionMock { get; private set; } + + protected virtual void CreateMocks() + { + _serviceFactoryMock = new Mock(MockBehavior.Strict); + _socketFactoryMock = new Mock(MockBehavior.Strict); + _sessionMock = new Mock(MockBehavior.Strict); + } + + protected virtual void SetupData() + { + } + + protected virtual void SetupMocks() + { + } + + protected override void Arrange() + { + CreateMocks(); + SetupData(); + SetupMocks(); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connect_OnConnectedThrowsException.cs b/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connect_OnConnectedThrowsException.cs index 792ab89d..e21f516d 100644 --- a/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connect_OnConnectedThrowsException.cs +++ b/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 _serviceFactoryMock; - private Mock _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() + protected override void Arrange() { - _connectionInfo = new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "pwd")); - _onConnectException = new ApplicationException(); - } - - private void CreateMocks() - { - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(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] diff --git a/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAliveInterval_NegativeOne.cs b/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAliveInterval_NegativeOne.cs index fb58e1f4..e3b10c87 100644 --- a/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAliveInterval_NegativeOne.cs +++ b/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAliveInterval_NegativeOne.cs @@ -2,29 +2,49 @@ 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 _serviceFactoryMock; - private Mock _sessionMock; private BaseClient _client; private ConnectionInfo _connectionInfo; private TimeSpan _keepAliveInterval; private int _keepAliveCount; - [TestInitialize] - public void Setup() + protected override void SetupData() { - Arrange(); - Act(); + _connectionInfo = new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "pwd")); + _keepAliveInterval = TimeSpan.FromMilliseconds(100d); + _keepAliveCount = 0; } - [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.IsConnected).Returns(true); + _sessionMock.Setup(p => p.TrySendMessage(It.IsAny())) + .Returns(true) + .Callback(() => Interlocked.Increment(ref _keepAliveCount)); + } + + protected override void Arrange() + { + base.Arrange(); + + _client = new MyClient(_connectionInfo, false, _serviceFactoryMock.Object); + _client.Connect(); + _client.KeepAliveInterval = _keepAliveInterval; + } + + protected override void TearDown() { if (_client != null) { @@ -34,42 +54,7 @@ namespace Renci.SshNet.Tests.Classes } } - private void SetupData() - { - _connectionInfo = new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "pwd")); - _keepAliveInterval = TimeSpan.FromMilliseconds(100d); - _keepAliveCount = 0; - } - - private void CreateMocks() - { - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - } - - private void SetupMocks() - { - _serviceFactoryMock.Setup(p => p.CreateSession(_connectionInfo)) - .Returns(_sessionMock.Object); - _sessionMock.Setup(p => p.Connect()); - _sessionMock.Setup(p => p.IsConnected).Returns(true); - _sessionMock.Setup(p => p.TrySendMessage(It.IsAny())) - .Returns(true) - .Callback(() => Interlocked.Increment(ref _keepAliveCount)); - } - - protected void Arrange() - { - SetupData(); - CreateMocks(); - SetupMocks(); - - _client = new MyClient(_connectionInfo, false, _serviceFactoryMock.Object); - _client.Connect(); - _client.KeepAliveInterval = _keepAliveInterval; - } - - protected void Act() + 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] diff --git a/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAliveInterval_NotNegativeOne.cs b/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAliveInterval_NotNegativeOne.cs index a3c807bd..26a8da00 100644 --- a/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAliveInterval_NotNegativeOne.cs +++ b/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAliveInterval_NotNegativeOne.cs @@ -7,24 +7,42 @@ 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 _serviceFactoryMock; - private Mock _sessionMock; private BaseClient _client; private ConnectionInfo _connectionInfo; private TimeSpan _keepAliveInterval; private int _keepAliveCount; - [TestInitialize] - public void Setup() + protected override void SetupData() { - Arrange(); - Act(); + _connectionInfo = new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "pwd")); + _keepAliveInterval = TimeSpan.FromMilliseconds(50d); + _keepAliveCount = 0; } - [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.IsConnected).Returns(true); + _sessionMock.Setup(p => p.TrySendMessage(It.IsAny())) + .Returns(true) + .Callback(() => Interlocked.Increment(ref _keepAliveCount)); + } + + protected override void Arrange() + { + base.Arrange(); + + _client = new MyClient(_connectionInfo, false, _serviceFactoryMock.Object); + _client.Connect(); + } + + protected override void TearDown() { if (_client != null) { @@ -34,41 +52,7 @@ namespace Renci.SshNet.Tests.Classes } } - private void SetupData() - { - _connectionInfo = new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "pwd")); - _keepAliveInterval = TimeSpan.FromMilliseconds(50d); - _keepAliveCount = 0; - } - - private void CreateMocks() - { - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - } - - private void SetupMocks() - { - _serviceFactoryMock.Setup(p => p.CreateSession(_connectionInfo)) - .Returns(_sessionMock.Object); - _sessionMock.Setup(p => p.Connect()); - _sessionMock.Setup(p => p.IsConnected).Returns(true); - _sessionMock.Setup(p => p.TrySendMessage(It.IsAny())) - .Returns(true) - .Callback(() => Interlocked.Increment(ref _keepAliveCount)); - } - - protected void Arrange() - { - SetupData(); - CreateMocks(); - SetupMocks(); - - _client = new MyClient(_connectionInfo, false, _serviceFactoryMock.Object); - _client.Connect(); - } - - protected void Act() + 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] diff --git a/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAlivesNotSentConcurrently.cs b/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAlivesNotSentConcurrently.cs index 4430b657..25d6ec7c 100644 --- a/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAlivesNotSentConcurrently.cs +++ b/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAlivesNotSentConcurrently.cs @@ -7,24 +7,53 @@ using Renci.SshNet.Messages.Transport; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class BaseClientTest_Connected_KeepAlivesNotSentConcurrently + public class BaseClientTest_Connected_KeepAlivesNotSentConcurrently : BaseClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; private MockSequence _mockSequence; private BaseClient _client; private ConnectionInfo _connectionInfo; private ManualResetEvent _keepAliveSent; - [TestInitialize] - public void Setup() + protected override void SetupData() { - Arrange(); - Act(); + _connectionInfo = new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "pwd")); + _keepAliveSent = new ManualResetEvent(false); } - [TestCleanup] - public void Cleanup() + protected override void SetupMocks() + { + _mockSequence = new MockSequence(); + + _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())) + .Returns(true) + .Callback(() => + { + Thread.Sleep(300); + _keepAliveSent.Set(); + }); + } + + protected override void Arrange() + { + base.Arrange(); + + _client = new MyClient(_connectionInfo, false, _serviceFactoryMock.Object) + { + KeepAliveInterval = TimeSpan.FromMilliseconds(50d) + }; + _client.Connect(); + } + + protected override void TearDown() { if (_client != null) { @@ -34,47 +63,7 @@ namespace Renci.SshNet.Tests.Classes } } - private void SetupData() - { - _connectionInfo = new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "pwd")); - _keepAliveSent = new ManualResetEvent(false); - } - - private void CreateMocks() - { - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - } - - private 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())) - .Returns(true) - .Callback(() => - { - Thread.Sleep(300); - _keepAliveSent.Set(); - }); - } - - protected void Arrange() - { - SetupData(); - CreateMocks(); - SetupMocks(); - - _client = new MyClient(_connectionInfo, false, _serviceFactoryMock.Object) - { - KeepAliveInterval = TimeSpan.FromMilliseconds(50d) - }; - _client.Connect(); - } - - protected void Act() + protected override void Act() { // should keep-alive message be sent concurrently, then multiple keep-alive // message would be sent during this sleep period diff --git a/src/Renci.SshNet.Tests/Classes/BaseClientTest_Disconnected_Connect.cs b/src/Renci.SshNet.Tests/Classes/BaseClientTest_Disconnected_Connect.cs new file mode 100644 index 00000000..51cd2d81 --- /dev/null +++ b/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 _socketFactory2Mock; + private Mock _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(MockBehavior.Strict); + _session2Mock = new Mock(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) + { + } + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/BaseClientTest_Disconnected_KeepAliveInterval_NotNegativeOne.cs b/src/Renci.SshNet.Tests/Classes/BaseClientTest_Disconnected_KeepAliveInterval_NotNegativeOne.cs index 588bfd83..1ca4b19d 100644 --- a/src/Renci.SshNet.Tests/Classes/BaseClientTest_Disconnected_KeepAliveInterval_NotNegativeOne.cs +++ b/src/Renci.SshNet.Tests/Classes/BaseClientTest_Disconnected_KeepAliveInterval_NotNegativeOne.cs @@ -7,23 +7,39 @@ 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 _serviceFactoryMock; - private Mock _sessionMock; private BaseClient _client; private ConnectionInfo _connectionInfo; private TimeSpan _keepAliveInterval; - [TestInitialize] - public void Setup() + protected override void SetupData() { - Arrange(); - Act(); + _connectionInfo = new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "pwd")); + _keepAliveInterval = TimeSpan.FromMilliseconds(50d); } - [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.IsConnected).Returns(false); + _sessionMock.Setup(p => p.TrySendMessage(It.IsAny())) + .Returns(true); + } + + protected override void Arrange() + { + base.Arrange(); + + _client = new MyClient(_connectionInfo, false, _serviceFactoryMock.Object); + _client.Connect(); + } + + protected override void TearDown() { if (_client != null) { @@ -33,39 +49,7 @@ namespace Renci.SshNet.Tests.Classes } } - private void SetupData() - { - _connectionInfo = new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "pwd")); - _keepAliveInterval = TimeSpan.FromMilliseconds(50d); - } - - private void CreateMocks() - { - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - } - - private void SetupMocks() - { - _serviceFactoryMock.Setup(p => p.CreateSession(_connectionInfo)) - .Returns(_sessionMock.Object); - _sessionMock.Setup(p => p.Connect()); - _sessionMock.Setup(p => p.IsConnected).Returns(false); - _sessionMock.Setup(p => p.TrySendMessage(It.IsAny())) - .Returns(true); - } - - protected void Arrange() - { - SetupData(); - CreateMocks(); - SetupMocks(); - - _client = new MyClient(_connectionInfo, false, _serviceFactoryMock.Object); - _client.Connect(); - } - - protected void Act() + 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] diff --git a/src/Renci.SshNet.Tests/Classes/BaseClientTest_NotConnected_KeepAliveInterval_NotNegativeOne.cs b/src/Renci.SshNet.Tests/Classes/BaseClientTest_NotConnected_KeepAliveInterval_NotNegativeOne.cs index dfaa8b27..0f449c82 100644 --- a/src/Renci.SshNet.Tests/Classes/BaseClientTest_NotConnected_KeepAliveInterval_NotNegativeOne.cs +++ b/src/Renci.SshNet.Tests/Classes/BaseClientTest_NotConnected_KeepAliveInterval_NotNegativeOne.cs @@ -7,24 +7,28 @@ 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 _serviceFactoryMock; - private Mock _sessionMock; private BaseClient _client; private ConnectionInfo _connectionInfo; private TimeSpan _keepAliveInterval; private int _keepAliveCount; - [TestInitialize] - public void Setup() + protected override void SetupData() { - Arrange(); - Act(); + _connectionInfo = new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "pwd")); + _keepAliveInterval = TimeSpan.FromMilliseconds(100d); + _keepAliveCount = 0; } - [TestCleanup] - public void Cleanup() + protected override void Arrange() + { + base.Arrange(); + + _client = new MyClient(_connectionInfo, false, _serviceFactoryMock.Object); + } + + protected override void TearDown() { if (_client != null) { @@ -34,33 +38,7 @@ namespace Renci.SshNet.Tests.Classes } } - private void SetupData() - { - _connectionInfo = new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "pwd")); - _keepAliveInterval = TimeSpan.FromMilliseconds(100d); - _keepAliveCount = 0; - } - - private void CreateMocks() - { - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - } - - private static void SetupMocks() - { - } - - protected void Arrange() - { - SetupData(); - CreateMocks(); - SetupMocks(); - - _client = new MyClient(_connectionInfo, false, _serviceFactoryMock.Object); - } - - 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())) .Returns(true) diff --git a/src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTestBase.cs b/src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTestBase.cs new file mode 100644 index 00000000..edb14680 --- /dev/null +++ b/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 SocketFactoryMock { get; private set; } + internal DirectConnector Connector { get; private set; } + internal SocketFactory SocketFactory { get; private set; } + + protected virtual void CreateMocks() + { + SocketFactoryMock = new Mock(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")); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_ConnectionRefusedByServer.cs b/src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_ConnectionRefusedByServer.cs new file mode 100644 index 00000000..40fd93b5 --- /dev/null +++ b/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()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_ConnectionSucceeded.cs b/src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_ConnectionSucceeded.cs new file mode 100644 index 00000000..5741c186 --- /dev/null +++ b/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()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_HostNameInvalid.cs b/src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_HostNameInvalid.cs new file mode 100644 index 00000000..05a69e94 --- /dev/null +++ b/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); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_TimeoutConnectingToServer.cs b/src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_TimeoutConnectingToServer.cs new file mode 100644 index 00000000..b8e4246a --- /dev/null +++ b/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()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTestBase.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTestBase.cs new file mode 100644 index 00000000..82cfe0c2 --- /dev/null +++ b/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 SocketFactoryMock { get; private set; } + internal HttpConnector Connector { get; private set; } + internal SocketFactory SocketFactory { get; private set; } + + protected virtual void CreateMocks() + { + SocketFactoryMock = new Mock(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(); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ConnectionToProxyRefused.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ConnectionToProxyRefused.cs new file mode 100644 index 00000000..5c790b4c --- /dev/null +++ b/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()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyClosesConnectionBeforeStatusLineIsSent.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyClosesConnectionBeforeStatusLineIsSent.cs new file mode 100644 index 00000000..72bdca90 --- /dev/null +++ b/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()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyHostInvalid.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyHostInvalid.cs new file mode 100644 index 00000000..5843947d --- /dev/null +++ b/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); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyPasswordIsEmpty.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyPasswordIsEmpty.cs new file mode 100644 index 00000000..c94933b2 --- /dev/null +++ b/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 _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(); + _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()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyPasswordIsNull.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyPasswordIsNull.cs new file mode 100644 index 00000000..277a6f29 --- /dev/null +++ b/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 _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(); + _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()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseDoesNotContainHttpStatusLine.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseDoesNotContainHttpStatusLine.cs new file mode 100644 index 00000000..b9f90019 --- /dev/null +++ b/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 _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(); + _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()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseStatusIs200_ExtraTextBeforeStatusLine.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseStatusIs200_ExtraTextBeforeStatusLine.cs new file mode 100644 index 00000000..70491330 --- /dev/null +++ b/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 _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(); + _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()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseStatusIs200_HeadersAndContent.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseStatusIs200_HeadersAndContent.cs new file mode 100644 index 00000000..dc0bba59 --- /dev/null +++ b/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 _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(); + _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()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseStatusIs200_OnlyHeaders.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseStatusIs200_OnlyHeaders.cs new file mode 100644 index 00000000..235b4dc0 --- /dev/null +++ b/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 _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(); + _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()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseStatusIsNot200.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseStatusIsNot200.cs new file mode 100644 index 00000000..9eb0094b --- /dev/null +++ b/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 _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(); + _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()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyUserNameIsEmpty.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyUserNameIsEmpty.cs new file mode 100644 index 00000000..63bb1baa --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyUserNameIsEmpty.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_ProxyUserNameIsEmpty : HttpConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private AsyncSocketListener _proxyServer; + private bool _disconnected; + private Socket _clientSocket; + private List _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(); + _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()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyUserNameIsNotNullAndNotEmpty.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyUserNameIsNotNullAndNotEmpty.cs new file mode 100644 index 00000000..dad0e74b --- /dev/null +++ b/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 _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(); + _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()); + } + } +} + diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyUserNameIsNull.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyUserNameIsNull.cs new file mode 100644 index 00000000..3de1bddb --- /dev/null +++ b/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 _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(); + _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()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_TimeoutConnectingToProxy.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_TimeoutConnectingToProxy.cs new file mode 100644 index 00000000..1a65fec1 --- /dev/null +++ b/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()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_TimeoutReadingHttpContent.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_TimeoutReadingHttpContent.cs new file mode 100644 index 00000000..29ea435d --- /dev/null +++ b/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 _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(); + _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()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_TimeoutReadingStatusLine.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_TimeoutReadingStatusLine.cs new file mode 100644 index 00000000..254262ba --- /dev/null +++ b/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()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseValid_Comments.cs b/src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseValid_Comments.cs index a71a1edf..d4aa0c7c 100644 --- a/src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseValid_Comments.cs +++ b/src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseValid_Comments.cs @@ -55,16 +55,16 @@ namespace Renci.SshNet.Tests.Classes _timeout = TimeSpan.FromSeconds(5); _serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122); _dataReceivedByServer = new List(); - _serverIdentification = Encoding.UTF8.GetBytes("Welcome stranger!\r\n\r\nSSH-ABC2.0-OurSSHAppliance-1.4.7 Use at own risk.\uD55C\r\n!"); + _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.Both); - }; + { + _dataReceivedByServer.AddRange(bytes); + socket.Send(_serverIdentification); + socket.Shutdown(SocketShutdown.Both); + }; _server.Disconnected += (socket) => _clientDisconnected = true; _client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTestBase.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTestBase.cs new file mode 100644 index 00000000..50f62256 --- /dev/null +++ b/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 SocketFactoryMock { get; private set; } + internal Socks4Connector Connector { get; private set; } + internal SocketFactory SocketFactory { get; private set; } + + protected virtual void CreateMocks() + { + SocketFactoryMock = new Mock(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")); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_ConnectionRejectedByProxy.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_ConnectionRejectedByProxy.cs new file mode 100644 index 00000000..3ef993f2 --- /dev/null +++ b/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 _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(); + _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; + } + } + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_ConnectionSucceeded.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_ConnectionSucceeded.cs new file mode 100644 index 00000000..9f184942 --- /dev/null +++ b/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 _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(); + _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()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_ConnectionToProxyRefused.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_ConnectionToProxyRefused.cs new file mode 100644 index 00000000..85f2f669 --- /dev/null +++ b/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()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutConnectingToProxy.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutConnectingToProxy.cs new file mode 100644 index 00000000..4ce1560b --- /dev/null +++ b/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()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingDestinationAddress.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingDestinationAddress.cs new file mode 100644 index 00000000..92840220 --- /dev/null +++ b/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()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingReplyCode.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingReplyCode.cs new file mode 100644 index 00000000..bbc724ac --- /dev/null +++ b/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()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingReplyVersion.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingReplyVersion.cs new file mode 100644 index 00000000..d2fcf5c1 --- /dev/null +++ b/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()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTestBase.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTestBase.cs new file mode 100644 index 00000000..2d2f2cb9 --- /dev/null +++ b/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 SocketFactoryMock { get; private set; } + internal Socks5Connector Connector { get; private set; } + internal SocketFactory SocketFactory { get; private set; } + + protected virtual void CreateMocks() + { + SocketFactoryMock = new Mock(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(); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_ConnectionToProxyRefused.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_ConnectionToProxyRefused.cs new file mode 100644 index 00000000..997c18a1 --- /dev/null +++ b/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()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_NoAuthentication_ConnectionSucceeded.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_NoAuthentication_ConnectionSucceeded.cs new file mode 100644 index 00000000..7f4b108c --- /dev/null +++ b/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 _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(); + + _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()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_ProxySocksVersionIsNotSupported.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_ProxySocksVersionIsNotSupported.cs new file mode 100644 index 00000000..dc9a3a9e --- /dev/null +++ b/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; + } + } + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_TimeoutConnectingToProxy.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_TimeoutConnectingToProxy.cs new file mode 100644 index 00000000..4ac547b9 --- /dev/null +++ b/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()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_AuthenticationFailed.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_AuthenticationFailed.cs new file mode 100644 index 00000000..878636ba --- /dev/null +++ b/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 _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(); + + _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(); + + // + // 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()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_ConnectionSucceeded.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_ConnectionSucceeded.cs new file mode 100644 index 00000000..b94c40e8 --- /dev/null +++ b/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 _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(); + + _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(); + + // + // 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()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_PasswordExceedsMaximumLength.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_PasswordExceedsMaximumLength.cs new file mode 100644 index 00000000..61db86a4 --- /dev/null +++ b/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 _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(); + _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(); + + // + // 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()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_UserNameExceedsMaximumLength.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_UserNameExceedsMaximumLength.cs new file mode 100644 index 00000000..7a0ef5d4 --- /dev/null +++ b/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 _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(); + _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(); + + // + // 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()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/NetConfClientTestBase.cs b/src/Renci.SshNet.Tests/Classes/NetConfClientTestBase.cs new file mode 100644 index 00000000..ee8cb018 --- /dev/null +++ b/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 _netConfSessionMock { get; private set; } + + protected override void CreateMocks() + { + base.CreateMocks(); + + _netConfSessionMock = new Mock(MockBehavior.Strict); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Connect_NetConfSessionConnectFailure.cs b/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Connect_NetConfSessionConnectFailure.cs index 64ffb6ce..980c574a 100644 --- a/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Connect_NetConfSessionConnectFailure.cs +++ b/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 _serviceFactoryMock; - private Mock _sessionMock; - private Mock _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(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - _netConfSessionMock = new Mock(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 { diff --git a/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Dispose_Connected.cs b/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Dispose_Connected.cs index ad0cf531..ba86a7c9 100644 --- a/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Dispose_Connected.cs +++ b/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 _serviceFactoryMock; - private Mock _sessionMock; - private Mock _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(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - _netConfSessionMock = new Mock(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.CreateSession(_connectionInfo)) - .Returns(_sessionMock.Object); - _sessionMock.InSequence(sequence).Setup(p => p.Connect()); + .Setup(p => p.CreateSocketFactory()) + .Returns(_socketFactoryMock.Object); _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.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()); + } + + 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] diff --git a/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Dispose_Disconnected.cs b/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Dispose_Disconnected.cs index 24b495d7..8a4fbcd2 100644 --- a/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Dispose_Disconnected.cs +++ b/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 _serviceFactoryMock; - private Mock _sessionMock; - private Mock _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(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - _netConfSessionMock = new Mock(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.CreateSession(_connectionInfo)) - .Returns(_sessionMock.Object); + .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.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] diff --git a/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Dispose_Disposed.cs b/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Dispose_Disposed.cs index 7439ed75..9ed692a3 100644 --- a/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Dispose_Disposed.cs +++ b/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 _serviceFactoryMock; - private Mock _sessionMock; - private Mock _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(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - _netConfSessionMock = new Mock(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.CreateSession(_connectionInfo)) - .Returns(_sessionMock.Object); + .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.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] diff --git a/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Finalize_Connected.cs b/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Finalize_Connected.cs index fb17e353..44761e96 100644 --- a/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Finalize_Connected.cs +++ b/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 _serviceFactoryMock; - private Mock _sessionMock; - private Mock _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(MockBehavior.Loose); - _sessionMock = new Mock(MockBehavior.Loose); - _netConfSessionMock = new Mock(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.CreateSession(_connectionInfo)) - .Returns(_sessionMock.Object); - _sessionMock.InSequence(sequence).Setup(p => p.Connect()); + .Setup(p => p.CreateSocketFactory()) + .Returns(_socketFactoryMock.Object); _serviceFactoryMock.InSequence(sequence) - .Setup(p => p.CreateNetConfSession(_sessionMock.Object, _operationTimeout)) - .Returns(_netConfSessionMock.Object); - _netConfSessionMock.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()); + } + + 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); + } } } diff --git a/src/Renci.SshNet.Tests/Classes/ScpClientTestBase.cs b/src/Renci.SshNet.Tests/Classes/ScpClientTestBase.cs index 8626d7d0..26aa22e3 100644 --- a/src/Renci.SshNet.Tests/Classes/ScpClientTestBase.cs +++ b/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 _serviceFactoryMock; internal Mock _remotePathTransformationMock; - internal Mock _sessionMock; internal Mock _channelSessionMock; internal Mock _pipeStreamMock; - protected abstract void SetupData(); - - protected void CreateMocks() + protected override void CreateMocks() { - _serviceFactoryMock = new Mock(MockBehavior.Strict); + base.CreateMocks(); + _remotePathTransformationMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); _channelSessionMock = new Mock(MockBehavior.Strict); _pipeStreamMock = new Mock(MockBehavior.Strict); } - - protected abstract void SetupMocks(); - - protected virtual void Arrange() - { - SetupData(); - CreateMocks(); - SetupMocks(); - } - - [TestInitialize] - public void Initialize() - { - Arrange(); - Act(); - } - - protected abstract void Act(); } } diff --git a/src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndDirectoryInfo_SendExecRequestReturnsFalse.cs b/src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndDirectoryInfo_SendExecRequestReturnsFalse.cs index aa5d5cfb..17372ee6 100644 --- a/src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndDirectoryInfo_SendExecRequestReturnsFalse.cs +++ b/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); diff --git a/src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndFileInfo_SendExecRequestReturnsFalse.cs b/src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndFileInfo_SendExecRequestReturnsFalse.cs index 80165725..355f0ea8 100644 --- a/src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndFileInfo_SendExecRequestReturnsFalse.cs +++ b/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); diff --git a/src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndStream_SendExecRequestReturnsFalse.cs b/src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndStream_SendExecRequestReturnsFalse.cs index 309e4566..8aceeee4 100644 --- a/src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndStream_SendExecRequestReturnsFalse.cs +++ b/src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndStream_SendExecRequestReturnsFalse.cs @@ -47,7 +47,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); diff --git a/src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_DirectoryInfoAndPath_SendExecRequestReturnsFalse.cs b/src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_DirectoryInfoAndPath_SendExecRequestReturnsFalse.cs index 1c47aeae..dd907f86 100644 --- a/src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_DirectoryInfoAndPath_SendExecRequestReturnsFalse.cs +++ b/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); diff --git a/src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_SendExecRequestReturnsFalse.cs b/src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_SendExecRequestReturnsFalse.cs index 0c842a21..28ce4538 100644 --- a/src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_SendExecRequestReturnsFalse.cs +++ b/src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_SendExecRequestReturnsFalse.cs @@ -53,7 +53,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); diff --git a/src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_Success.cs b/src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_Success.cs index c04525e0..4a036c37 100644 --- a/src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_Success.cs +++ b/src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_Success.cs @@ -60,7 +60,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); diff --git a/src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_StreamAndPath_SendExecRequestReturnsFalse.cs b/src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_StreamAndPath_SendExecRequestReturnsFalse.cs index d2c6992b..4050d1cd 100644 --- a/src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_StreamAndPath_SendExecRequestReturnsFalse.cs +++ b/src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_StreamAndPath_SendExecRequestReturnsFalse.cs @@ -50,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); diff --git a/src/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateConnector.cs b/src/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateConnector.cs index 0e50bf8e..af2764b8 100644 --- a/src/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateConnector.cs +++ b/src/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateConnector.cs @@ -10,6 +10,7 @@ namespace Renci.SshNet.Tests.Classes { private ServiceFactory _serviceFactory; private Mock _connectionInfoMock; + private Mock _socketFactoryMock; [TestInitialize] public void Setup() @@ -25,7 +26,7 @@ namespace Renci.SshNet.Tests.Classes try { - _serviceFactory.CreateConnector(connectionInfo); + _serviceFactory.CreateConnector(connectionInfo, _socketFactoryMock.Object); Assert.Fail(); } catch (ArgumentNullException ex) @@ -35,16 +36,36 @@ namespace Renci.SshNet.Tests.Classes } } + [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); + 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); } @@ -53,11 +74,14 @@ namespace Renci.SshNet.Tests.Classes { _connectionInfoMock.Setup(p => p.ProxyType).Returns(ProxyTypes.None); - var actual = _serviceFactory.CreateConnector(_connectionInfoMock.Object); + 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); } @@ -66,11 +90,14 @@ namespace Renci.SshNet.Tests.Classes { _connectionInfoMock.Setup(p => p.ProxyType).Returns(ProxyTypes.Socks4); - var actual = _serviceFactory.CreateConnector(_connectionInfoMock.Object); + 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); } @@ -79,11 +106,33 @@ namespace Renci.SshNet.Tests.Classes { _connectionInfoMock.Setup(p => p.ProxyType).Returns(ProxyTypes.Socks5); - var actual = _serviceFactory.CreateConnector(_connectionInfoMock.Object); + 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.Once); } } diff --git a/src/Renci.SshNet.Tests/Classes/SessionTest.HttpProxy.cs b/src/Renci.SshNet.Tests/Classes/SessionTest.HttpProxy.cs index 4fe0bd08..03ce2491 100644 --- a/src/Renci.SshNet.Tests/Classes/SessionTest.HttpProxy.cs +++ b/src/Renci.SshNet.Tests/Classes/SessionTest.HttpProxy.cs @@ -11,286 +11,6 @@ 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(); - - var connectionInfo = CreateConnectionInfoWithHttpProxy(proxyEndPoint, serverEndPoint, "anon"); - - using (var session = new Session(connectionInfo, _serviceFactoryMock.Object)) - { - _serviceFactoryMock.Setup(p => p.CreateConnector(connectionInfo)) - .Returns(_connectorMock.Object); - _connectorMock.Setup(p => p.Connect(connectionInfo)) - .Returns(c => new HttpConnector().Connect(c)); - - 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(); - - var connectionInfo = CreateConnectionInfoWithHttpProxy(proxyEndPoint, serverEndPoint, "anon"); - - using (var session = new Session(connectionInfo, _serviceFactoryMock.Object)) - { - _serviceFactoryMock.Setup(p => p.CreateConnector(connectionInfo)) - .Returns(_connectorMock.Object); - _connectorMock.Setup(p => p.Connect(connectionInfo)) - .Returns(c => new HttpConnector().Connect(c)); - - 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(); - - var connectionInfo = CreateConnectionInfoWithHttpProxy(proxyEndPoint, serverEndPoint, "anon"); - - using (var session = new Session(connectionInfo, _serviceFactoryMock.Object)) - { - _serviceFactoryMock.Setup(p => p.CreateConnector(connectionInfo)) - .Returns(_connectorMock.Object); - _connectorMock.Setup(p => p.Connect(connectionInfo)) - .Returns(c => new HttpConnector().Connect(c)); - - 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(); - - var connectionInfo = CreateConnectionInfoWithHttpProxy(proxyEndPoint, serverEndPoint, "anon"); - - using (var session = new Session(connectionInfo, _serviceFactoryMock.Object)) - { - _serviceFactoryMock.Setup(p => p.CreateConnector(connectionInfo)) - .Returns(_connectorMock.Object); - _connectorMock.Setup(p => p.Connect(connectionInfo)) - .Returns(c => new HttpConnector().Connect(c)); - - 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(); - - var connectionInfo = CreateConnectionInfoWithHttpProxy(proxyEndPoint, serverEndPoint, "anon"); - - using (var session = new Session(connectionInfo, _serviceFactoryMock.Object)) - { - _serviceFactoryMock.Setup(p => p.CreateConnector(connectionInfo)) - .Returns(_connectorMock.Object); - _connectorMock.Setup(p => p.Connect(connectionInfo)) - .Returns(c => new HttpConnector().Connect(c)); - - 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 = CreateConnectionInfoWithHttpProxy(proxyEndPoint, serverEndPoint, "anon"); - - using (var session = new Session(connectionInfo, _serviceFactoryMock.Object)) - { - _serviceFactoryMock.Setup(p => p.CreateConnector(connectionInfo)) - .Returns(_connectorMock.Object); - _connectorMock.Setup(p => p.Connect(connectionInfo)) - .Returns(c => new HttpConnector().Connect(c)); - - 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 = CreateConnectionInfoWithHttpProxy(proxyEndPoint, serverEndPoint, string.Empty); - - using (var session = new Session(connectionInfo, _serviceFactoryMock.Object)) - { - _serviceFactoryMock.Setup(p => p.CreateConnector(connectionInfo)) - .Returns(_connectorMock.Object); - _connectorMock.Setup(p => p.Connect(connectionInfo)) - .Returns(c => new HttpConnector().Connect(c)); - - 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 = CreateConnectionInfoWithHttpProxy(proxyEndPoint, serverEndPoint, null); - using (var session = new Session(connectionInfo, _serviceFactoryMock.Object)) - { - _serviceFactoryMock.Setup(p => p.CreateConnector(connectionInfo)) - .Returns(_connectorMock.Object); - _connectorMock.Setup(p => p.Connect(connectionInfo)) - .Returns(c => new HttpConnector().Connect(c)); - - try - { - session.Connect(); - Assert.Fail(); - } - catch (ProxyException) - { - } - } - - Assert.IsFalse(proxyStub.HttpRequest.Headers.Any(p => p.StartsWith("Proxy-Authorization:"))); - } - } - private static ConnectionInfo CreateConnectionInfoWithHttpProxy(IPEndPoint proxyEndPoint, IPEndPoint serverEndPoint, string proxyUserName) { return new ConnectionInfo( diff --git a/src/Renci.SshNet.Tests/Classes/SessionTest.cs b/src/Renci.SshNet.Tests/Classes/SessionTest.cs index e15e05f6..972757e4 100644 --- a/src/Renci.SshNet.Tests/Classes/SessionTest.cs +++ b/src/Renci.SshNet.Tests/Classes/SessionTest.cs @@ -1,16 +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.Connection; -using Renci.SshNet.Messages.Transport; using Renci.SshNet.Tests.Common; -using Renci.SshNet.Tests.Properties; namespace Renci.SshNet.Tests.Classes { @@ -21,25 +14,28 @@ namespace Renci.SshNet.Tests.Classes public partial class SessionTest : TestBase { private Mock _serviceFactoryMock; + private Mock _socketFactoryMock; private Mock _connectorMock; + private Mock _protocolVersionExchangeMock; protected override void OnInit() { base.OnInit(); _serviceFactoryMock = new Mock(MockBehavior.Strict); + _socketFactoryMock = new Mock(MockBehavior.Strict); _connectorMock = new Mock(MockBehavior.Strict); + _protocolVersionExchangeMock = new Mock(MockBehavior.Strict); } [TestMethod] public void ConstructorShouldThrowArgumentNullExceptionWhenConnectionInfoIsNull() { - ConnectionInfo connectionInfo = null; - var serviceFactory = new Mock(MockBehavior.Strict).Object; + const ConnectionInfo connectionInfo = null; try { - new Session(connectionInfo, serviceFactory); + new Session(connectionInfo, _serviceFactoryMock.Object, _socketFactoryMock.Object); Assert.Fail(); } catch (ArgumentNullException ex) @@ -58,7 +54,7 @@ namespace Renci.SshNet.Tests.Classes try { - new Session(connectionInfo, serviceFactory); + new Session(connectionInfo, serviceFactory, _socketFactoryMock.Object); Assert.Fail(); } catch (ArgumentNullException ex) @@ -69,400 +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)) - { - _serviceFactoryMock.Setup(p => p.CreateConnector(connectionInfo)) - .Returns(_connectorMock.Object); - _connectorMock.Setup(p => p.Connect(connectionInfo)) - .Returns(c => new DirectConnector().Connect(c)); - - 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)) - { - _serviceFactoryMock.Setup(p => p.CreateConnector(connectionInfo)) - .Returns(_connectorMock.Object); - _connectorMock.Setup(p => p.Connect(connectionInfo)) - .Returns(c => new DirectConnector().Connect(c)); - - 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)) - { - _serviceFactoryMock.Setup(p => p.CreateConnector(connectionInfo)) - .Returns(_connectorMock.Object); - _connectorMock.Setup(p => p.Connect(connectionInfo)) - .Returns(c => new DirectConnector().Connect(c)); - - 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(); - - var connectionInfo = CreateConnectionInfo(serverEndPoint, TimeSpan.FromMilliseconds(500)); - - using (var session = new Session(connectionInfo, _serviceFactoryMock.Object)) - { - _serviceFactoryMock.Setup(p => p.CreateConnector(connectionInfo)) - .Returns(_connectorMock.Object); - _connectorMock.Setup(p => p.Connect(connectionInfo)) - .Returns(c => new DirectConnector().Connect(c)); - - 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(); - - var connectionInfo = CreateConnectionInfo(serverEndPoint, TimeSpan.FromSeconds(5)); - - using (var session = new Session(connectionInfo, _serviceFactoryMock.Object)) - { - _serviceFactoryMock.Setup(p => p.CreateConnector(connectionInfo)) - .Returns(_connectorMock.Object); - _connectorMock.Setup(p => p.Connect(connectionInfo)) - .Returns(c => new DirectConnector().Connect(c)); - - 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(); - - var connectionInfo = CreateConnectionInfo(serverEndPoint, TimeSpan.FromSeconds(5)); - - using (var session = new Session(connectionInfo, _serviceFactoryMock.Object)) - { - _serviceFactoryMock.Setup(p => p.CreateConnector(connectionInfo)) - .Returns(_connectorMock.Object); - _connectorMock.Setup(p => p.Connect(connectionInfo)) - .Returns(c => new DirectConnector().Connect(c)); - - 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(); - - var connectionInfo = CreateConnectionInfo(serverEndPoint, TimeSpan.FromSeconds(5)); - - using (var session = new Session(connectionInfo, _serviceFactoryMock.Object)) - { - _serviceFactoryMock.Setup(p => p.CreateConnector(connectionInfo)) - .Returns(_connectorMock.Object); - _connectorMock.Setup(p => p.Connect(connectionInfo)) - .Returns(c => new DirectConnector().Connect(c)); - - 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); - - _serviceFactoryMock.Setup(p => p.CreateConnector(connectionInfo)) - .Returns(_connectorMock.Object); - _connectorMock.Setup(p => p.Connect(connectionInfo)) - .Returns(c => new DirectConnector().Connect(c)); + const ISocketFactory socketFactory = null; try { - session.Connect(); + new Session(connectionInfo, _serviceFactoryMock.Object, socketFactory); Assert.Fail(); } - catch (SocketException ex) + catch (ArgumentNullException ex) { - Assert.AreEqual(ex.ErrorCode, (int)SocketError.HostNotFound); + Assert.IsNull(ex.InnerException); + Assert.AreEqual("socketFactory", ex.ParamName); } } - [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); - - _serviceFactoryMock.Setup(p => p.CreateConnector(connectionInfo)) - .Returns(_connectorMock.Object); - _connectorMock.Setup(p => p.Connect(connectionInfo)) - .Returns(c => new HttpConnector().Connect(c)); - - 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); - - _serviceFactoryMock.Setup(p => p.CreateConnector(connectionInfo)) - .Returns(_connectorMock.Object); - _connectorMock.Setup(p => p.Connect(connectionInfo)) - .Returns(c => new DirectConnector().Connect(c)); - - try - { - session.Connect(); - 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); - - _serviceFactoryMock.Setup(p => p.CreateConnector(connectionInfo)) - .Returns(_connectorMock.Object); - _connectorMock.Setup(p => p.Connect(connectionInfo)) - .Returns(c => new DirectConnector().Connect(c)); - - try - { - session.Connect(); - Assert.Fail(); - } - catch (SocketException) - { - session.Dispose(); - } - } - - [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( diff --git a/src/Renci.SshNet.Tests/Classes/SessionTestBase.cs b/src/Renci.SshNet.Tests/Classes/SessionTestBase.cs new file mode 100644 index 00000000..cbe69e2a --- /dev/null +++ b/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 _serviceFactoryMock { get; private set; } + internal Mock _socketFactoryMock { get; private set; } + internal Mock _connectorMock { get; private set; } + + protected virtual void CreateMocks() + { + _serviceFactoryMock = new Mock(MockBehavior.Strict); + _socketFactoryMock = new Mock(MockBehavior.Strict); + _connectorMock = new Mock(MockBehavior.Strict); + } + + protected virtual void SetupData() + { + } + + protected virtual void SetupMocks() + { + } + + protected override void Arrange() + { + CreateMocks(); + SetupData(); + SetupMocks(); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/SessionTest_ConnectToServerFails.cs b/src/Renci.SshNet.Tests/Classes/SessionTest_ConnectToServerFails.cs new file mode 100644 index 00000000..df19c8ec --- /dev/null +++ b/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; + } + } +} \ No newline at end of file diff --git a/src/Renci.SshNet.Tests/Classes/SessionTest_Connected.cs b/src/Renci.SshNet.Tests/Classes/SessionTest_Connected.cs index 730aa3e4..fa36aed5 100644 --- a/src/Renci.SshNet.Tests/Classes/SessionTest_Connected.cs +++ b/src/Renci.SshNet.Tests/Classes/SessionTest_Connected.cs @@ -66,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] @@ -253,10 +253,17 @@ namespace Renci.SshNet.Tests.Classes } } + [TestMethod] + public void ClientSocketShouldBeConnected() + { + Assert.IsNotNull(ClientSocket); + Assert.IsTrue(ClientSocket.Connected); + } + [TestMethod] public void CreateConnectorOnServiceFactoryShouldHaveBeenInvokedOnce() { - ServiceFactoryMock.Verify(p => p.CreateConnector(ConnectionInfo), Times.Once()); + ServiceFactoryMock.Verify(p => p.CreateConnector(ConnectionInfo, SocketFactoryMock.Object), Times.Once()); } [TestMethod] diff --git a/src/Renci.SshNet.Tests/Classes/SessionTest_ConnectedBase.cs b/src/Renci.SshNet.Tests/Classes/SessionTest_ConnectedBase.cs index 0651896d..a920b190 100644 --- a/src/Renci.SshNet.Tests/Classes/SessionTest_ConnectedBase.cs +++ b/src/Renci.SshNet.Tests/Classes/SessionTest_ConnectedBase.cs @@ -22,11 +22,16 @@ namespace Renci.SshNet.Tests.Classes public abstract class SessionTest_ConnectedBase { internal Mock ServiceFactoryMock { get; private set; } + internal Mock SocketFactoryMock { get; private set; } internal Mock ConnectorMock { get; private set; } + + private Mock _protocolVersionExchangeMock; private Mock _keyExchangeMock; private Mock _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; } @@ -39,6 +44,7 @@ namespace Renci.SshNet.Tests.Classes 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() @@ -67,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() @@ -87,8 +99,11 @@ namespace Renci.SshNet.Tests.Classes DisconnectReceivedRegister = new List>(); ErrorOccurredRegister = new List(); ServerBytesReceivedRegister = new List(); + 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); @@ -105,60 +120,64 @@ 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(MockBehavior.Strict); + SocketFactoryMock = new Mock(MockBehavior.Strict); ConnectorMock = new Mock(MockBehavior.Strict); + _protocolVersionExchangeMock = new Mock(MockBehavior.Strict); _keyExchangeMock = new Mock(MockBehavior.Strict); _clientAuthenticationMock = new Mock(MockBehavior.Strict); } private void SetupMocks() { - ServiceFactoryMock.Setup(p => p.CreateConnector(ConnectionInfo)) - .Returns(ConnectorMock.Object); + ServiceFactoryMock.Setup(p => p.CreateConnector(ConnectionInfo, SocketFactoryMock.Object)) + .Returns(ConnectorMock.Object); ConnectorMock.Setup(p => p.Connect(ConnectionInfo)) - .Returns(c => new DirectConnector().Connect(c)); + .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); @@ -184,7 +203,6 @@ namespace Renci.SshNet.Tests.Classes SetupData(); SetupMocks(); - ServerListener.Start(); Session.Connect(); } diff --git a/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_Disconnect.cs b/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_Disconnect.cs index 3aed3027..eacb27c3 100644 --- a/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_Disconnect.cs +++ b/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); + } } } \ No newline at end of file diff --git a/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_GlobalRequestMessageAfterAuthenticationRace.cs b/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_GlobalRequestMessageAfterAuthenticationRace.cs index a78154f7..a8ce5c96 100644 --- a/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_GlobalRequestMessageAfterAuthenticationRace.cs +++ b/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()); } } } diff --git a/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerAndClientDisconnectRace.cs b/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerAndClientDisconnectRace.cs index e652a8af..cee331f9 100644 --- a/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerAndClientDisconnectRace.cs +++ b/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerAndClientDisconnectRace.cs @@ -22,12 +22,16 @@ namespace Renci.SshNet.Tests.Classes public class SessionTest_Connected_ServerAndClientDisconnectRace { private Mock _serviceFactoryMock; + private Mock _socketFactoryMock; private Mock _connectorMock; + private Mock _protocolVersionExchangeMock; private Mock _keyExchangeMock; private Mock _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; } @@ -38,7 +42,9 @@ namespace Renci.SshNet.Tests.Classes protected AsyncSocketListener ServerListener { get; private set; } protected IList 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() { @@ -51,6 +57,12 @@ namespace Renci.SshNet.Tests.Classes { Session.Dispose(); } + + if (ClientSocket != null && ClientSocket.Connected) + { + ClientSocket.Shutdown(SocketShutdown.Both); + ClientSocket.Dispose(); + } } protected virtual void SetupData() @@ -71,76 +83,83 @@ namespace Renci.SshNet.Tests.Classes DisconnectReceivedRegister = new List>(); ErrorOccurredRegister = new List(); ServerBytesReceivedRegister = new List(); + 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; - - ServerListener.BytesReceived += (received, socket) => - { - ServerBytesReceivedRegister.Add(received); - - switch (counter++) { - 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: + ServerSocket = socket; + + // 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); + }; + 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(MockBehavior.Strict); + _socketFactoryMock = new Mock(MockBehavior.Strict); _connectorMock = new Mock(MockBehavior.Strict); + _protocolVersionExchangeMock = new Mock(MockBehavior.Strict); _keyExchangeMock = new Mock(MockBehavior.Strict); _clientAuthenticationMock = new Mock(MockBehavior.Strict); } private void SetupMocks() { - _serviceFactoryMock.Setup(p => p.CreateConnector(ConnectionInfo)) + _serviceFactoryMock.Setup(p => p.CreateConnector(ConnectionInfo, _socketFactoryMock.Object)) .Returns(_connectorMock.Object); _connectorMock.Setup(p => p.Connect(ConnectionInfo)) - .Returns(c => new DirectConnector().Connect(c)); + .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); @@ -164,7 +183,6 @@ namespace Renci.SshNet.Tests.Classes SetupData(); SetupMocks(); - ServerListener.Start(); Session.Connect(); } diff --git a/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerSendsDisconnectMessage.cs b/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerSendsDisconnectMessage.cs index 1b887dc6..36a73d82 100644 --- a/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerSendsDisconnectMessage.cs +++ b/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] diff --git a/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerSendsDisconnectMessageAndShutsDownSocket.cs b/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerSendsDisconnectMessageAndShutsDownSocket.cs index d8a50a3d..f3bb9877 100644 --- a/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerSendsDisconnectMessageAndShutsDownSocket.cs +++ b/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] diff --git a/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerSendsUnsupportedMessageType.cs b/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerSendsUnsupportedMessageType.cs index 02e998a4..a40f6e03 100644 --- a/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerSendsUnsupportedMessageType.cs +++ b/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); diff --git a/src/Renci.SshNet.Tests/Classes/SessionTest_NotConnected.cs b/src/Renci.SshNet.Tests/Classes/SessionTest_NotConnected.cs index 4a8b3ded..97a49a0e 100644 --- a/src/Renci.SshNet.Tests/Classes/SessionTest_NotConnected.cs +++ b/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(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() { diff --git a/src/Renci.SshNet.Tests/Classes/SessionTest_SocketConnected_BadPacketAndDispose.cs b/src/Renci.SshNet.Tests/Classes/SessionTest_SocketConnected_BadPacketAndDispose.cs index bcc42b6a..f5fb4d79 100644 --- a/src/Renci.SshNet.Tests/Classes/SessionTest_SocketConnected_BadPacketAndDispose.cs +++ b/src/Renci.SshNet.Tests/Classes/SessionTest_SocketConnected_BadPacketAndDispose.cs @@ -1,7 +1,6 @@ using System; using System.Net; using System.Net.Sockets; -using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Renci.SshNet.Common; @@ -15,13 +14,17 @@ namespace Renci.SshNet.Tests.Classes public class SessionTest_SocketConnected_BadPacketAndDispose { private Mock _serviceFactoryMock; + private Mock _socketFactoryMock; private Mock _connectorMock; + private Mock _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() @@ -42,7 +45,9 @@ namespace Renci.SshNet.Tests.Classes protected void CreateMocks() { _serviceFactoryMock = new Mock(MockBehavior.Strict); + _socketFactoryMock = new Mock(MockBehavior.Strict); _connectorMock = new Mock(MockBehavior.Strict); + _protocolVersionExchangeMock = new Mock(MockBehavior.Strict); } protected void SetupData() @@ -55,30 +60,37 @@ namespace Renci.SshNet.Tests.Classes new PasswordAuthenticationMethod("user", "password")); _connectionInfo.Timeout = TimeSpan.FromMilliseconds(200); _actualException = null; + _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) => - { + // 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)) + _serviceFactoryMock.Setup(p => p.CreateConnector(_connectionInfo, _socketFactoryMock.Object)) .Returns(_connectorMock.Object); _connectorMock.Setup(p => p.Connect(_connectionInfo)) - .Returns(c => new DirectConnector().Connect(c)); + .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() @@ -86,15 +98,13 @@ namespace Renci.SshNet.Tests.Classes CreateMocks(); SetupData(); SetupMocks(); - - _serverListener.Start(); } protected virtual void Act() { try { - using (_session = new Session(_connectionInfo, _serviceFactoryMock.Object)) + { _session.Connect(); } diff --git a/src/Renci.SshNet.Tests/Classes/SftpClientTestBase.cs b/src/Renci.SshNet.Tests/Classes/SftpClientTestBase.cs new file mode 100644 index 00000000..11657962 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/SftpClientTestBase.cs @@ -0,0 +1,19 @@ +using Moq; +using Renci.SshNet.Sftp; + +namespace Renci.SshNet.Tests.Classes +{ + public abstract class SftpClientTestBase : BaseClientTestBase + { + internal Mock _sftpResponseFactoryMock { get; private set; } + internal Mock _sftpSessionMock { get; private set; } + + protected override void CreateMocks() + { + base.CreateMocks(); + + _sftpResponseFactoryMock = new Mock(MockBehavior.Strict); + _sftpSessionMock = new Mock(MockBehavior.Strict); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/SftpClientTest_Connect_SftpSessionConnectFailure.cs b/src/Renci.SshNet.Tests/Classes/SftpClientTest_Connect_SftpSessionConnectFailure.cs index afbc247b..b3bcb8e8 100644 --- a/src/Renci.SshNet.Tests/Classes/SftpClientTest_Connect_SftpSessionConnectFailure.cs +++ b/src/Renci.SshNet.Tests/Classes/SftpClientTest_Connect_SftpSessionConnectFailure.cs @@ -4,59 +4,35 @@ using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Renci.SshNet.Common; +using Renci.SshNet.Connection; using Renci.SshNet.Security; using Renci.SshNet.Sftp; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class SftpClientTest_Connect_SftpSessionConnectFailure + public class SftpClientTest_Connect_SftpSessionConnectFailure : SftpClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; - private Mock _sftpResponseFactoryMock; - private Mock _sftpSessionMock; private ConnectionInfo _connectionInfo; private ApplicationException _sftpSessionConnectionException; private SftpClient _sftpClient; private ApplicationException _actualException; - [TestInitialize] - public void Setup() - { - Arrange(); - Act(); - } - - private void Arrange() - { - SetupData(); - CreateMocks(); - SetupMocks(); - - _sftpClient = new SftpClient(_connectionInfo, false, _serviceFactoryMock.Object); - } - - private void SetupData() + protected override void SetupData() { _connectionInfo = new ConnectionInfo("host", "user", new NoneAuthenticationMethod("userauth")); _sftpSessionConnectionException = new ApplicationException(); } - private void CreateMocks() - { - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - _sftpResponseFactoryMock = new Mock(MockBehavior.Strict); - _sftpSessionMock = new Mock(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()); @@ -75,7 +51,14 @@ namespace Renci.SshNet.Tests.Classes .Setup(p => p.Dispose()); } - private void Act() + protected override void Arrange() + { + base.Arrange(); + + _sftpClient = new SftpClient(_connectionInfo, false, _serviceFactoryMock.Object); + } + + protected override void Act() { try { diff --git a/src/Renci.SshNet.Tests/Classes/SftpClientTest_Dispose_Connected.cs b/src/Renci.SshNet.Tests/Classes/SftpClientTest_Dispose_Connected.cs index ba7d080f..239c8493 100644 --- a/src/Renci.SshNet.Tests/Classes/SftpClientTest_Dispose_Connected.cs +++ b/src/Renci.SshNet.Tests/Classes/SftpClientTest_Dispose_Connected.cs @@ -6,44 +6,30 @@ using Renci.SshNet.Sftp; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class SftpClientTest_Dispose_Connected + public class SftpClientTest_Dispose_Connected : SftpClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; - private Mock _sftpResponseFactoryMock; - private Mock _sftpSessionMock; private SftpClient _sftpClient; 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(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - _sftpResponseFactoryMock = new Mock(MockBehavior.Strict); - _sftpSessionMock = new Mock(MockBehavior.Strict); - _connectionInfo = new ConnectionInfo("host", "user", new NoneAuthenticationMethod("userauth")); _operationTimeout = new Random().Next(1000, 10000); _sftpClient = new SftpClient(_connectionInfo, false, _serviceFactoryMock.Object); _sftpClient.OperationTimeout = TimeSpan.FromMilliseconds(_operationTimeout); + } + protected override void SetupMocks() + { var sequence = new MockSequence(); + _serviceFactoryMock.InSequence(sequence) - .Setup(p => p.CreateSession(_connectionInfo)) - .Returns(_sessionMock.Object); + .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.CreateSftpResponseFactory()) @@ -55,11 +41,16 @@ namespace Renci.SshNet.Tests.Classes _sessionMock.InSequence(sequence).Setup(p => p.OnDisconnecting()); _sftpSessionMock.InSequence(sequence).Setup(p => p.Dispose()); _sessionMock.InSequence(sequence).Setup(p => p.Dispose()); + } + + protected override void Arrange() + { + base.Arrange(); _sftpClient.Connect(); } - protected void Act() + protected override void Act() { _sftpClient.Dispose(); } @@ -78,10 +69,17 @@ namespace Renci.SshNet.Tests.Classes 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] diff --git a/src/Renci.SshNet.Tests/Classes/SftpClientTest_Dispose_Disconnected.cs b/src/Renci.SshNet.Tests/Classes/SftpClientTest_Dispose_Disconnected.cs index 516b8aeb..192c5e95 100644 --- a/src/Renci.SshNet.Tests/Classes/SftpClientTest_Dispose_Disconnected.cs +++ b/src/Renci.SshNet.Tests/Classes/SftpClientTest_Dispose_Disconnected.cs @@ -6,40 +6,31 @@ using Renci.SshNet.Sftp; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class SftpClientTest_Dispose_Disconnected + public class SftpClientTest_Dispose_Disconnected : SftpClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; - private Mock _sftpResponseFactoryMock; - private Mock _sftpSessionMock; private SftpClient _sftpClient; private ConnectionInfo _connectionInfo; private int _operationTimeout; - [TestInitialize] - public void Setup() + protected override void SetupData() { - Arrange(); - Act(); - } - - protected void Arrange() - { - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - _sftpResponseFactoryMock = new Mock(MockBehavior.Strict); - _sftpSessionMock = new Mock(MockBehavior.Strict); - _connectionInfo = new ConnectionInfo("host", "user", new NoneAuthenticationMethod("userauth")); _operationTimeout = new Random().Next(1000, 10000); _sftpClient = new SftpClient(_connectionInfo, false, _serviceFactoryMock.Object); _sftpClient.OperationTimeout = TimeSpan.FromMilliseconds(_operationTimeout); + } + protected override void SetupMocks() + { var sequence = new MockSequence(); + _serviceFactoryMock.InSequence(sequence) - .Setup(p => p.CreateSession(_connectionInfo)) - .Returns(_sessionMock.Object); + .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.CreateSftpResponseFactory()) @@ -51,12 +42,17 @@ namespace Renci.SshNet.Tests.Classes _sessionMock.InSequence(sequence).Setup(p => p.OnDisconnecting()); _sftpSessionMock.InSequence(sequence).Setup(p => p.Dispose()); _sessionMock.InSequence(sequence).Setup(p => p.Dispose()); + } + + protected override void Arrange() + { + base.Arrange(); _sftpClient.Connect(); _sftpClient.Disconnect(); } - protected void Act() + protected override void Act() { _sftpClient.Dispose(); } @@ -75,10 +71,17 @@ namespace Renci.SshNet.Tests.Classes 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] diff --git a/src/Renci.SshNet.Tests/Classes/SftpClientTest_Dispose_Disposed.cs b/src/Renci.SshNet.Tests/Classes/SftpClientTest_Dispose_Disposed.cs index 73b07ad4..11e00ad0 100644 --- a/src/Renci.SshNet.Tests/Classes/SftpClientTest_Dispose_Disposed.cs +++ b/src/Renci.SshNet.Tests/Classes/SftpClientTest_Dispose_Disposed.cs @@ -6,44 +6,30 @@ using Renci.SshNet.Sftp; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class SftpClientTest_Dispose_Disposed + public class SftpClientTest_Dispose_Disposed : SftpClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; - private Mock _sftpResponseFactoryMock; - private Mock _sftpSessionMock; private SftpClient _sftpClient; 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(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - _sftpResponseFactoryMock = new Mock(MockBehavior.Strict); - _sftpSessionMock = new Mock(MockBehavior.Strict); - _connectionInfo = new ConnectionInfo("host", "user", new NoneAuthenticationMethod("userauth")); _operationTimeout = new Random().Next(1000, 10000); _sftpClient = new SftpClient(_connectionInfo, false, _serviceFactoryMock.Object); _sftpClient.OperationTimeout = TimeSpan.FromMilliseconds(_operationTimeout); + } + protected override void SetupMocks() + { var sequence = new MockSequence(); + _serviceFactoryMock.InSequence(sequence) - .Setup(p => p.CreateSession(_connectionInfo)) - .Returns(_sessionMock.Object); + .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.CreateSftpResponseFactory()) @@ -55,12 +41,17 @@ namespace Renci.SshNet.Tests.Classes _sessionMock.InSequence(sequence).Setup(p => p.OnDisconnecting()); _sftpSessionMock.InSequence(sequence).Setup(p => p.Dispose()); _sessionMock.InSequence(sequence).Setup(p => p.Dispose()); + } + + protected override void Arrange() + { + base.Arrange(); _sftpClient.Connect(); _sftpClient.Dispose(); } - protected void Act() + protected override void Act() { _sftpClient.Dispose(); } @@ -79,10 +70,17 @@ namespace Renci.SshNet.Tests.Classes 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] diff --git a/src/Renci.SshNet.Tests/Classes/SftpClientTest_Finalize_Connected.cs b/src/Renci.SshNet.Tests/Classes/SftpClientTest_Finalize_Connected.cs index 1c1328d7..033f0ea0 100644 --- a/src/Renci.SshNet.Tests/Classes/SftpClientTest_Finalize_Connected.cs +++ b/src/Renci.SshNet.Tests/Classes/SftpClientTest_Finalize_Connected.cs @@ -6,54 +6,49 @@ using Renci.SshNet.Sftp; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class SftpClientTest_Finalize_Connected + public class SftpClientTest_Finalize_Connected : SftpClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; - private Mock _sftpResponseFactoryMock; - private Mock _sftpSessionMock; private SftpClient _sftpClient; private ConnectionInfo _connectionInfo; private int _operationTimeout; + private WeakReference _sftpClientWeakRefence; - [TestInitialize] - public void Setup() + protected override void SetupData() { - Arrange(); - Act(); - } - - protected void Arrange() - { - _serviceFactoryMock = new Mock(MockBehavior.Loose); - _sessionMock = new Mock(MockBehavior.Strict); - _sftpResponseFactoryMock = new Mock(MockBehavior.Strict); - _sftpSessionMock = new Mock(MockBehavior.Strict); - _connectionInfo = new ConnectionInfo("host", "user", new NoneAuthenticationMethod("userauth")); _operationTimeout = new Random().Next(1000, 10000); _sftpClient = new SftpClient(_connectionInfo, false, _serviceFactoryMock.Object); _sftpClient.OperationTimeout = TimeSpan.FromMilliseconds(_operationTimeout); + _sftpClientWeakRefence = new WeakReference(_sftpClient); + } - _serviceFactoryMock.Setup(p => p.CreateSession(_connectionInfo)) - .Returns(_sessionMock.Object); + 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()); _serviceFactoryMock.Setup(p => p.CreateSftpResponseFactory()) .Returns(_sftpResponseFactoryMock.Object); _serviceFactoryMock.Setup(p => p.CreateSftpSession(_sessionMock.Object, _operationTimeout, _connectionInfo.Encoding, _sftpResponseFactoryMock.Object)) .Returns(_sftpSessionMock.Object); _sftpSessionMock.Setup(p => p.Connect()); + } + + protected override void Arrange() + { + base.Arrange(); _sftpClient.Connect(); _sftpClient = 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 +57,25 @@ namespace Renci.SshNet.Tests.Classes [TestMethod] public void DisconnectOnSftpSessionShouldNeverBeInvoked() { + // 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 _sftpSessionMock.Verify(p => p.Disconnect(), Times.Never); } [TestMethod] public void DisposeOnSftpSessionShouldNeverBeInvoked() { + // 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 _sftpSessionMock.Verify(p => p.Dispose(), Times.Never); } + + [TestMethod] + public void SftpClientShouldHaveBeenFinalized() + { + Assert.IsNull(_sftpClientWeakRefence.Target); + } } } diff --git a/src/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSizeAndTerminalModes_Connected.cs b/src/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSizeAndTerminalModes_Connected.cs index 99aba81f..df1ea835 100644 --- a/src/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSizeAndTerminalModes_Connected.cs +++ b/src/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSizeAndTerminalModes_Connected.cs @@ -8,11 +8,8 @@ using Renci.SshNet.Common; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class - SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSizeAndTerminalModes_Connected + public class SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSizeAndTerminalModes_Connected : BaseClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; private SshClient _sshClient; private ConnectionInfo _connectionInfo; private string _terminalName; @@ -25,14 +22,7 @@ namespace Renci.SshNet.Tests.Classes private ShellStream _expected; private ShellStream _actual; - [TestInitialize] - public void Setup() - { - Arrange(); - Act(); - } - - private void SetupData() + protected override void SetupData() { var random = new Random(); @@ -49,18 +39,15 @@ namespace Renci.SshNet.Tests.Classes _expected = CreateShellStream(); } - private void CreateMocks() - { - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(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()); @@ -76,17 +63,15 @@ namespace Renci.SshNet.Tests.Classes .Returns(_expected); } - private void Arrange() + protected override void Arrange() { - SetupData(); - CreateMocks(); - SetupMocks(); + base.Arrange(); _sshClient = new SshClient(_connectionInfo, false, _serviceFactoryMock.Object); _sshClient.Connect(); } - protected void Act() + protected override void Act() { _actual = _sshClient.CreateShellStream(_terminalName, _widthColumns, diff --git a/src/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSize_Connected.cs b/src/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSize_Connected.cs index 14f52887..337f9bce 100644 --- a/src/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSize_Connected.cs +++ b/src/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSize_Connected.cs @@ -8,10 +8,8 @@ using Renci.SshNet.Common; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSize_Connected + public class SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSize_Connected : BaseClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; private SshClient _sshClient; private ConnectionInfo _connectionInfo; private string _terminalName; @@ -23,14 +21,7 @@ namespace Renci.SshNet.Tests.Classes private ShellStream _expected; private ShellStream _actual; - [TestInitialize] - public void Setup() - { - Arrange(); - Act(); - } - - private void SetupData() + protected override void SetupData() { var random = new Random(); @@ -46,18 +37,15 @@ namespace Renci.SshNet.Tests.Classes _expected = CreateShellStream(); } - private void CreateMocks() - { - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(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()); @@ -73,17 +61,15 @@ namespace Renci.SshNet.Tests.Classes .Returns(_expected); } - private void Arrange() + protected override void Arrange() { - SetupData(); - CreateMocks(); - SetupMocks(); + base.Arrange(); _sshClient = new SshClient(_connectionInfo, false, _serviceFactoryMock.Object); _sshClient.Connect(); } - protected void Act() + protected override void Act() { _actual = _sshClient.CreateShellStream(_terminalName, _widthColumns, diff --git a/src/Renci.SshNet.Tests/Classes/SshClientTest_Disconnect_ForwardedPortStarted.cs b/src/Renci.SshNet.Tests/Classes/SshClientTest_Disconnect_ForwardedPortStarted.cs index a5f19697..e2c22b9a 100644 --- a/src/Renci.SshNet.Tests/Classes/SshClientTest_Disconnect_ForwardedPortStarted.cs +++ b/src/Renci.SshNet.Tests/Classes/SshClientTest_Disconnect_ForwardedPortStarted.cs @@ -5,42 +5,37 @@ using Moq; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class SshClientTest_Disconnect_ForwardedPortStarted + public class SshClientTest_Disconnect_ForwardedPortStarted : BaseClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; private Mock _forwardedPortMock; private SshClient _sshClient; private ConnectionInfo _connectionInfo; - [TestInitialize] - public void Setup() - { - Arrange(); - Act(); - } - - [TestCleanup] - public void Cleanup() - { - } - - protected void Arrange() + protected override void SetupData() { _connectionInfo = new ConnectionInfo("host", "user", new NoneAuthenticationMethod("userauth")); + } + protected override void SetupMocks() + { var sequence = new MockSequence(); - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - _forwardedPortMock = new Mock(MockBehavior.Strict); - - _serviceFactoryMock.InSequence(sequence).Setup(p => p.CreateSession(_connectionInfo)).Returns(_sessionMock.Object); + _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()); _forwardedPortMock.InSequence(sequence).Setup(p => p.Start()); _sessionMock.InSequence(sequence).Setup(p => p.OnDisconnecting()); _forwardedPortMock.InSequence(sequence).Setup(p => p.Stop()); _sessionMock.InSequence(sequence).Setup(p => p.Dispose()); + } + + protected override void Arrange() + { + base.Arrange(); _sshClient = new SshClient(_connectionInfo, false, _serviceFactoryMock.Object); _sshClient.Connect(); @@ -49,7 +44,7 @@ namespace Renci.SshNet.Tests.Classes _forwardedPortMock.Object.Start(); } - protected void Act() + protected override void Act() { _sshClient.Disconnect(); } diff --git a/src/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Connected.cs b/src/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Connected.cs index a2133775..55108df6 100644 --- a/src/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Connected.cs +++ b/src/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Connected.cs @@ -4,51 +4,55 @@ using Moq; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class SshClientTest_Dispose_Connected + public class SshClientTest_Dispose_Connected : BaseClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; private SshClient _sshClient; private ConnectionInfo _connectionInfo; - [TestInitialize] - public void Setup() - { - Arrange(); - Act(); - } - - [TestCleanup] - public void Cleanup() - { - } - - protected void Arrange() + protected override void SetupData() { _connectionInfo = new ConnectionInfo("host", "user", new NoneAuthenticationMethod("userauth")); + } - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - + protected override void SetupMocks() + { var sequence = new MockSequence(); - _serviceFactoryMock.InSequence(sequence).Setup(p => p.CreateSession(_connectionInfo)).Returns(_sessionMock.Object); + + _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()); + } + + protected override void Arrange() + { + base.Arrange(); _sshClient = new SshClient(_connectionInfo, false, _serviceFactoryMock.Object); _sshClient.Connect(); } - protected void Act() + protected override void Act() { _sshClient.Dispose(); } + [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] diff --git a/src/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Disconnected.cs b/src/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Disconnected.cs index b4d28cb0..1d78561b 100644 --- a/src/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Disconnected.cs +++ b/src/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Disconnected.cs @@ -4,52 +4,56 @@ using Moq; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class SshClientTest_Dispose_Disconnected + public class SshClientTest_Dispose_Disconnected : BaseClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; private SshClient _sshClient; private ConnectionInfo _connectionInfo; - [TestInitialize] - public void Setup() - { - Arrange(); - Act(); - } - - [TestCleanup] - public void Cleanup() - { - } - - protected void Arrange() + protected override void SetupData() { _connectionInfo = new ConnectionInfo("host", "user", new NoneAuthenticationMethod("userauth")); + } - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - + protected override void SetupMocks() + { var sequence = new MockSequence(); - _serviceFactoryMock.InSequence(sequence).Setup(p => p.CreateSession(_connectionInfo)).Returns(_sessionMock.Object); + + _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()); + } + + protected override void Arrange() + { + base.Arrange(); _sshClient = new SshClient(_connectionInfo, false, _serviceFactoryMock.Object); _sshClient.Connect(); _sshClient.Disconnect(); } - protected void Act() + protected override void Act() { _sshClient.Dispose(); } + [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] diff --git a/src/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Disposed.cs b/src/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Disposed.cs index b5ffd003..54e07be9 100644 --- a/src/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Disposed.cs +++ b/src/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Disposed.cs @@ -4,52 +4,56 @@ using Moq; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class SshClientTest_Dispose_Disposed + public class SshClientTest_Dispose_Disposed : BaseClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; private SshClient _sshClient; private ConnectionInfo _connectionInfo; - [TestInitialize] - public void Setup() - { - Arrange(); - Act(); - } - - [TestCleanup] - public void Cleanup() - { - } - - protected void Arrange() + protected override void SetupData() { _connectionInfo = new ConnectionInfo("host", "user", new NoneAuthenticationMethod("userauth")); + } - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - + protected override void SetupMocks() + { var sequence = new MockSequence(); - _serviceFactoryMock.InSequence(sequence).Setup(p => p.CreateSession(_connectionInfo)).Returns(_sessionMock.Object); + + _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()); + } + + protected override void Arrange() + { + base.Arrange(); _sshClient = new SshClient(_connectionInfo, false, _serviceFactoryMock.Object); _sshClient.Connect(); _sshClient.Dispose(); } - protected void Act() + protected override void Act() { _sshClient.Dispose(); } + [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] diff --git a/src/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_ForwardedPortStarted.cs b/src/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_ForwardedPortStarted.cs index 28df578d..0b6da3cd 100644 --- a/src/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_ForwardedPortStarted.cs +++ b/src/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_ForwardedPortStarted.cs @@ -6,42 +6,44 @@ using Moq; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class SshClientTest_Dispose_ForwardedPortStarted + public class SshClientTest_Dispose_ForwardedPortStarted : BaseClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; private Mock _forwardedPortMock; private SshClient _sshClient; private ConnectionInfo _connectionInfo; - [TestInitialize] - public void Setup() + protected override void CreateMocks() { - Arrange(); - Act(); + base.CreateMocks(); + + _forwardedPortMock = new Mock(MockBehavior.Strict); } - [TestCleanup] - public void Cleanup() - { - } - - protected void Arrange() + protected override void SetupData() { _connectionInfo = new ConnectionInfo("host", "user", new NoneAuthenticationMethod("userauth")); + } + protected override void SetupMocks() + { var sequence = new MockSequence(); - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - _forwardedPortMock = new Mock(MockBehavior.Strict); - - _serviceFactoryMock.InSequence(sequence).Setup(p => p.CreateSession(_connectionInfo)).Returns(_sessionMock.Object); + _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()); _forwardedPortMock.InSequence(sequence).Setup(p => p.Start()); _sessionMock.InSequence(sequence).Setup(p => p.OnDisconnecting()); _forwardedPortMock.InSequence(sequence).Setup(p => p.Stop()); _sessionMock.InSequence(sequence).Setup(p => p.Dispose()); + } + + protected override void Arrange() + { + base.Arrange(); _sshClient = new SshClient(_connectionInfo, false, _serviceFactoryMock.Object); _sshClient.Connect(); @@ -50,7 +52,7 @@ namespace Renci.SshNet.Tests.Classes _forwardedPortMock.Object.Start(); } - protected void Act() + protected override void Act() { _sshClient.Dispose(); } diff --git a/src/Renci.SshNet.Tests/Common/AsyncSocketListener.cs b/src/Renci.SshNet.Tests/Common/AsyncSocketListener.cs index dbb6128b..e7b051dd 100644 --- a/src/Renci.SshNet.Tests/Common/AsyncSocketListener.cs +++ b/src/Renci.SshNet.Tests/Common/AsyncSocketListener.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Threading; @@ -12,9 +13,11 @@ namespace Renci.SshNet.Tests.Common { private readonly IPEndPoint _endPoint; private readonly ManualResetEvent _acceptCallbackDone; + private List _connectedClients; private Socket _listener; private Thread _receiveThread; private bool _started; + private object _syncLock; public delegate void BytesReceivedHandler(byte[] bytesReceived, Socket socket); public delegate void ConnectedHandler(Socket socket); @@ -27,6 +30,8 @@ namespace Renci.SshNet.Tests.Common { _endPoint = endPoint; _acceptCallbackDone = new ManualResetEvent(false); + _connectedClients = new List(); + _syncLock = new object(); ShutdownRemoteCommunicationSocket = true; } @@ -56,11 +61,31 @@ namespace Renci.SshNet.Tests.Common public void Stop() { _started = false; + + lock (_syncLock) + { + foreach (var connectedClient in _connectedClients) + { + try + { + connectedClient.Shutdown(SocketShutdown.Send); + } + catch (Exception ex) + { + Console.Error.WriteLine("[{0}] Failure shutting down socket: {1}", + typeof(AsyncSocketListener).FullName, + ex); + } + + DrainSocket(connectedClient); + } + } + if (_listener != null) { _listener.Dispose(); - _listener = null; } + if (_receiveThread != null) { _receiveThread.Join(); @@ -87,15 +112,23 @@ namespace Renci.SshNet.Tests.Common private void AcceptCallback(IAsyncResult ar) { - // Signal the main thread to continue. + // Signal the main thread to continue _acceptCallbackDone.Set(); - // Get the socket that handles the client request. - var listener = (Socket)ar.AsyncState; + // Get the socket that listens for inbound connections + var listener = (Socket) ar.AsyncState; + + // Get the socket that handles the client request + var handler = listener.EndAccept(ar); + + // Signal new connection + SignalConnected(handler); + + // Register client socket + _connectedClients.Add(handler); + try { - var handler = listener.EndAccept(ar); - SignalConnected(handler); var state = new SocketStateObject(handler); handler.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, ReadCallback, state); } @@ -109,7 +142,7 @@ namespace Renci.SshNet.Tests.Common private void ReadCallback(IAsyncResult ar) { // Retrieve the state object and the handler socket - // from the asynchronous state object. + // from the asynchronous state object var state = (SocketStateObject) ar.AsyncState; var handler = state.Socket; @@ -132,8 +165,6 @@ namespace Renci.SshNet.Tests.Common var bytesReceived = new byte[bytesRead]; Array.Copy(state.Buffer, bytesReceived, bytesRead); SignalBytesReceived(bytesReceived, handler); - - // prepare to receive more bytes handler.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, ReadCallback, state); } else @@ -142,8 +173,13 @@ namespace Renci.SshNet.Tests.Common if (ShutdownRemoteCommunicationSocket) { - handler.Shutdown(SocketShutdown.Both); - handler.Close(); + lock (_syncLock) + { + handler.Shutdown(SocketShutdown.Send); + handler.Close(); + + _connectedClients.Remove(handler); + } } } } @@ -169,6 +205,30 @@ namespace Renci.SshNet.Tests.Common subscribers(client); } + private static void DrainSocket(Socket socket) + { + var buffer = new byte[128]; + + try + { + while (true && socket.Connected) + { + var bytesRead = socket.Receive(buffer); + if (bytesRead == 0) + { + break; + } + } + } + catch (SocketException ex) + { + Console.Error.WriteLine("[{0}] Failure draining socket ({1}): {2}", + typeof(AsyncSocketListener).FullName, + ex.SocketErrorCode.ToString("G"), + ex); + } + } + private class SocketStateObject { public Socket Socket { get; private set; } diff --git a/src/Renci.SshNet.Tests/Common/TripleATestBase.cs b/src/Renci.SshNet.Tests/Common/TripleATestBase.cs new file mode 100644 index 00000000..8e8f6005 --- /dev/null +++ b/src/Renci.SshNet.Tests/Common/TripleATestBase.cs @@ -0,0 +1,29 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Renci.SshNet.Tests.Common +{ + public abstract class TripleATestBase + { + [TestInitialize] + public void Init() + { + Arrange(); + Act(); + } + + [TestCleanup] + public void Cleanup() + { + TearDown(); + } + + protected virtual void TearDown() + { + } + + protected abstract void Arrange(); + + protected abstract void Act(); + } +} + diff --git a/src/Renci.SshNet/BaseClient.cs b/src/Renci.SshNet/BaseClient.cs index 0b5e627f..5b0e01c9 100644 --- a/src/Renci.SshNet/BaseClient.cs +++ b/src/Renci.SshNet/BaseClient.cs @@ -457,7 +457,7 @@ namespace Renci.SshNet private ISession CreateAndConnectSession() { - var session = _serviceFactory.CreateSession(ConnectionInfo); + var session = _serviceFactory.CreateSession(ConnectionInfo, _serviceFactory.CreateSocketFactory()); session.HostKeyReceived += Session_HostKeyReceived; session.ErrorOccured += Session_ErrorOccured; diff --git a/src/Renci.SshNet/Connection/ConnectorBase.cs b/src/Renci.SshNet/Connection/ConnectorBase.cs index a1dd051f..6e9bed7a 100644 --- a/src/Renci.SshNet/Connection/ConnectorBase.cs +++ b/src/Renci.SshNet/Connection/ConnectorBase.cs @@ -9,6 +9,16 @@ namespace Renci.SshNet.Connection { internal abstract class ConnectorBase : IConnector { + protected ConnectorBase(ISocketFactory socketFactory) + { + if (socketFactory == null) + throw new ArgumentNullException("socketFactory"); + + SocketFactory = socketFactory; + } + + internal ISocketFactory SocketFactory { get; private set; } + public abstract Socket Connect(IConnectionInfo connectionInfo); /// @@ -26,18 +36,35 @@ namespace Renci.SshNet.Connection DiagnosticAbstraction.Log(string.Format("Initiating connection to '{0}:{1}'.", host, port)); - var socket = SocketAbstraction.Connect(ep, timeout); + var socket = SocketFactory.Create(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp); - const int socketBufferSize = 2 * Session.MaximumSshPacketSize; - socket.SendBufferSize = socketBufferSize; - socket.ReceiveBufferSize = socketBufferSize; - return socket; + try + { + SocketAbstraction.Connect(socket, ep, timeout); + + const int socketBufferSize = 2 * Session.MaximumSshPacketSize; + socket.SendBufferSize = socketBufferSize; + socket.ReceiveBufferSize = socketBufferSize; + return socket; + } + catch (Exception) + { + socket.Dispose(); + throw; + } } protected static byte SocketReadByte(Socket socket) { var buffer = new byte[1]; - SocketRead(socket, buffer, 0, 1); + SocketRead(socket, buffer, 0, 1, Session.InfiniteTimeSpan); + return buffer[0]; + } + + protected static byte SocketReadByte(Socket socket, TimeSpan readTimeout) + { + var buffer = new byte[1]; + SocketRead(socket, buffer, 0, 1, readTimeout); return buffer[0]; } @@ -52,16 +79,31 @@ namespace Renci.SshNet.Connection /// The number of bytes read. /// /// The socket is closed. - /// The read has timed-out. /// The read failed. protected static int SocketRead(Socket socket, byte[] buffer, int offset, int length) { - var bytesRead = SocketAbstraction.Read(socket, buffer, offset, length, Session.InfiniteTimeSpan); + return SocketRead(socket, buffer, offset, length, Session.InfiniteTimeSpan); + } + + /// + /// Performs a blocking read on the socket until bytes are received. + /// + /// The to read from. + /// An array of type that is the storage location for the received data. + /// The position in parameter to store the received data. + /// The number of bytes to read. + /// The maximum time to wait until bytes have been received. + /// + /// The number of bytes read. + /// + /// The socket is closed. + /// The read has timed-out. + /// The read failed. + protected static int SocketRead(Socket socket, byte[] buffer, int offset, int length, TimeSpan readTimeout) + { + var bytesRead = SocketAbstraction.Read(socket, buffer, offset, length, readTimeout); if (bytesRead == 0) { - // when we're in the disconnecting state (either triggered by client or server), then the - // SshConnectionException will interrupt the message listener loop (if not already interrupted) - // and the exception itself will be ignored (in RaiseError) throw new SshConnectionException("An established connection was aborted by the server.", DisconnectReason.ConnectionLost); } diff --git a/src/Renci.SshNet/Connection/DirectConnector.cs b/src/Renci.SshNet/Connection/DirectConnector.cs index 05dc833e..ec846450 100644 --- a/src/Renci.SshNet/Connection/DirectConnector.cs +++ b/src/Renci.SshNet/Connection/DirectConnector.cs @@ -4,6 +4,10 @@ namespace Renci.SshNet.Connection { internal class DirectConnector : ConnectorBase { + public DirectConnector(ISocketFactory socketFactory) : base(socketFactory) + { + } + public override Socket Connect(IConnectionInfo connectionInfo) { return SocketConnect(connectionInfo.Host, connectionInfo.Port, connectionInfo.Timeout); diff --git a/src/Renci.SshNet/Connection/HttpConnector.cs b/src/Renci.SshNet/Connection/HttpConnector.cs index d4ff0e15..b77f0734 100644 --- a/src/Renci.SshNet/Connection/HttpConnector.cs +++ b/src/Renci.SshNet/Connection/HttpConnector.cs @@ -8,18 +8,57 @@ using System.Text.RegularExpressions; namespace Renci.SshNet.Connection { + /// + /// Establishes a tunnel via an HTTP proxy server. + /// + /// + /// + /// + /// Specification + /// URL + /// + /// + /// HTTP CONNECT method + /// https://tools.ietf.org/html/rfc7231#section-4.3.6 + /// + /// + /// HTTP Authentication: Basic and Digest Access Authentication + /// https://tools.ietf.org/html/rfc2617 + /// + /// + /// internal class HttpConnector : ConnectorBase { + public HttpConnector(ISocketFactory socketFactory) : base(socketFactory) + { + } + public override Socket Connect(IConnectionInfo connectionInfo) { var socket = SocketConnect(connectionInfo.ProxyHost, connectionInfo.ProxyPort, connectionInfo.Timeout); + try + { + HandleProxyConnect(connectionInfo, socket); + return socket; + } + catch (Exception) + { + socket.Shutdown(SocketShutdown.Both); + socket.Dispose(); + + throw; + } + } + + private void HandleProxyConnect(IConnectionInfo connectionInfo, Socket socket) + { var httpResponseRe = new Regex(@"HTTP/(?\d[.]\d) (?\d{3}) (?.+)$"); var httpHeaderRe = new Regex(@"(?[^\[\]()<>@,;:\""/?={} \t]+):(?.+)?"); SocketAbstraction.Send(socket, SshData.Ascii.GetBytes(string.Format("CONNECT {0}:{1} HTTP/1.0\r\n", connectionInfo.Host, connectionInfo.Port))); - // Sent proxy authorization is specified + // Sent proxy authorization if specified if (!string.IsNullOrEmpty(connectionInfo.ProxyUsername)) { var authorization = string.Format("Proxy-Authorization: Basic {0}\r\n", @@ -50,9 +89,9 @@ namespace Renci.SshNet.Connection statusCode = (HttpStatusCode)int.Parse(httpStatusCode); if (statusCode != HttpStatusCode.OK) { - var reasonPhrase = statusMatch.Result("${reasonPhrase}"); - throw new ProxyException(string.Format("HTTP: Status code {0}, \"{1}\"", httpStatusCode, - reasonPhrase)); + throw new ProxyException(string.Format("HTTP: Status code {0}, \"{1}\"", + httpStatusCode, + statusMatch.Result("${reasonPhrase}"))); } } @@ -78,7 +117,7 @@ namespace Renci.SshNet.Connection if (contentLength > 0) { var contentBody = new byte[contentLength]; - SocketRead(socket, contentBody, 0, contentLength); + SocketRead(socket, contentBody, 0, contentLength, connectionInfo.Timeout); } break; } @@ -88,21 +127,19 @@ namespace Renci.SshNet.Connection { throw new ProxyException("HTTP response does not contain status line."); } - - return socket; } /// /// Performs a blocking read on the socket until a line is read. /// /// The to read from. - /// A that represents the time to wait until a line is read. + /// A that represents the time to wait until a line is read. /// The read has timed-out. /// An error occurred when trying to access the socket. /// /// The line read from the socket, or null when the remote server has shutdown and all data has been received. /// - private static string SocketReadLine(Socket socket, TimeSpan timeout) + private static string SocketReadLine(Socket socket, TimeSpan readTimeout) { var encoding = SshData.Ascii; var buffer = new List(); @@ -112,10 +149,12 @@ namespace Renci.SshNet.Connection // to be processed by subsequent invocations do { - var bytesRead = SocketAbstraction.Read(socket, data, 0, data.Length, timeout); + var bytesRead = SocketAbstraction.Read(socket, data, 0, data.Length, readTimeout); if (bytesRead == 0) + { // the remote server shut down the socket break; + } var b = data[0]; buffer.Add(b); diff --git a/src/Renci.SshNet/Connection/ISocketFactory.cs b/src/Renci.SshNet/Connection/ISocketFactory.cs new file mode 100644 index 00000000..0d76eeb2 --- /dev/null +++ b/src/Renci.SshNet/Connection/ISocketFactory.cs @@ -0,0 +1,9 @@ +using System.Net.Sockets; + +namespace Renci.SshNet.Connection +{ + internal interface ISocketFactory + { + Socket Create(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType); + } +} diff --git a/src/Renci.SshNet/Connection/SocketFactory.cs b/src/Renci.SshNet/Connection/SocketFactory.cs new file mode 100644 index 00000000..8c61b87c --- /dev/null +++ b/src/Renci.SshNet/Connection/SocketFactory.cs @@ -0,0 +1,12 @@ +using System.Net.Sockets; + +namespace Renci.SshNet.Connection +{ + internal class SocketFactory : ISocketFactory + { + public Socket Create(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType) + { + return new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp) { NoDelay = true }; + } + } +} diff --git a/src/Renci.SshNet/Connection/Socks4Connector.cs b/src/Renci.SshNet/Connection/Socks4Connector.cs index 64b15e07..096be937 100644 --- a/src/Renci.SshNet/Connection/Socks4Connector.cs +++ b/src/Renci.SshNet/Connection/Socks4Connector.cs @@ -2,26 +2,58 @@ using Renci.SshNet.Common; using System; using System.Net.Sockets; +using System.Text; namespace Renci.SshNet.Connection { + /// + /// Establishes a tunnel via a SOCKS4 proxy server. + /// + /// + /// https://www.openssh.com/txt/socks4.protocol + /// internal class Socks4Connector : ConnectorBase { + public Socks4Connector(ISocketFactory socketFactory) : base(socketFactory) + { + } + public override Socket Connect(IConnectionInfo connectionInfo) { var socket = SocketConnect(connectionInfo.ProxyHost, connectionInfo.ProxyPort, connectionInfo.Timeout); + try + { + HandleProxyConnect(connectionInfo, socket); + return socket; + } + catch (Exception) + { + socket.Shutdown(SocketShutdown.Both); + socket.Dispose(); + + throw; + } + } + + /// + /// Establishes a connection to the server via a SOCKS5 proxy. + /// + /// The connection information. + /// The . + private void HandleProxyConnect(IConnectionInfo connectionInfo, Socket socket) + { var connectionRequest = CreateSocks4ConnectionRequest(connectionInfo.Host, (ushort)connectionInfo.Port, connectionInfo.ProxyUsername); SocketAbstraction.Send(socket, connectionRequest); - // Read null byte - if (SocketReadByte(socket) != 0) + // Read reply version + if (SocketReadByte(socket, connectionInfo.Timeout) != 0x00) { throw new ProxyException("SOCKS4: Null is expected."); } // Read response code - var code = SocketReadByte(socket); + var code = SocketReadByte(socket, connectionInfo.Timeout); switch (code) { @@ -37,15 +69,14 @@ namespace Renci.SshNet.Connection throw new ProxyException("SOCKS4: Not valid response."); } - var dummyBuffer = new byte[6]; // field 3 (2 bytes) and field 4 (4) should be ignored - SocketRead(socket, dummyBuffer, 0, 6); - - return socket; + var destBuffer = new byte[6]; // destination port and IP address should be ignored + SocketRead(socket, destBuffer, 0, destBuffer.Length, connectionInfo.Timeout); } private static byte[] CreateSocks4ConnectionRequest(string hostname, ushort port, string username) { var addressBytes = GetSocks4DestinationAddress(hostname); + var proxyUserBytes = GetProxyUserBytes(username); var connectionRequest = new byte [ @@ -58,7 +89,7 @@ namespace Renci.SshNet.Connection // IP address addressBytes.Length + // Username - username.Length + + proxyUserBytes.Length + // Null terminator 1 ]; @@ -79,6 +110,11 @@ namespace Renci.SshNet.Connection Buffer.BlockCopy(addressBytes, 0, connectionRequest, index, addressBytes.Length); index += addressBytes.Length; + // User name + Buffer.BlockCopy(proxyUserBytes, 0, connectionRequest, index, proxyUserBytes.Length); + index += proxyUserBytes.Length; + + // Null terminator connectionRequest[index] = 0x00; return connectionRequest; @@ -99,5 +135,15 @@ namespace Renci.SshNet.Connection throw new ProxyException(string.Format("SOCKS4 only supports IPv4. No such address found for '{0}'.", hostname)); } + + private static byte[] GetProxyUserBytes(string proxyUser) + { + if (proxyUser == null) + { + return Array.Empty; + } + + return Encoding.ASCII.GetBytes(proxyUser); + } } } diff --git a/src/Renci.SshNet/Connection/Socks5Connector.cs b/src/Renci.SshNet/Connection/Socks5Connector.cs index c000af80..720ac500 100644 --- a/src/Renci.SshNet/Connection/Socks5Connector.cs +++ b/src/Renci.SshNet/Connection/Socks5Connector.cs @@ -5,12 +5,43 @@ using System.Net.Sockets; namespace Renci.SshNet.Connection { + /// + /// Establishes a tunnel via a SOCKS5 proxy server. + /// + /// + /// https://en.wikipedia.org/wiki/SOCKS#SOCKS5 + /// internal class Socks5Connector : ConnectorBase { + public Socks5Connector(ISocketFactory socketFactory) : base(socketFactory) + { + } + public override Socket Connect(IConnectionInfo connectionInfo) { var socket = SocketConnect(connectionInfo.ProxyHost, connectionInfo.ProxyPort, connectionInfo.Timeout); + try + { + HandleProxyConnect(connectionInfo, socket); + return socket; + } + catch (Exception) + { + socket.Shutdown(SocketShutdown.Both); + socket.Dispose(); + + throw; + } + } + + /// + /// Establishes a connection to the server via a SOCKS5 proxy. + /// + /// The connection information. + /// The . + private void HandleProxyConnect(IConnectionInfo connectionInfo, Socket socket) + { var greeting = new byte[] { // SOCKS version number @@ -26,7 +57,9 @@ namespace Renci.SshNet.Connection var socksVersion = SocketReadByte(socket); if (socksVersion != 0x05) + { throw new ProxyException(string.Format("SOCKS Version '{0}' is not supported.", socksVersion)); + } var authenticationMethod = SocketReadByte(socket); switch (authenticationMethod) @@ -50,7 +83,7 @@ namespace Renci.SshNet.Connection throw new ProxyException("SOCKS5: No acceptable authentication methods were offered."); } - var connectionRequest = CreateSocks5ConnectionRequest(connectionInfo.Host, (ushort)connectionInfo.Port); + var connectionRequest = CreateSocks5ConnectionRequest(connectionInfo.Host, (ushort) connectionInfo.Port); SocketAbstraction.Send(socket, connectionRequest); // Read Server SOCKS5 version @@ -111,8 +144,6 @@ namespace Renci.SshNet.Connection // Read 2 bytes to be ignored SocketRead(socket, port, 0, 2); - - return socket; } /// @@ -145,14 +176,14 @@ namespace Renci.SshNet.Connection authenticationRequest[index++] = 0x01; // Length of the username - authenticationRequest[index++] = (byte)username.Length; + authenticationRequest[index++] = (byte) username.Length; // Username SshData.Ascii.GetBytes(username, 0, username.Length, authenticationRequest, index); index += username.Length; // Length of the password - authenticationRequest[index++] = (byte)password.Length; + authenticationRequest[index++] = (byte) password.Length; // Password SshData.Ascii.GetBytes(password, 0, password.Length, authenticationRequest, index); diff --git a/src/Renci.SshNet/IServiceFactory.cs b/src/Renci.SshNet/IServiceFactory.cs index 5b501961..dad93e23 100644 --- a/src/Renci.SshNet/IServiceFactory.cs +++ b/src/Renci.SshNet/IServiceFactory.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Net.Sockets; using System.Text; using Renci.SshNet.Common; using Renci.SshNet.Connection; @@ -16,14 +17,17 @@ namespace Renci.SshNet IClientAuthentication CreateClientAuthentication(); /// - /// Creates a new with the specified . + /// Creates a new with the specified and + /// . /// /// The to use for creating a new session. + /// A factory to create instances. /// /// An for the specified . /// /// is null. - ISession CreateSession(ConnectionInfo connectionInfo); + /// is null. + ISession CreateSession(ConnectionInfo connectionInfo, ISocketFactory socketFactory); /// /// Creates a new in a given and with @@ -114,20 +118,31 @@ namespace Renci.SshNet /// to the server identified by the specified . /// /// A detailing the server to establish a connection to. + /// A factory to create instances. /// - /// An . + /// An that can be used to establish a connection to the + /// server identified by the specified . /// /// is . + /// is . /// The value of is not supported. - IConnector CreateConnector(IConnectionInfo connectionInfo); + IConnector CreateConnector(IConnectionInfo connectionInfo, ISocketFactory socketFactory); /// - /// Create an that deals with the SSH protocol + /// Creates an that deals with the SSH protocol /// version exchange. /// /// /// An . /// IProtocolVersionExchange CreateProtocolVersionExchange(); + + /// + /// Creates a factory to create instances. + /// + /// + /// An . + /// + ISocketFactory CreateSocketFactory(); } } diff --git a/src/Renci.SshNet/ServiceFactory.cs b/src/Renci.SshNet/ServiceFactory.cs index 72112f32..f04e6116 100644 --- a/src/Renci.SshNet/ServiceFactory.cs +++ b/src/Renci.SshNet/ServiceFactory.cs @@ -8,6 +8,7 @@ using Renci.SshNet.Security; using Renci.SshNet.Sftp; using Renci.SshNet.Abstractions; using Renci.SshNet.Connection; +using System.Net.Sockets; namespace Renci.SshNet { @@ -34,16 +35,19 @@ namespace Renci.SshNet } /// - /// Creates a new with the specified . + /// Creates a new with the specified and + /// . /// /// The to use for creating a new session. + /// A factory to create instances. /// /// An for the specified . /// /// is null. - public ISession CreateSession(ConnectionInfo connectionInfo) + /// is null. + public ISession CreateSession(ConnectionInfo connectionInfo, ISocketFactory socketFactory) { - return new Session(connectionInfo, this); + return new Session(connectionInfo, this, socketFactory); } /// @@ -195,34 +199,38 @@ namespace Renci.SshNet /// to the server identified by the specified . /// /// A detailing the server to establish a connection to. + /// A factory to create instances. /// /// An that can be used to establish a connection to the /// server identified by the specified . /// /// is . + /// is . /// The value of is not supported. - public IConnector CreateConnector(IConnectionInfo connectionInfo) + public IConnector CreateConnector(IConnectionInfo connectionInfo, ISocketFactory socketFactory) { if (connectionInfo == null) throw new ArgumentNullException("connectionInfo"); + if (socketFactory == null) + throw new ArgumentNullException("socketFactory"); switch (connectionInfo.ProxyType) { case ProxyTypes.None: - return new DirectConnector(); + return new DirectConnector(socketFactory); case ProxyTypes.Socks4: - return new Socks4Connector(); + return new Socks4Connector(socketFactory); case ProxyTypes.Socks5: - return new Socks5Connector(); + return new Socks5Connector(socketFactory); case ProxyTypes.Http: - return new HttpConnector(); + return new HttpConnector(socketFactory); default: throw new NotSupportedException(string.Format("ProxyTypes '{0}' is not supported.", connectionInfo.ProxyType)); } } /// - /// Create an that deals with the SSH protocol + /// Creates an that deals with the SSH protocol /// version exchange. /// /// @@ -232,5 +240,16 @@ namespace Renci.SshNet { return new ProtocolVersionExchange(); } + + /// + /// Creates a factory to create instances. + /// + /// + /// An . + /// + public ISocketFactory CreateSocketFactory() + { + return new SocketFactory(); + } } } diff --git a/src/Renci.SshNet/Session.cs b/src/Renci.SshNet/Session.cs index bc3c0769..0748b8da 100644 --- a/src/Renci.SshNet/Session.cs +++ b/src/Renci.SshNet/Session.cs @@ -6,6 +6,7 @@ using System.Threading; using Renci.SshNet.Channels; using Renci.SshNet.Common; using Renci.SshNet.Compression; +using Renci.SshNet.Connection; using Renci.SshNet.Messages; using Renci.SshNet.Messages.Authentication; using Renci.SshNet.Messages.Connection; @@ -161,6 +162,7 @@ namespace Renci.SshNet /// Holds the factory to use for creating new services. /// private readonly IServiceFactory _serviceFactory; + private readonly ISocketFactory _socketFactory; /// /// Holds connection socket. @@ -531,18 +533,23 @@ namespace Renci.SshNet /// /// The connection info. /// The factory to use for creating new services. + /// A factory to create instances. /// is null. /// is null. - internal Session(ConnectionInfo connectionInfo, IServiceFactory serviceFactory) + /// is null. + internal Session(ConnectionInfo connectionInfo, IServiceFactory serviceFactory, ISocketFactory socketFactory) { if (connectionInfo == null) throw new ArgumentNullException("connectionInfo"); if (serviceFactory == null) throw new ArgumentNullException("serviceFactory"); + if (socketFactory == null) + throw new ArgumentNullException("socketFactory"); ClientVersion = "SSH-2.0-Renci.SshNet.SshClient.0.0.1"; ConnectionInfo = connectionInfo; _serviceFactory = serviceFactory; + _socketFactory = socketFactory; _messageListenerCompleted = new ManualResetEvent(true); } @@ -577,14 +584,14 @@ namespace Renci.SshNet // Build list of available messages while connecting _sshMessageFactory = new SshMessageFactory(); - _socket = _serviceFactory.CreateConnector(ConnectionInfo) + _socket = _serviceFactory.CreateConnector(ConnectionInfo, _socketFactory) .Connect(ConnectionInfo); var serverIdentification = _serviceFactory.CreateProtocolVersionExchange() .Start(ClientVersion, _socket, ConnectionInfo.Timeout); // Set connection versions - ConnectionInfo.ServerVersion = serverIdentification.ToString(); + ServerVersion = ConnectionInfo.ServerVersion = serverIdentification.ToString(); ConnectionInfo.ClientVersion = ClientVersion; DiagnosticAbstraction.Log(string.Format("Server version '{0}' on '{1}'.", serverIdentification.ProtocolVersion, serverIdentification.SoftwareVersion));