From cfdaa75902b6a4c01a09948752189bdd4bd115cb Mon Sep 17 00:00:00 2001 From: drieseng Date: Tue, 5 Apr 2016 15:07:48 +0200 Subject: [PATCH] Fix tests after recent changes. --- .../Session.SilverlightShared.cs | 11 -- .../Channels/ChannelDirectTcpipTest.cs | 4 + ...lose_SessionIsConnectedAndChannelIsOpen.cs | 4 + .../Abstractions/SocketAbstraction.cs | 154 +++++++++++++++++- .../Channels/ChannelDirectTcpip.cs | 4 +- .../Channels/ChannelForwardedTcpip.cs | 4 +- src/Renci.SshNet/Renci.SshNet.csproj | 2 +- src/Renci.SshNet/Session.NET.cs | 10 -- src/Renci.SshNet/Session.cs | 15 +- 9 files changed, 166 insertions(+), 42 deletions(-) diff --git a/src/Renci.SshNet.Silverlight/Session.SilverlightShared.cs b/src/Renci.SshNet.Silverlight/Session.SilverlightShared.cs index 29a11220..f21dbaec 100644 --- a/src/Renci.SshNet.Silverlight/Session.SilverlightShared.cs +++ b/src/Renci.SshNet.Silverlight/Session.SilverlightShared.cs @@ -13,17 +13,6 @@ namespace Renci.SshNet isConnected = (_socket != null && _socket.Connected); } - /// - /// Closes the socket. - /// - /// - /// This method will wait up to 10 seconds to send any remaining data. - /// - partial void SocketDisconnect() - { - _socket.Close(10); - } - partial void InternalRegisterMessage(string messageName) { lock (_messagesMetadata) diff --git a/src/Renci.SshNet.Tests/Classes/Channels/ChannelDirectTcpipTest.cs b/src/Renci.SshNet.Tests/Classes/Channels/ChannelDirectTcpipTest.cs index 8a7d4317..a45d68a6 100644 --- a/src/Renci.SshNet.Tests/Classes/Channels/ChannelDirectTcpipTest.cs +++ b/src/Renci.SshNet.Tests/Classes/Channels/ChannelDirectTcpipTest.cs @@ -18,6 +18,7 @@ namespace Renci.SshNet.Tests.Classes.Channels { private Mock _sessionMock; private Mock _forwardedPortMock; + private Mock _connectionInfoMock; private uint _localChannelNumber; private uint _localWindowSize; private uint _localPacketSize; @@ -44,6 +45,7 @@ namespace Renci.SshNet.Tests.Classes.Channels _sessionMock = new Mock(MockBehavior.Strict); _forwardedPortMock = new Mock(MockBehavior.Strict); + _connectionInfoMock = new Mock(MockBehavior.Strict); } [TestMethod] @@ -158,6 +160,8 @@ namespace Renci.SshNet.Tests.Classes.Channels _remoteWindowSize, _remotePacketSize, _remoteChannelNumber)))); _sessionMock.Setup(p => p.WaitOnHandle(It.IsAny())) .Callback(p => p.WaitOne(-1)); + _sessionMock.Setup(p => p.ConnectionInfo).Returns(_connectionInfoMock.Object); + _connectionInfoMock.Setup(p => p.Timeout).Returns(TimeSpan.FromSeconds(60)); _sessionMock.Setup(p => p.TrySendMessage(It.IsAny())) .Returns(true) .Callback( diff --git a/src/Renci.SshNet.Tests/Classes/Channels/ChannelDirectTcpipTest_Close_SessionIsConnectedAndChannelIsOpen.cs b/src/Renci.SshNet.Tests/Classes/Channels/ChannelDirectTcpipTest_Close_SessionIsConnectedAndChannelIsOpen.cs index e47557bf..3b659a0e 100644 --- a/src/Renci.SshNet.Tests/Classes/Channels/ChannelDirectTcpipTest_Close_SessionIsConnectedAndChannelIsOpen.cs +++ b/src/Renci.SshNet.Tests/Classes/Channels/ChannelDirectTcpipTest_Close_SessionIsConnectedAndChannelIsOpen.cs @@ -16,6 +16,7 @@ namespace Renci.SshNet.Tests.Classes.Channels { private Mock _sessionMock; private Mock _forwardedPortMock; + private Mock _connectionInfoMock; private ChannelDirectTcpip _channel; private uint _localChannelNumber; private uint _localWindowSize; @@ -72,6 +73,7 @@ namespace Renci.SshNet.Tests.Classes.Channels _sessionMock = new Mock(MockBehavior.Strict); _forwardedPortMock = new Mock(MockBehavior.Strict); + _connectionInfoMock = new Mock(MockBehavior.Strict); var sequence = new MockSequence(); _sessionMock.InSequence(sequence).Setup(p => p.IsConnected).Returns(true); @@ -92,6 +94,8 @@ namespace Renci.SshNet.Tests.Classes.Channels _remoteChannelNumber))); w.WaitOne(); }); + _sessionMock.InSequence(sequence).Setup(p => p.ConnectionInfo).Returns(_connectionInfoMock.Object); + _connectionInfoMock.InSequence(sequence).Setup(p => p.Timeout).Returns(TimeSpan.FromSeconds(60)); _sessionMock.InSequence(sequence).Setup(p => p.IsConnected).Returns(true); _sessionMock.InSequence(sequence) .Setup( diff --git a/src/Renci.SshNet/Abstractions/SocketAbstraction.cs b/src/Renci.SshNet/Abstractions/SocketAbstraction.cs index d4a63795..8ffe3b49 100644 --- a/src/Renci.SshNet/Abstractions/SocketAbstraction.cs +++ b/src/Renci.SshNet/Abstractions/SocketAbstraction.cs @@ -75,6 +75,68 @@ namespace Renci.SshNet.Abstractions #endif } + public static void ClearReadBuffer(Socket socket) + { + try + { + var buffer = new byte[256]; + int bytesReceived; + + do + { + bytesReceived = ReadPartial(socket, buffer, 0, buffer.Length, TimeSpan.FromSeconds(2)); + } while (bytesReceived > 0); + } + catch + { + // ignore any exceptions + } + } + + public static int ReadPartial(Socket socket, byte[] buffer, int offset, int size, TimeSpan timeout) + { +#if FEATURE_SOCKET_SYNC + return socket.Receive(buffer, offset, size, SocketFlags.None); +#elif FEATURE_SOCKET_EAP + var receiveCompleted = new ManualResetEvent(false); + var sendReceiveToken = new PartialSendReceiveToken(socket, receiveCompleted); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = socket.RemoteEndPoint, + UserToken = sendReceiveToken + }; + args.Completed += ReceiveCompleted; + args.SetBuffer(buffer, offset, size); + + try + { + if (socket.ReceiveAsync(args)) + { + if (!receiveCompleted.WaitOne(timeout)) + throw new SshOperationTimeoutException( + string.Format( + CultureInfo.InvariantCulture, + "Socket read operation has timed out after {0:F0} milliseconds.", + timeout.TotalMilliseconds)); + } + + if (args.SocketError != SocketError.Success) + throw new SocketException((int) args.SocketError); + + return args.BytesTransferred; + } + finally + { + // initialize token to avoid the waithandle getting used after it's disposed + args.UserToken = null; + args.Dispose(); + receiveCompleted.Dispose(); + } +#else +#error Receiving data from a Socket is not implemented. +#endif + } + /// /// Receives data from a bound into a receive buffer. /// @@ -96,10 +158,35 @@ namespace Renci.SshNet.Abstractions public static int Read(Socket socket, byte[] buffer, int offset, int size, TimeSpan timeout) { #if FEATURE_SOCKET_SYNC - return socket.Receive(buffer, offset, size, SocketFlags.None); + var totalBytesRead = 0; + var totalBytesToRead = size; + + do + { + try + { + var bytesRead = socket.Receive(buffer, offset + totalBytesRead, totalBytesToRead - totalBytesRead, SocketFlags.None); + if (bytesRead == 0) + return 0; + + totalBytesRead += bytesRead; + } + catch (SocketException ex) + { + if (IsErrorResumable(ex.SocketErrorCode)) + { + ThreadAbstraction.Sleep(30); + continue; + } + throw; + } + } + while (totalBytesRead < totalBytesToRead); + + return totalBytesRead; #elif FEATURE_SOCKET_EAP var receiveCompleted = new ManualResetEvent(false); - var sendReceiveToken = new SendReceiveToken(socket, buffer, offset, size, receiveCompleted); + var sendReceiveToken = new BlockingSendReceiveToken(socket, buffer, offset, size, receiveCompleted); var args = new SocketAsyncEventArgs { @@ -131,7 +218,7 @@ namespace Renci.SshNet.Abstractions receiveCompleted.Dispose(); } #else - #error Receiving data from a Socket is not implemented. +#error Receiving data from a Socket is not implemented. #endif } @@ -171,7 +258,7 @@ namespace Renci.SshNet.Abstractions } while (totalBytesSent < totalBytesToSend); #elif FEATURE_SOCKET_EAP var sendCompleted = new ManualResetEvent(false); - var sendReceiveToken = new SendReceiveToken(socket, data, offset, size, sendCompleted); + var sendReceiveToken = new BlockingSendReceiveToken(socket, data, offset, size, sendCompleted); var socketAsyncSendArgs = new SocketAsyncEventArgs { RemoteEndPoint = socket.RemoteEndPoint, @@ -203,7 +290,7 @@ namespace Renci.SshNet.Abstractions sendCompleted.Dispose(); } #else - #error Receiving data from a Socket is not implemented. +#error Sending data to a Socket is not implemented. #endif } @@ -232,21 +319,26 @@ namespace Renci.SshNet.Abstractions #if FEATURE_SOCKET_EAP && !FEATURE_SOCKET_SYNC private static void ReceiveCompleted(object sender, SocketAsyncEventArgs e) { - var sendReceiveToken = (SendReceiveToken) e.UserToken; + var sendReceiveToken = (Token) e.UserToken; if (sendReceiveToken != null) sendReceiveToken.Process(e); } private static void SendCompleted(object sender, SocketAsyncEventArgs e) { - var sendReceiveToken = (SendReceiveToken)e.UserToken; + var sendReceiveToken = (Token) e.UserToken; if (sendReceiveToken != null) sendReceiveToken.Process(e); } - private class SendReceiveToken + private interface Token { - public SendReceiveToken(Socket socket, byte[] buffer, int offset, int size, EventWaitHandle completionWaitHandle) + void Process(SocketAsyncEventArgs args); + } + + private class BlockingSendReceiveToken : Token + { + public BlockingSendReceiveToken(Socket socket, byte[] buffer, int offset, int size, EventWaitHandle completionWaitHandle) { _socket = socket; _buffer = buffer; @@ -312,6 +404,50 @@ namespace Renci.SshNet.Abstractions private readonly byte[] _buffer; private int _offset; } + + private class PartialSendReceiveToken : Token + { + public PartialSendReceiveToken(Socket socket, EventWaitHandle completionWaitHandle) + { + _socket = socket; + _completionWaitHandle = completionWaitHandle; + } + + public void Process(SocketAsyncEventArgs args) + { + if (args.SocketError == SocketError.Success) + { + _completionWaitHandle.Set(); + return; + } + + if (IsErrorResumable(args.SocketError)) + { + ThreadAbstraction.Sleep(30); + ResumeOperation(args); + return; + } + + // we're dealing with a (fatal) error + _completionWaitHandle.Set(); + } + + private void ResumeOperation(SocketAsyncEventArgs args) + { + switch (args.LastOperation) + { + case SocketAsyncOperation.Receive: + _socket.ReceiveAsync(args); + break; + case SocketAsyncOperation.Send: + _socket.SendAsync(args); + break; + } + } + + private readonly EventWaitHandle _completionWaitHandle; + private readonly Socket _socket; + } #endif // FEATURE_SOCKET_EAP && !FEATURE_SOCKET_SYNC } } diff --git a/src/Renci.SshNet/Channels/ChannelDirectTcpip.cs b/src/Renci.SshNet/Channels/ChannelDirectTcpip.cs index 68a26985..4b577772 100644 --- a/src/Renci.SshNet/Channels/ChannelDirectTcpip.cs +++ b/src/Renci.SshNet/Channels/ChannelDirectTcpip.cs @@ -11,7 +11,7 @@ namespace Renci.SshNet.Channels /// /// Implements "direct-tcpip" SSH channel. /// - internal partial class ChannelDirectTcpip : ClientChannel, IChannelDirectTcpip + internal class ChannelDirectTcpip : ClientChannel, IChannelDirectTcpip { private readonly object _socketLock = new object(); @@ -91,7 +91,7 @@ namespace Renci.SshNet.Channels { try { - var read = SocketAbstraction.Read(_socket, buffer, 0, buffer.Length, ConnectionInfo.Timeout); + var read = SocketAbstraction.ReadPartial(_socket, buffer, 0, buffer.Length, ConnectionInfo.Timeout); if (read > 0) { #if TUNING diff --git a/src/Renci.SshNet/Channels/ChannelForwardedTcpip.cs b/src/Renci.SshNet/Channels/ChannelForwardedTcpip.cs index fb857fdd..78815d6e 100644 --- a/src/Renci.SshNet/Channels/ChannelForwardedTcpip.cs +++ b/src/Renci.SshNet/Channels/ChannelForwardedTcpip.cs @@ -10,7 +10,7 @@ namespace Renci.SshNet.Channels /// /// Implements "forwarded-tcpip" SSH channel. /// - internal partial class ChannelForwardedTcpip : ServerChannel, IChannelForwardedTcpip + internal class ChannelForwardedTcpip : ServerChannel, IChannelForwardedTcpip { private readonly object _socketShutdownAndCloseLock = new object(); private Socket _socket; @@ -83,7 +83,7 @@ namespace Renci.SshNet.Channels { try { - var read = SocketAbstraction.Read(_socket, buffer, 0, buffer.Length, ConnectionInfo.Timeout); + var read = SocketAbstraction.ReadPartial(_socket, buffer, 0, buffer.Length, ConnectionInfo.Timeout); if (read > 0) { #if TUNING diff --git a/src/Renci.SshNet/Renci.SshNet.csproj b/src/Renci.SshNet/Renci.SshNet.csproj index 7e4e086c..5b45f643 100644 --- a/src/Renci.SshNet/Renci.SshNet.csproj +++ b/src/Renci.SshNet/Renci.SshNet.csproj @@ -18,7 +18,7 @@ full false bin\Debug\ - TRACE;DEBUG;TUNING;FEATURE_RNG_CSP;FEATURE_SOCKET_EAP;FEATURE_SOCKET_POLL;FEATURE_SOCKET_SETSOCKETOPTION;FEATURE_STREAM_APM;FEATURE_DNS_SYNC;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_HASH_MD5;FEATURE_HASH_SHA1;FEATURE_HASH_SHA256;FEATURE_HASH_SHA384;FEATURE_HASH_SHA512;FEATURE_HASH_RIPEMD160;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_HMAC_RIPEMD160;FEATURE_MEMORYSTREAM_GETBUFFER + TRACE;DEBUG;TUNING;FEATURE_RNG_CSP;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_APM;FEATURE_SOCKET_POLL;FEATURE_SOCKET_SETSOCKETOPTION;FEATURE_STREAM_APM;FEATURE_DNS_SYNC;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_HASH_MD5;FEATURE_HASH_SHA1;FEATURE_HASH_SHA256;FEATURE_HASH_SHA384;FEATURE_HASH_SHA512;FEATURE_HASH_RIPEMD160;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_HMAC_RIPEMD160;FEATURE_MEMORYSTREAM_GETBUFFER prompt 4 bin\Debug\Renci.SshNet.xml diff --git a/src/Renci.SshNet/Session.NET.cs b/src/Renci.SshNet/Session.NET.cs index 140f4fa6..b1b946dc 100644 --- a/src/Renci.SshNet/Session.NET.cs +++ b/src/Renci.SshNet/Session.NET.cs @@ -79,16 +79,6 @@ namespace Renci.SshNet } } - /// - /// Closes the socket and allows the socket to be reused after the current connection is closed. - /// - /// An error occurred when trying to access the socket. - partial void SocketDisconnect() - { - // TODO should disconnect instead ?!! - _socket.Dispose(); - } - [Conditional("DEBUG")] partial void Log(string text) { diff --git a/src/Renci.SshNet/Session.cs b/src/Renci.SshNet/Session.cs index 7e5ca97b..4aa326f3 100644 --- a/src/Renci.SshNet/Session.cs +++ b/src/Renci.SshNet/Session.cs @@ -1864,12 +1864,6 @@ namespace Renci.SshNet //#endif // FEATURE_SOCKET_SETSOCKETOPTION } - /// - /// Closes the socket. - /// - /// An error occurred when trying to access the socket. - partial void SocketDisconnect(); - /// /// Performs a blocking read on the socket until bytes are received. /// @@ -1958,7 +1952,10 @@ namespace Renci.SshNet if (_socket != null) { if (_socket.Connected) - SocketDisconnect(); + { + _socket.Shutdown(SocketShutdown.Send); + SocketAbstraction.ClearReadBuffer(_socket); + } _socket.Dispose(); _socket = null; } @@ -1979,6 +1976,10 @@ namespace Renci.SshNet HandleMessageCore(message); } } + catch (SocketException ex) + { + RaiseError(new SshConnectionException(ex.Message, DisconnectReason.ConnectionLost, ex)); + } catch (Exception exp) { RaiseError(exp);