ソースを参照

Remove usage of socket related partial methods.

Use methods in SocketAbstraction instead of partial methods.
Gert Driesen 9 年 前
コミット
2f76f8de79

+ 0 - 20
src/Renci.SshNet.NET35/Channels/ChannelDirectTcpip.NET35.cs

@@ -1,20 +0,0 @@
-using System.Net.Sockets;
-
-namespace Renci.SshNet.Channels
-{
-    /// <summary>
-    /// Implements "direct-tcpip" SSH channel.
-    /// </summary>
-    internal partial class ChannelDirectTcpip
-    {
-        partial void InternalSocketReceive(byte[] buffer, ref int read)
-        {
-            read = this._socket.Receive(buffer);
-        }
-
-        partial void InternalSocketSend(byte[] data)
-        {
-            this._socket.Send(data, 0, data.Length, SocketFlags.None);
-        }
-    }
-}

+ 1 - 5
src/Renci.SshNet.NET35/Renci.SshNet.NET35.csproj

@@ -18,7 +18,7 @@
     <DebugType>full</DebugType>
     <Optimize>false</Optimize>
     <OutputPath>bin\Debug\</OutputPath>
-    <DefineConstants>TRACE;DEBUG;TUNING;FEATURE_RNG_CSP;FEATURE_SOCKET_EAP;FEATURE_SOCKET_POLL;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</DefineConstants>
+    <DefineConstants>TRACE;DEBUG;TUNING;FEATURE_RNG_CSP;FEATURE_SOCKET_SYNC;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</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
   </PropertyGroup>
@@ -77,9 +77,6 @@
     <Compile Include="..\Renci.SshNet\Channels\ChannelForwardedTcpip.cs">
       <Link>Channels\ChannelForwardedTcpip.cs</Link>
     </Compile>
-    <Compile Include="..\Renci.SshNet\Channels\ChannelForwardedTcpip.NET40.cs">
-      <Link>Channels\ChannelForwardedTcpip.NET40.cs</Link>
-    </Compile>
     <Compile Include="..\Renci.SshNet\Channels\ChannelSession.cs">
       <Link>Channels\ChannelSession.cs</Link>
     </Compile>
@@ -917,7 +914,6 @@
     <Compile Include="..\Renci.SshNet\SubsystemSession.cs">
       <Link>SubsystemSession.cs</Link>
     </Compile>
-    <Compile Include="Channels\ChannelDirectTcpip.NET35.cs" />
     <Compile Include="Common\Extensions.NET35.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="..\Renci.SshNet\Properties\CommonAssemblyInfo.cs">

+ 0 - 74
src/Renci.SshNet.Silverlight/Channels/ChannelDirectTcpip.SilverlightShared.cs

@@ -1,74 +0,0 @@
-using System.Net.Sockets;
-using System.Threading;
-
-namespace Renci.SshNet.Channels
-{
-    internal partial class ChannelDirectTcpip
-    {
-        private readonly AutoResetEvent _sendEvent = new AutoResetEvent(false);
-        private readonly AutoResetEvent _receiveEvent = new AutoResetEvent(false);
-
-        partial void InternalSocketReceive(byte[] buffer, ref int read)
-        {
-            var bytesToRead = buffer.Length;
-            var receivedTotal = 0;  // how many bytes is already received
-
-            do
-            {
-                var args = new SocketAsyncEventArgs();
-                args.SetBuffer(buffer, receivedTotal, bytesToRead - receivedTotal);
-                args.UserToken = _socket;
-                args.RemoteEndPoint = _socket.RemoteEndPoint;
-                args.Completed += OnReceive;
-                _socket.ReceiveAsync(args);
-
-                _receiveEvent.WaitOne(ConnectionInfo.Timeout);
-
-                if (args.SocketError == SocketError.WouldBlock ||
-                    args.SocketError == SocketError.IOPending ||
-                    args.SocketError == SocketError.NoBufferSpaceAvailable)
-                {
-                    // socket buffer is probably empty, wait and try again
-                    Thread.Sleep(30);
-                    continue;
-                }
-
-                if (args.SocketError != SocketError.Success)
-                {
-                    throw new SocketException((int)args.SocketError);
-                }
-
-                var receivedBytes = args.BytesTransferred;
-                if (receivedBytes > 0)
-                {
-                    receivedTotal += receivedBytes;
-                    continue;
-                }
-                break;
-            } while (receivedTotal < bytesToRead);
-
-            read = receivedTotal;
-        }
-
-        partial void InternalSocketSend(byte[] data)
-        {
-            var args = new SocketAsyncEventArgs();
-            args.SetBuffer(data, 0, data.Length);
-            args.UserToken = _socket;
-            args.RemoteEndPoint = _socket.RemoteEndPoint;
-            args.Completed += OnSend;
-            _socket.SendAsync(args);
-            _sendEvent.WaitOne(ConnectionInfo.Timeout);
-        }
-
-        private void OnReceive(object sender, SocketAsyncEventArgs e)
-        {
-            _receiveEvent.Set();
-        }
-
-        private void OnSend(object sender, SocketAsyncEventArgs e)
-        {
-            _sendEvent.Set();
-        }
-    }
-}

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

@@ -877,7 +877,6 @@
     <Compile Include="..\Renci.SshNet\SubsystemSession.cs">
       <Link>SubsystemSession.cs</Link>
     </Compile>
-    <Compile Include="Channels\ChannelDirectTcpip.SilverlightShared.cs" />
     <Compile Include="Common\Extensions.SilverlightShared.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="..\Renci.SshNet\Properties\CommonAssemblyInfo.cs">

+ 1 - 207
src/Renci.SshNet.Silverlight/Session.SilverlightShared.cs

@@ -1,26 +1,9 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Net;
-using System.Net.Sockets;
-using System.Threading;
-using Renci.SshNet.Abstractions;
-using Renci.SshNet.Common;
-using Renci.SshNet.Messages.Transport;
+using System.Linq;
 
 namespace Renci.SshNet
 {
     public partial class Session
     {
-        private const byte Null = 0x00;
-        private const byte CarriageReturn = 0x0d;
-        private const byte LineFeed = 0x0a;
-
-        private readonly AutoResetEvent _connectEvent = new AutoResetEvent(false);
-        private readonly AutoResetEvent _sendEvent = new AutoResetEvent(false);
-        private readonly AutoResetEvent _receiveEvent = new AutoResetEvent(false);
-
         /// <summary>
         /// Gets a value indicating whether the socket is connected.
         /// </summary>
@@ -30,33 +13,6 @@ namespace Renci.SshNet
             isConnected = (_socket != null && _socket.Connected);
         }
 
-        /// <summary>
-        /// Establishes a socket connection to the specified host and port.
-        /// </summary>
-        /// <param name="host">The host name of the server to connect to.</param>
-        /// <param name="port">The port to connect to.</param>
-        /// <exception cref="SshOperationTimeoutException">The connection failed to establish within the configured <see cref="Renci.SshNet.ConnectionInfo.Timeout"/>.</exception>
-        /// <exception cref="SocketException">An error occurred trying to establish the connection.</exception>
-        partial void SocketConnect(string host, int port)
-        {
-            var timeout = ConnectionInfo.Timeout;
-            var ipAddress = DnsAbstraction.GetHostAddresses(host)[0];
-            var ep = new IPEndPoint(ipAddress, port);
-
-            _socket = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
-
-            var args = CreateSocketAsyncEventArgs(_connectEvent);
-            if (_socket.ConnectAsync(args))
-            {
-                if (!_connectEvent.WaitOne(timeout))
-                    throw new SshOperationTimeoutException(string.Format(CultureInfo.InvariantCulture,
-                        "Connection failed to establish within {0:F0} milliseconds.", timeout.TotalMilliseconds));
-            }
-
-            if (args.SocketError != SocketError.Success)
-                throw new SocketException((int) args.SocketError);
-        }
-
         /// <summary>
         /// Closes the socket.
         /// </summary>
@@ -68,152 +24,6 @@ namespace Renci.SshNet
             _socket.Close(10);
         }
 
-        /// <summary>
-        /// Performs a blocking read on the socket until a line is read.
-        /// </summary>
-        /// <param name="response">The line read from the socket, or <c>null</c> when the remote server has shutdown and all data has been received.</param>
-        /// <param name="timeout">A <see cref="TimeSpan"/> that represents the time to wait until a line is read.</param>
-        /// <exception cref="SshOperationTimeoutException">The read has timed-out.</exception>
-        /// <exception cref="SocketException">An error occurred when trying to access the socket.</exception>
-        partial void SocketReadLine(ref string response, TimeSpan timeout)
-        {
-            var encoding = new ASCIIEncoding();
-            var buffer = new List<byte>();
-            var data = new byte[1];
-
-            // read data one byte at a time to find end of line and leave any unhandled information in the buffer
-            // to be processed by subsequent invocations
-            do
-            {
-                var args = CreateSocketAsyncEventArgs(_receiveEvent, data, 0, data.Length);
-                if (_socket.ReceiveAsync(args))
-                {
-                    if (!_receiveEvent.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);
-
-                if (args.BytesTransferred == 0)
-                    // the remote server shut down the socket
-                    break;
-
-                buffer.Add(data[0]);
-            }
-            while (!(buffer.Count > 0 && (buffer[buffer.Count - 1] == LineFeed || buffer[buffer.Count - 1] == Null)));
-
-            if (buffer.Count == 0)
-                response = null;
-            else if (buffer.Count == 1 && buffer[buffer.Count - 1] == 0x00)
-                // return an empty version string if the buffer consists of only a 0x00 character
-                response = string.Empty;
-            else if (buffer.Count > 1 && buffer[buffer.Count - 2] == CarriageReturn)
-                // strip trailing CRLF
-                response = encoding.GetString(buffer.ToArray(), 0, buffer.Count - 2);
-            else if (buffer.Count > 1 && buffer[buffer.Count - 1] == LineFeed)
-                // strip trailing LF
-                response = encoding.GetString(buffer.ToArray(), 0, buffer.Count - 1);
-            else
-                response = encoding.GetString(buffer.ToArray(), 0, buffer.Count);
-        }
-
-        /// <summary>
-        /// Performs a blocking read on the socket until <paramref name="length"/> bytes are received.
-        /// </summary>
-        /// <param name="length">The number of bytes to read.</param>
-        /// <param name="buffer">The buffer to read to.</param>
-        /// <exception cref="SshConnectionException">The socket is closed.</exception>
-        /// <exception cref="SshOperationTimeoutException">The read has timed-out.</exception>
-        /// <exception cref="SocketException">The read failed.</exception>
-        partial void SocketRead(int length, ref byte[] buffer)
-        {
-            var timeout = InfiniteTimeSpan;
-            var totalBytesReceived = 0;  // how many bytes are already received
-
-            do
-            {
-                var args = CreateSocketAsyncEventArgs(_receiveEvent, buffer, totalBytesReceived,
-                    length - totalBytesReceived);
-                if (_socket.ReceiveAsync(args))
-                {
-                    if (!_receiveEvent.WaitOne(timeout))
-                        // currently we wait indefinitely, so this exception will never be thrown
-                        // but let's leave this here anyway as we may revisit this later
-                        throw new SshOperationTimeoutException(string.Format(CultureInfo.InvariantCulture,
-                            "Socket read operation has timed out after {0:F0} milliseconds.", timeout.TotalMilliseconds));
-                }
-
-                switch (args.SocketError)
-                {
-                    case SocketError.WouldBlock:
-                    case SocketError.IOPending:
-                    case SocketError.NoBufferSpaceAvailable:
-                        // socket buffer is probably full, wait and try again
-                        Thread.Sleep(30);
-                        break;
-                    case SocketError.Success:
-                        var bytesReceived = args.BytesTransferred;
-                        if (bytesReceived > 0)
-                        {
-                            totalBytesReceived += bytesReceived;
-                            continue;
-                        }
-
-                        if (_isDisconnecting)
-                            throw new SshConnectionException(
-                                "An established connection was aborted by the software in your host machine.",
-                                DisconnectReason.ConnectionLost);
-                        throw new SshConnectionException("An established connection was aborted by the server.",
-                            DisconnectReason.ConnectionLost);
-                    default:
-                        throw new SocketException((int) args.SocketError);
-                }
-            } while (totalBytesReceived < length);
-        }
-
-        /// <summary>
-        /// Writes the specified data to the server.
-        /// </summary>
-        /// <param name="data">The data to write to the server.</param>
-        /// <param name="offset">The zero-based offset in <paramref name="data"/> at which to begin taking data from.</param>
-        /// <param name="length">The number of bytes of <paramref name="data"/> to write.</param>
-        /// <exception cref="SshOperationTimeoutException">The write has timed-out.</exception>
-        /// <exception cref="SocketException">The write failed.</exception>
-        private void SocketWrite(byte[] data, int offset, int length)
-        {
-            var timeout = ConnectionInfo.Timeout;
-            var totalBytesSent = 0;  // how many bytes are already sent
-            var totalBytesToSend = length;
-
-            do
-            {
-                var args = CreateSocketAsyncEventArgs(_sendEvent, data, offset + totalBytesSent, totalBytesToSend - totalBytesSent);
-                if (_socket.SendAsync(args))
-                {
-                    if (!_sendEvent.WaitOne(timeout))
-                        throw new SshOperationTimeoutException(string.Format(CultureInfo.InvariantCulture,
-                            "Socket write operation has timed out after {0:F0} milliseconds.", timeout.TotalMilliseconds));
-                }
-
-                switch (args.SocketError)
-                {
-                    case SocketError.WouldBlock:
-                    case SocketError.IOPending:
-                    case SocketError.NoBufferSpaceAvailable:
-                        // socket buffer is probably full, wait and try again
-                        Thread.Sleep(30);
-                        break;
-                    case SocketError.Success:
-                        totalBytesSent += args.BytesTransferred;
-                        break;
-                    default:
-                        throw new SocketException((int) args.SocketError);
-}
-                } while (totalBytesSent < totalBytesToSend);
-        }
-
         partial void InternalRegisterMessage(string messageName)
         {
             lock (_messagesMetadata)
@@ -237,21 +47,5 @@ namespace Renci.SshNet
                 }
             }
         }
-
-        private SocketAsyncEventArgs CreateSocketAsyncEventArgs(EventWaitHandle waitHandle)
-        {
-            var args = new SocketAsyncEventArgs();
-            args.UserToken = _socket;
-            args.RemoteEndPoint = _socket.RemoteEndPoint;
-            args.Completed += (sender, eventArgs) => waitHandle.Set();
-            return args;
-        }
-
-        private SocketAsyncEventArgs CreateSocketAsyncEventArgs(EventWaitHandle waitHandle, byte[] data, int offset, int count)
-        {
-            var args = CreateSocketAsyncEventArgs(waitHandle);
-            args.SetBuffer(data, offset, count);
-            return args;
-        }
     }
 }

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

@@ -69,9 +69,6 @@
     <Reference Include="System.Net" />
   </ItemGroup>
   <ItemGroup>
-    <Compile Include="..\Renci.SshNet.Silverlight\Channels\ChannelDirectTcpip.SilverlightShared.cs">
-      <Link>Channels\ChannelDirectTcpip.SilverlightShared.cs</Link>
-    </Compile>
     <Compile Include="..\Renci.SshNet.Silverlight\Session.SilverlightBrowser.cs">
       <Link>Session.SilverlightBrowser.cs</Link>
     </Compile>

+ 1 - 1
src/Renci.SshNet.WindowsPhone/Renci.SshNet.WindowsPhone.csproj

@@ -24,7 +24,7 @@
     <DebugType>full</DebugType>
     <Optimize>false</Optimize>
     <OutputPath>Bin\Debug</OutputPath>
-    <DefineConstants>TRACE;DEBUG;WINDOWS_PHONE;TUNING;FEATURE_RNG_CSP;FEATURE_STREAM_APM;FEATURE_DEVICEINFORMATION_APM;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_HASH_SHA1;FEATURE_HASH_SHA256;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256</DefineConstants>
+    <DefineConstants>TRACE;DEBUG;WINDOWS_PHONE;TUNING;FEATURE_RNG_CSP;FEATURE_SOCKET_EAP;FEATURE_STREAM_APM;FEATURE_DEVICEINFORMATION_APM;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_HASH_SHA1;FEATURE_HASH_SHA256;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256</DefineConstants>
     <NoStdLib>true</NoStdLib>
     <NoConfig>true</NoConfig>
     <ErrorReport>prompt</ErrorReport>

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

@@ -91,9 +91,6 @@
     <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
   </PropertyGroup>
   <ItemGroup>
-    <Compile Include="..\Renci.SshNet.Silverlight\Channels\ChannelDirectTcpip.SilverlightShared.cs">
-      <Link>Channels\ChannelDirectTcpip.SilverlightShared.cs</Link>
-    </Compile>
     <Compile Include="..\Renci.SshNet.WindowsPhone\Session.WP.cs">
       <Link>Session.WP.cs</Link>
     </Compile>

+ 285 - 1
src/Renci.SshNet/Abstractions/SocketAbstraction.cs

@@ -1,4 +1,10 @@
-using System.Net.Sockets;
+using System;
+using System.Globalization;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Transport;
 
 namespace Renci.SshNet.Abstractions
 {
@@ -28,5 +34,283 @@ namespace Renci.SshNet.Abstractions
 
             return false;
         }
+
+        public static Socket Connect(IPEndPoint remoteEndpoint, TimeSpan connectTimeout)
+        {
+            var socket = new Socket(remoteEndpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp) {NoDelay = true};
+
+#if FEATURE_SOCKET_EAP
+            var connectCompleted = new ManualResetEvent(false);
+            var args = new SocketAsyncEventArgs
+            {
+                UserToken = connectCompleted,
+                RemoteEndPoint = remoteEndpoint
+            };
+            args.Completed += ConnectCompleted;
+
+            if (!socket.ConnectAsync(args))
+            {
+                if (!connectCompleted.WaitOne(connectTimeout))
+                    throw new SshOperationTimeoutException(string.Format(CultureInfo.InvariantCulture,
+                        "Connection failed to establish within {0:F0} milliseconds.", connectTimeout.TotalMilliseconds));
+            }
+
+            if (args.SocketError != SocketError.Success)
+                throw new SocketException((int) args.SocketError);
+            return socket;
+#elif FEATURE_SOCKET_APM
+            var connectResult = _socket.BeginConnect(ep, null, null);
+            if (!connectResult.AsyncWaitHandle.WaitOne(connectTimeout, false))
+                throw new SshOperationTimeoutException(string.Format(CultureInfo.InvariantCulture,
+                    "Connection failed to establish within {0:F0} milliseconds.", connectTimeout.TotalMilliseconds));
+
+            _socket.EndConnect(connectResult);
+#elif FEATURE_SOCKET_TAP
+            if (!_socket.ConnectAsync(ep).Wait(connectTimeout))
+                throw new SshOperationTimeoutException(string.Format(CultureInfo.InvariantCulture,
+                    "Connection failed to establish within {0:F0} milliseconds.", connectTimeout.TotalMilliseconds));
+#else
+            #error Connecting to a remote endpoint is not implemented.
+#endif
+        }
+
+        /// <summary>
+        /// Receives data from a bound <see cref="Socket"/>into a receive buffer.
+        /// </summary>
+        /// <param name="socket"></param>
+        /// <param name="buffer">An array of type <see cref="byte"/> that is the storage location for the received data. </param>
+        /// <param name="offset">The position in <paramref name="buffer"/> parameter to store the received data.</param>
+        /// <param name="size">The number of bytes to receive.</param>
+        /// <param name="timeout">Specifies the amount of time after which the call will time out.</param>
+        /// <returns>
+        /// The number of bytes received.
+        /// </returns>
+        /// <remarks>
+        /// If no data is available for reading, the <see cref="Read(Socket,byte[], int, int, TimeSpan)"/> method will
+        /// block until data is available or the time-out value was exceeded. If the time-out value was exceeded, the
+        /// <see cref="Read(Socket,byte[], int, int, TimeSpan)"/> call will throw a <see cref="SshOperationTimeoutException"/>.
+        ///  If you are in non-blocking mode, and there is no data available in the in the protocol stack buffer, the
+        /// <see cref="Read(Socket,byte[], int, int, TimeSpan)"/> method will complete immediately and throw a <see cref="SocketException"/>.
+        /// </remarks>
+        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);
+#elif FEATURE_SOCKET_EAP
+            var receiveCompleted = new ManualResetEvent(false);
+            var sendReceiveToken = new SendReceiveToken(socket, buffer, offset, size, receiveCompleted);
+
+            var args = new SocketAsyncEventArgs
+            {
+                UserToken = sendReceiveToken,
+                RemoteEndPoint = socket.RemoteEndPoint
+            };
+            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 sendReceiveToken.TotalBytesTransferred;
+            }
+            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
+        }
+
+        public static void Send(Socket socket, byte[] data)
+        {
+            Send(socket, data, 0, data.Length);
+        }
+
+        public static void Send(Socket socket, byte[] data, int offset, int size)
+        {
+#if FEATURE_SOCKET_SYNC
+            var totalBytesSent = 0;  // how many bytes are already sent
+            var totalBytesToSend = size;
+
+            do
+            {
+                try
+                {
+                    var bytesSent = socket.Send(data, offset + totalBytesSent, totalBytesToSend - totalBytesSent,
+                        SocketFlags.None);
+                    if (bytesSent == 0)
+                        throw new SshConnectionException("An established connection was aborted by the server.",
+                            DisconnectReason.ConnectionLost);
+
+                    totalBytesSent += bytesSent;
+                }
+                catch (SocketException ex)
+                {
+                    if (IsErrorResumable(ex.SocketErrorCode))
+                    {
+                        // socket buffer is probably full, wait and try again
+                        ThreadAbstraction.Sleep(30);
+                    }
+                    else
+                        throw;  // any serious error occurr
+                }
+            } while (totalBytesSent < totalBytesToSend);
+#elif FEATURE_SOCKET_EAP
+            var sendCompleted = new ManualResetEvent(false);
+            var sendReceiveToken = new SendReceiveToken(socket, data, offset, size, sendCompleted);
+            var socketAsyncSendArgs = new SocketAsyncEventArgs
+            {
+                RemoteEndPoint = socket.RemoteEndPoint,
+                UserToken = sendReceiveToken
+            };
+            socketAsyncSendArgs.SetBuffer(data, offset, size);
+            socketAsyncSendArgs.Completed += SendCompleted;
+
+            try
+            {
+                if (socket.SendAsync(socketAsyncSendArgs))
+                {
+                    if (!sendCompleted.WaitOne())
+                        throw new SocketException((int) SocketError.TimedOut);
+                }
+
+                if (socketAsyncSendArgs.SocketError != SocketError.Success)
+                    throw new SocketException((int) socketAsyncSendArgs.SocketError);
+
+                if (sendReceiveToken.TotalBytesTransferred == 0)
+                    throw new SshConnectionException("An established connection was aborted by the server.",
+                        DisconnectReason.ConnectionLost);
+            }
+            finally
+            {
+                // initialize token to avoid the completion waithandle getting used after it's disposed
+                socketAsyncSendArgs.UserToken = null;
+                socketAsyncSendArgs.Dispose();
+                sendCompleted.Dispose();
+            }
+#else
+#error Receiving data from a Socket is not implemented.
+#endif
+        }
+
+        private static bool IsErrorResumable(SocketError socketError)
+        {
+            switch (socketError)
+            {
+                case SocketError.WouldBlock:
+                case SocketError.IOPending:
+                case SocketError.NoBufferSpaceAvailable:
+                    return true;
+                default:
+                    return false;
+            }
+        }
+
+#if FEATURE_SOCKET_EAP
+        private static void ConnectCompleted(object sender, SocketAsyncEventArgs e)
+        {
+            var eventWaitHandle = (ManualResetEvent) e.UserToken;
+            if (eventWaitHandle != null)
+                eventWaitHandle.Set();
+        }
+#endif // FEATURE_SOCKET_EAP
+
+#if FEATURE_SOCKET_EAP && !FEATURE_SOCKET_SYNC
+        private static void ReceiveCompleted(object sender, SocketAsyncEventArgs e)
+        {
+            var sendReceiveToken = (SendReceiveToken) e.UserToken;
+            if (sendReceiveToken != null)
+                sendReceiveToken.Process(e);
+        }
+
+        private static void SendCompleted(object sender, SocketAsyncEventArgs e)
+        {
+            var sendReceiveToken = (SendReceiveToken)e.UserToken;
+            if (sendReceiveToken != null)
+                sendReceiveToken.Process(e);
+        }
+
+        private class SendReceiveToken
+        {
+            public SendReceiveToken(Socket socket, byte[] buffer, int offset, int size, EventWaitHandle completionWaitHandle)
+            {
+                _socket = socket;
+                _buffer = buffer;
+                _offset = offset;
+                _bytesToTransfer = size;
+                _completionWaitHandle = completionWaitHandle;
+            }
+
+            public void Process(SocketAsyncEventArgs args)
+            {
+                if (args.SocketError == SocketError.Success)
+                {
+                    TotalBytesTransferred += args.BytesTransferred;
+
+                    if (TotalBytesTransferred == _bytesToTransfer)
+                    {
+                        // finished transferring specified bytes
+                        _completionWaitHandle.Set();
+                        return;
+                    }
+
+                    if (args.BytesTransferred == 0)
+                    {
+                        // remote server closed the connection
+                        _completionWaitHandle.Set();
+                        return;
+                    }
+
+                    _offset += args.BytesTransferred;
+                    args.SetBuffer(_buffer, _offset, _bytesToTransfer - TotalBytesTransferred);
+                    ResumeOperation(args);
+                    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 int _bytesToTransfer;
+            public int TotalBytesTransferred { get; private set; }
+            private readonly EventWaitHandle _completionWaitHandle;
+            private readonly Socket _socket;
+            private readonly byte[] _buffer;
+            private int _offset;
+        }
+#endif // FEATURE_SOCKET_EAP && !FEATURE_SOCKET_SYNC
     }
 }

+ 0 - 20
src/Renci.SshNet/Channels/ChannelDirectTcpip.NET40.cs

@@ -1,20 +0,0 @@
-using System.Net.Sockets;
-
-namespace Renci.SshNet.Channels
-{
-    /// <summary>
-    /// Implements "direct-tcpip" SSH channel.
-    /// </summary>
-    internal partial class ChannelDirectTcpip 
-    {
-        partial void InternalSocketReceive(byte[] buffer, ref int read)
-        {
-            read = _socket.Receive(buffer);
-        }
-
-        partial void InternalSocketSend(byte[] data)
-        {
-            this._socket.Send(data, 0, data.Length, SocketFlags.None);
-        }
-    }
-}

+ 2 - 7
src/Renci.SshNet/Channels/ChannelDirectTcpip.cs

@@ -91,8 +91,7 @@ namespace Renci.SshNet.Channels
             {
                 try
                 {
-                    var read = 0;
-                    InternalSocketReceive(buffer, ref read);
+                    var read = SocketAbstraction.Read(_socket, buffer, 0, buffer.Length, ConnectionInfo.Timeout);
                     if (read > 0)
                     {
 #if TUNING
@@ -217,7 +216,7 @@ namespace Renci.SshNet.Channels
                 {
                     if (_socket != null && _socket.Connected)
                     {
-                        InternalSocketSend(data);
+                        SocketAbstraction.Send(_socket, data, 0, data.Length);
                     }
                 }
             }
@@ -294,10 +293,6 @@ namespace Renci.SshNet.Channels
             ShutdownSocket(SocketShutdown.Both);
         }
 
-        partial void InternalSocketReceive(byte[] buffer, ref int read);
-
-        partial void InternalSocketSend(byte[] data);
-
         protected override void Dispose(bool disposing)
         {
             // make sure we've unsubscribed from all session events and closed the channel

+ 0 - 28
src/Renci.SshNet/Channels/ChannelForwardedTcpip.NET40.cs

@@ -1,28 +0,0 @@
-using System.Net;
-using System.Net.Sockets;
-
-namespace Renci.SshNet.Channels
-{
-    /// <summary>
-    /// Implements "forwarded-tcpip" SSH channel.
-    /// </summary>
-    internal partial class ChannelForwardedTcpip
-    {
-        partial void OpenSocket(IPEndPoint remoteEndpoint)
-        {
-            _socket = new Socket(remoteEndpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
-            _socket.Connect(remoteEndpoint);
-            _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, 1);
-        }
-
-        partial void InternalSocketReceive(byte[] buffer, ref int read)
-        {
-            read = _socket.Receive(buffer);
-        }
-
-        partial void InternalSocketSend(byte[] data)
-        {
-            _socket.Send(data);
-        }
-    }
-}

+ 3 - 11
src/Renci.SshNet/Channels/ChannelForwardedTcpip.cs

@@ -65,7 +65,7 @@ namespace Renci.SshNet.Channels
                 //  Get buffer in memory for data exchange
                 buffer = new byte[RemotePacketSize];
 
-                OpenSocket(remoteEndpoint);
+                _socket = SocketAbstraction.Connect(remoteEndpoint, ConnectionInfo.Timeout);
 
                 // send channel open confirmation message
                 SendMessage(new ChannelOpenConfirmationMessage(RemoteChannelNumber, LocalWindowSize, LocalPacketSize, LocalChannelNumber));
@@ -83,9 +83,7 @@ namespace Renci.SshNet.Channels
                 {
                 try
                 {
-                    var read = 0;
-                    InternalSocketReceive(buffer, ref read);
-
+                    var read = SocketAbstraction.Read(_socket, buffer, 0, buffer.Length, ConnectionInfo.Timeout);
                     if (read > 0)
                     {
 #if TUNING
@@ -142,8 +140,6 @@ namespace Renci.SshNet.Channels
             ShutdownSocket(SocketShutdown.Send);
         }
 
-        partial void OpenSocket(IPEndPoint remoteEndpoint);
-
         /// <summary>
         /// Shuts down the socket.
         /// </summary>
@@ -217,13 +213,9 @@ namespace Renci.SshNet.Channels
             base.OnData(data);
 
             if (_socket != null && _socket.Connected)
-                InternalSocketSend(data);
+                SocketAbstraction.Send(_socket, data, 0, data.Length);
         }
 
-        partial void InternalSocketSend(byte[] data);
-        
-        partial void InternalSocketReceive(byte[] buffer, ref int read);
-
         /// <summary>
         /// Releases unmanaged and - optionally - managed resources
         /// </summary>

+ 1 - 3
src/Renci.SshNet/Renci.SshNet.csproj

@@ -18,7 +18,7 @@
     <DebugType>full</DebugType>
     <Optimize>false</Optimize>
     <OutputPath>bin\Debug\</OutputPath>
-    <DefineConstants>TRACE;DEBUG;TUNING;FEATURE_RNG_CSP;FEATURE_SOCKET_EAP;FEATURE_SOCKET_POLL;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</DefineConstants>
+    <DefineConstants>TRACE;DEBUG;TUNING;FEATURE_RNG_CSP;FEATURE_SOCKET_SYNC;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</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <DocumentationFile>bin\Debug\Renci.SshNet.xml</DocumentationFile>
@@ -64,9 +64,7 @@
     <Compile Include="CommandAsyncResult.cs" />
     <Compile Include="Channels\Channel.cs" />
     <Compile Include="Channels\ChannelDirectTcpip.cs" />
-    <Compile Include="Channels\ChannelDirectTcpip.NET40.cs" />
     <Compile Include="Channels\ChannelForwardedTcpip.cs" />
-    <Compile Include="Channels\ChannelForwardedTcpip.NET40.cs" />
     <Compile Include="Channels\ChannelSession.cs" />
     <Compile Include="Channels\ChannelTypes.cs" />
     <Compile Include="Channels\ClientChannel.cs" />

+ 1 - 446
src/Renci.SshNet/Session.NET.cs

@@ -1,23 +1,10 @@
-using System.Globalization;
-using System.Linq;
-using System;
-using System.Net.Sockets;
-using System.Net;
-using Renci.SshNet.Common;
-using Renci.SshNet.Messages.Transport;
+using System.Net.Sockets;
 using System.Diagnostics;
-using System.Collections.Generic;
-using System.Threading;
-using Renci.SshNet.Abstractions;
 
 namespace Renci.SshNet
 {
     public partial class Session
     {
-        private const byte Null = 0x00;
-        private const byte CarriageReturn = 0x0d;
-        private const byte LineFeed = 0x0a;
-
 #if FEATURE_DIAGNOSTICS_TRACESOURCE
         private readonly TraceSource _log =
 #if DEBUG
@@ -92,64 +79,6 @@ namespace Renci.SshNet
             }
         }
 
-        /// <summary>
-        /// Establishes a socket connection to the specified host and port.
-        /// </summary>
-        /// <param name="host">The host name of the server to connect to.</param>
-        /// <param name="port">The port to connect to.</param>
-        /// <exception cref="SshOperationTimeoutException">The connection failed to establish within the configured <see cref="Renci.SshNet.ConnectionInfo.Timeout"/>.</exception>
-        /// <exception cref="SocketException">An error occurred trying to establish the connection.</exception>
-        partial void SocketConnect(string host, int port)
-        {
-            const int socketBufferSize = 2 * MaximumSshPacketSize;
-
-            var ipAddress = DnsAbstraction.GetHostAddresses(host)[0];
-            var timeout = ConnectionInfo.Timeout;
-            var ep = new IPEndPoint(ipAddress, port);
-
-            _socket = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
-            _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
-            _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendBuffer, socketBufferSize);
-            _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer, socketBufferSize);
-
-            Log(string.Format("Initiating connect to '{0}:{1}'.", ConnectionInfo.Host, ConnectionInfo.Port));
-
-#if FEATURE_SOCKET_EAP
-            var connectCompleted = new ManualResetEvent(false);
-            var connectAsyncEventArgs = new SocketAsyncEventArgs
-                {
-                    RemoteEndPoint = ep,
-                };
-            connectAsyncEventArgs.Completed += (sender, args) => { connectCompleted.Set(); };
-
-            if (_socket.ConnectAsync(connectAsyncEventArgs))
-            {
-                if (!connectCompleted.WaitOne(timeout, false))
-                    throw new SshOperationTimeoutException(
-                        string.Format(
-                            CultureInfo.InvariantCulture,
-                            "Connection failed to establish within {0:F0} milliseconds.",
-                            timeout.TotalMilliseconds));
-            }
-
-            if (connectAsyncEventArgs.SocketError != SocketError.Success)
-                throw new SocketException((int) connectAsyncEventArgs.SocketError);
-#elif FEATURE_SOCKET_APM
-            var connectResult = _socket.BeginConnect(ep, null, null);
-            if (!connectResult.AsyncWaitHandle.WaitOne(timeout, false))
-                throw new SshOperationTimeoutException(string.Format(CultureInfo.InvariantCulture,
-                    "Connection failed to establish within {0:F0} milliseconds.", timeout.TotalMilliseconds));
-
-            _socket.EndConnect(connectResult);
-#elif FEATURE_SOCKET_TAP
-            if (!_socket.ConnectAsync(ep).Wait(timeout))
-                throw new SshOperationTimeoutException(string.Format(CultureInfo.InvariantCulture,
-                    "Connection failed to establish within {0:F0} milliseconds.", timeout.TotalMilliseconds));
-#else
-            #error Connecting socket is not implemented.
-#endif
-        }
-
         /// <summary>
         /// Closes the socket and allows the socket to be reused after the current connection is closed.
         /// </summary>
@@ -160,184 +89,6 @@ namespace Renci.SshNet
             _socket.Dispose();
         }
 
-        /// <summary>
-        /// Performs a blocking read on the socket until a line is read.
-        /// </summary>
-        /// <param name="response">The line read from the socket, or <c>null</c> when the remote server has shutdown and all data has been received.</param>
-        /// <param name="timeout">A <see cref="TimeSpan"/> that represents the time to wait until a line is read.</param>
-        /// <exception cref="SshOperationTimeoutException">The read has timed-out.</exception>
-        /// <exception cref="SocketException">An error occurred when trying to access the socket.</exception>
-        partial void SocketReadLine(ref string response, TimeSpan timeout)
-        {
-            var buffer = new List<byte>();
-            var data = new byte[1];
-
-#if FEATURE_SOCKET_EAP
-            var receiveCompleted = new AutoResetEvent(false);
-            var receiveAsyncEventArgs = new SocketAsyncEventArgs { SocketFlags = SocketFlags.None };
-            receiveAsyncEventArgs.Completed += (sender, args) => receiveCompleted.Set();
-            receiveAsyncEventArgs.SetBuffer(data, 0, data.Length);
-#endif // FEATURE_SOCKET_EAP
-
-            try
-            {
-                // read data one byte at a time to find end of line and leave any unhandled information in the buffer
-                // to be processed by subsequent invocations
-                do
-                {
-#if FEATURE_SOCKET_EAP
-                    if (_socket.ReceiveAsync(receiveAsyncEventArgs))
-                    {
-                        if (!receiveCompleted.WaitOne(timeout))
-                            throw new SshOperationTimeoutException(
-                                string.Format(
-                                    CultureInfo.InvariantCulture,
-                                    "Socket read operation has timed out after {0:F0} milliseconds.",
-                                    timeout.TotalMilliseconds));
-                    }
-
-                    var received = receiveAsyncEventArgs.BytesTransferred;
-#elif FEATURE_SOCKET_TAP
-                    var receiveTask = _socket.ReceiveAsync(new ArraySegment<byte>(data, 0, data.Length), SocketFlags.None);
-                    if (!receiveTask.Wait(timeout))
-                        throw new SshOperationTimeoutException(string.Format(CultureInfo.InvariantCulture,
-                            "Socket read operation has timed out after {0:F0} milliseconds.", timeout.TotalMilliseconds));
-
-                    var received = receiveTask.Result;
-    #elif FEATURE_SOCKET_APM
-                    var asyncResult = _socket.BeginReceive(data, 0, data.Length, SocketFlags.None, null, null);
-                    if (!asyncResult.AsyncWaitHandle.WaitOne(timeout))
-                        throw new SshOperationTimeoutException(string.Format(CultureInfo.InvariantCulture,
-                            "Socket read operation has timed out after {0:F0} milliseconds.", timeout.TotalMilliseconds));
-
-                    var received = _socket.EndReceive(asyncResult);
-    #else
-                    #error Receiving from socket is not implemented.
-    #endif
-
-                    if (received == 0)
-                        // the remote server shut down the socket
-                        break;
-
-                    buffer.Add(data[0]);
-                }
-                while (!(buffer.Count > 0 && (buffer[buffer.Count - 1] == LineFeed || buffer[buffer.Count - 1] == Null)));
-
-                if (buffer.Count == 0)
-                    response = null;
-                else if (buffer.Count == 1 && buffer[buffer.Count - 1] == 0x00)
-                    // return an empty version string if the buffer consists of only a 0x00 character
-                    response = string.Empty;
-                else if (buffer.Count > 1 && buffer[buffer.Count - 2] == CarriageReturn)
-                    // strip trailing CRLF
-                    response = SshData.Ascii.GetString(buffer.Take(buffer.Count - 2).ToArray());
-                else if (buffer.Count > 1 && buffer[buffer.Count - 1] == LineFeed)
-                    // strip trailing LF
-                    response = SshData.Ascii.GetString(buffer.Take(buffer.Count - 1).ToArray());
-                else
-                    response = SshData.Ascii.GetString(buffer.ToArray());
-            }
-            finally
-            {
-#if FEATURE_SOCKET_EAP
-                receiveAsyncEventArgs.Dispose();
-                receiveCompleted.Dispose();
-#endif // FEATURE_SOCKET_EAP
-            }
-        }
-
-        /// <summary>
-        /// Performs a blocking read on the socket until <paramref name="length"/> bytes are received.
-        /// </summary>
-        /// <param name="length">The number of bytes to read.</param>
-        /// <param name="buffer">The buffer to read to.</param>
-        /// <exception cref="SshConnectionException">The socket is closed.</exception>
-        /// <exception cref="SocketException">The read failed.</exception>
-        partial void SocketRead(int length, ref byte[] buffer)
-        {
-            var receivedTotal = 0;  // how many bytes is already received
-
-            do
-            {
-                try
-                {
-                    var receivedBytes = _socket.Receive(buffer, receivedTotal, length - receivedTotal, SocketFlags.None);
-                    if (receivedBytes > 0)
-                    {
-                        // signal that bytes have been read from the socket
-                        // this is used to improve accuracy of Session.IsSocketConnected
-                        _bytesReadFromSocket.Set();
-                        receivedTotal += receivedBytes;
-                        continue;
-                    }
-
-                    // 2012-09-11: Kenneth_aa
-                    // When Disconnect or Dispose is called, this throws SshConnectionException(), which...
-                    // 1 - goes up to ReceiveMessage() 
-                    // 2 - up again to MessageListener()
-                    // which is where there is a catch-all exception block so it can notify event listeners.
-                    // 3 - MessageListener then again calls RaiseError().
-                    // There the exception is checked for the exception thrown here (ConnectionLost), and if it matches it will not call Session.SendDisconnect().
-                    //
-                    // Adding a check for _isDisconnecting causes ReceiveMessage() to throw SshConnectionException: "Bad packet length {0}".
-                    //
-
-                    if (_isDisconnecting)
-                        throw new SshConnectionException("An established connection was aborted by the software in your host machine.", DisconnectReason.ConnectionLost);
-                    throw new SshConnectionException("An established connection was aborted by the server.", DisconnectReason.ConnectionLost);
-                }
-                catch (SocketException exp)
-                {
-                    if (exp.SocketErrorCode == SocketError.WouldBlock ||
-                        exp.SocketErrorCode == SocketError.IOPending ||
-                        exp.SocketErrorCode == SocketError.NoBufferSpaceAvailable)
-                    {
-                        // socket buffer is probably empty, wait and try again
-                        ThreadAbstraction.Sleep(30);
-                    }
-                    else
-                    {
-                        throw new SshConnectionException(exp.Message, DisconnectReason.ConnectionLost, exp);
-                    }
-                }
-            } while (receivedTotal < length);
-        }
-
-        /// <summary>
-        /// Writes the specified data to the server.
-        /// </summary>
-        /// <param name="data">The data to write to the server.</param>
-        /// <param name="offset">The zero-based offset in <paramref name="data"/> at which to begin taking data from.</param>
-        /// <param name="length">The number of bytes of <paramref name="data"/> to write.</param>
-        /// <exception cref="SshOperationTimeoutException">The write has timed-out.</exception>
-        /// <exception cref="SocketException">The write failed.</exception>
-        private void SocketWrite(byte[] data, int offset, int length)
-        {
-            var totalBytesSent = 0;  // how many bytes are already sent
-            var totalBytesToSend = length;
-
-            do
-            {
-                try
-                {
-                    totalBytesSent += _socket.Send(data, offset + totalBytesSent, totalBytesToSend - totalBytesSent,
-                        SocketFlags.None);
-                }
-                catch (SocketException ex)
-                {
-                    if (ex.SocketErrorCode == SocketError.WouldBlock ||
-                        ex.SocketErrorCode == SocketError.IOPending ||
-                        ex.SocketErrorCode == SocketError.NoBufferSpaceAvailable)
-                    {
-                        // socket buffer is probably full, wait and try again
-                        ThreadAbstraction.Sleep(30);
-                    }
-                    else
-                        throw;  // any serious error occurr
-                }
-            } while (totalBytesSent < totalBytesToSend);
-        }
-
         [Conditional("DEBUG")]
         partial void Log(string text)
         {
@@ -345,201 +96,5 @@ namespace Renci.SshNet
             _log.TraceEvent(TraceEventType.Verbose, 1, text);
 #endif // FEATURE_DIAGNOSTICS_TRACESOURCE
         }
-
-#if ASYNC_SOCKET_READ
-        private void SocketRead(int length, ref byte[] buffer)
-        {
-            var state = new SocketReadState(_socket, length, ref buffer);
-
-            _socket.BeginReceive(buffer, 0, length, SocketFlags.None, SocketReceiveCallback, state);
-
-            var readResult = state.Wait();
-            switch (readResult)
-            {
-                case SocketReadResult.Complete:
-                    break;
-                case SocketReadResult.ConnectionLost:
-                    if (_isDisconnecting)
-                        throw new SshConnectionException(
-                            "An established connection was aborted by the software in your host machine.",
-                            DisconnectReason.ConnectionLost);
-                    throw new SshConnectionException("An established connection was aborted by the server.",
-                        DisconnectReason.ConnectionLost);
-                case SocketReadResult.Failed:
-                    var socketException = state.Exception as SocketException;
-                    if (socketException != null)
-                    {
-                        if (socketException.SocketErrorCode == SocketError.ConnectionAborted)
-                        {
-                            buffer = new byte[length];
-                            Disconnect();
-                            return;
-                        }
-                    }
-                    throw state.Exception;
-            }
-        }
-
-        private void SocketReceiveCallback(IAsyncResult ar)
-        {
-            var state = ar.AsyncState as SocketReadState;
-            var socket = state.Socket;
-
-            try
-            {
-                var bytesReceived = socket.EndReceive(ar);
-                if (bytesReceived > 0)
-                {
-                    _bytesReadFromSocket.Set();
-                    state.BytesRead += bytesReceived;
-                    if (state.BytesRead < state.TotalBytesToRead)
-                    {
-                        socket.BeginReceive(state.Buffer, state.BytesRead, state.TotalBytesToRead - state.BytesRead,
-                            SocketFlags.None, SocketReceiveCallback, state);
-                    }
-                    else
-                    {
-                        // we received all bytes that we wanted, so lets mark the read
-                        // complete
-                        state.Complete();
-                    }
-                }
-                else
-                {
-                    // the remote host shut down the connection; this could also have been
-                    // triggered by a SSH_MSG_DISCONNECT sent by the client
-                    state.ConnectionLost();
-                }
-            }
-            catch (SocketException ex)
-            {
-                if (ex.SocketErrorCode != SocketError.ConnectionAborted)
-                {
-                    if (ex.SocketErrorCode == SocketError.WouldBlock ||
-                        ex.SocketErrorCode == SocketError.IOPending ||
-                        ex.SocketErrorCode == SocketError.NoBufferSpaceAvailable)
-                    {
-                        // socket buffer is probably empty, wait and try again
-                        Thread.Sleep(30);
-
-                        socket.BeginReceive(state.Buffer, state.BytesRead, state.TotalBytesToRead - state.BytesRead,
-                            SocketFlags.None, SocketReceiveCallback, state);
-                        return;
-                    }
-                }
-
-                state.Fail(ex);
-            }
-            catch (Exception ex)
-            {
-                state.Fail(ex);
-            }
-        }
-
-        private class SocketReadState
-        {
-            private SocketReadResult _result;
-
-            /// <summary>
-            /// WaitHandle to signal that read from socket has completed (either successfully
-            /// or with failure)
-            /// </summary>
-            private EventWaitHandle _socketReadComplete;
-
-            public SocketReadState(Socket socket, int totalBytesToRead, ref byte[] buffer)
-            {
-                Socket = socket;
-                TotalBytesToRead = totalBytesToRead;
-                Buffer = buffer;
-                _socketReadComplete = new ManualResetEvent(false);
-            }
-
-            /// <summary>
-            /// Gets the <see cref="Socket"/> to read from.
-            /// </summary>
-            /// <value>
-            /// The <see cref="Socket"/> to read from.
-            /// </value>
-            public Socket Socket { get; private set; }
-
-            /// <summary>
-            /// Gets or sets the number of bytes that have been read from the <see cref="Socket"/>.
-            /// </summary>
-            /// <value>
-            /// The number of bytes that have been read from the <see cref="Socket"/>.
-            /// </value>
-            public int BytesRead { get; set; }
-
-            /// <summary>
-            /// Gets the total number of bytes to read from the <see cref="Socket"/>.
-            /// </summary>
-            /// <value>
-            /// The total number of bytes to read from the <see cref="Socket"/>.
-            /// </value>
-            public int TotalBytesToRead { get; private set; }
-
-            /// <summary>
-            /// Gets or sets the buffer to hold the bytes that have been read.
-            /// </summary>
-            /// <value>
-            /// The buffer to hold the bytes that have been read.
-            /// </value>
-            public byte[] Buffer { get; private set; }
-
-            /// <summary>
-            /// Gets or sets the exception that was thrown while reading from the
-            /// <see cref="Socket"/>.
-            /// </summary>
-            /// <value>
-            /// The exception that was thrown while reading from the <see cref="Socket"/>,
-            /// or <c>null</c> if no exception was thrown.
-            /// </value>
-            public Exception Exception { get; private set; }
-
-            /// <summary>
-            /// Signals that the total number of bytes has been read successfully.
-            /// </summary>
-            public void Complete()
-            {
-                _result = SocketReadResult.Complete;
-                _socketReadComplete.Set();
-            }
-
-            /// <summary>
-            /// Signals that the socket read failed.
-            /// </summary>
-            /// <param name="cause">The <see cref="Exception"/> that caused the read to fail.</param>
-            public void Fail(Exception cause)
-            {
-                Exception = cause;
-                _result = SocketReadResult.Failed;
-                _socketReadComplete.Set();
-            }
-
-            /// <summary>
-            /// Signals that the connection to the server was lost.
-            /// </summary>
-            public void ConnectionLost()
-            {
-                _result = SocketReadResult.ConnectionLost;
-                _socketReadComplete.Set();
-            }
-
-            public SocketReadResult Wait()
-            {
-                _socketReadComplete.WaitOne();
-                _socketReadComplete.Dispose();
-                _socketReadComplete = null;
-                return _result;
-            }
-        }
-
-        private enum SocketReadResult
-        {
-            Complete,
-            ConnectionLost,
-            Failed
-        }
-#endif
     }
 }

+ 170 - 32
src/Renci.SshNet/Session.cs

@@ -26,6 +26,10 @@ namespace Renci.SshNet
     /// </summary>
     public partial class Session : ISession
     {
+        private const byte Null = 0x00;
+        private const byte CarriageReturn = 0x0d;
+        private const byte LineFeed = 0x0a;
+
         /// <summary>
         /// Specifies an infinite waiting period.
         /// </summary>
@@ -550,8 +554,7 @@ namespace Renci.SshNet
                     //  ignore text lines which are sent before if any
                     while (true)
                     {
-                        var serverVersion = string.Empty;
-                        SocketReadLine(ref serverVersion, ConnectionInfo.Timeout);
+                        var serverVersion = SocketReadLine(ConnectionInfo.Timeout);
                         if (serverVersion == null)
                             throw new SshConnectionException("Server response does not contain SSH protocol identification.", DisconnectReason.ProtocolError);
                         versionMatch = ServerVersionRe.Match(serverVersion);
@@ -578,7 +581,7 @@ namespace Renci.SshNet
                         throw new SshConnectionException(string.Format(CultureInfo.CurrentCulture, "Server version '{0}' is not supported.", version), DisconnectReason.ProtocolVersionNotSupported);
                     }
 
-                    SocketWrite(Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "{0}\x0D\x0A", ClientVersion)));
+                    SocketAbstraction.Send(_socket, Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "{0}\x0D\x0A", ClientVersion)));
 
                     //  Register Transport response messages
                     RegisterMessage("SSH_MSG_DISCONNECT");
@@ -862,12 +865,12 @@ namespace Renci.SshNet
                 var packetLength = packetData.Length - packetDataOffset;
                 if (hash == null)
                 {
-                    SocketWrite(packetData, packetDataOffset, packetLength);
+                    SocketAbstraction.Send(_socket, packetData, packetDataOffset, packetLength);
                 }
 #else
                 if (_clientMac == null)
                 {
-                    SocketWrite(packetData);
+                    SocketAbstraction.Send(_socket, packetData, 0, packetData.Length);
                 }
 #endif
                 else
@@ -884,7 +887,7 @@ namespace Renci.SshNet
                     hash.CopyTo(data, packetData.Length);
 #endif
 
-                    SocketWrite(data);
+                    SocketAbstraction.Send(_socket, data, 0, data.Length);
                 }
 
                 _outboundPacketSequence++;
@@ -1122,32 +1125,50 @@ namespace Renci.SshNet
 
         #region Handle transport messages
 
+        /// <summary>
+        /// Invoked via reflection.
+        /// </summary>
         private void HandleMessage(DisconnectMessage message)
         {
             OnDisconnectReceived(message);
             Disconnect(message.ReasonCode, message.Description);
         }
 
+        /// <summary>
+        /// Invoked via reflection.
+        /// </summary>
         private void HandleMessage(IgnoreMessage message)
         {
             OnIgnoreReceived(message);
         }
 
+        /// <summary>
+        /// Invoked via reflection.
+        /// </summary>
         private void HandleMessage(UnimplementedMessage message)
         {
             OnUnimplementedReceived(message);
         }
 
+        /// <summary>
+        /// Invoked via reflection.
+        /// </summary>
         private void HandleMessage(DebugMessage message)
         {
             OnDebugReceived(message);
         }
 
+        /// <summary>
+        /// Invoked via reflection.
+        /// </summary>
         private void HandleMessage(ServiceRequestMessage message)
         {
             OnServiceRequestReceived(message);
         }
 
+        /// <summary>
+        /// Invoked via reflection.
+        /// </summary>
         private void HandleMessage(ServiceAcceptMessage message)
         {
             //  TODO:   Refactor to avoid this method here
@@ -1156,11 +1177,17 @@ namespace Renci.SshNet
             _serviceAccepted.Set();
         }
 
+        /// <summary>
+        /// Invoked via reflection.
+        /// </summary>
         private void HandleMessage(KeyExchangeInitMessage message)
         {
             OnKeyExchangeInitReceived(message);
         }
 
+        /// <summary>
+        /// Invoked via reflection.
+        /// </summary>
         private void HandleMessage(NewKeysMessage message)
         {
             OnNewKeysReceived(message);
@@ -1170,21 +1197,33 @@ namespace Renci.SshNet
 
         #region Handle User Authentication messages
 
+        /// <summary>
+        /// Invoked via reflection.
+        /// </summary>
         private void HandleMessage(RequestMessage message)
         {
             OnUserAuthenticationRequestReceived(message);
         }
 
+        /// <summary>
+        /// Invoked via reflection.
+        /// </summary>
         private void HandleMessage(FailureMessage message)
         {
             OnUserAuthenticationFailureReceived(message);
         }
 
+        /// <summary>
+        /// Invoked via reflection.
+        /// </summary>
         private void HandleMessage(SuccessMessage message)
         {
             OnUserAuthenticationSuccessReceived(message);
         }
 
+        /// <summary>
+        /// Invoked via reflection.
+        /// </summary>
         private void HandleMessage(BannerMessage message)
         {
             OnUserAuthenticationBannerReceived(message);
@@ -1194,71 +1233,113 @@ namespace Renci.SshNet
 
         #region Handle connection messages
 
+        /// <summary>
+        /// Invoked via reflection.
+        /// </summary>
         private void HandleMessage(GlobalRequestMessage message)
         {
             OnGlobalRequestReceived(message);
         }
 
+        /// <summary>
+        /// Invoked via reflection.
+        /// </summary>
         private void HandleMessage(RequestSuccessMessage message)
         {
             OnRequestSuccessReceived(message);
         }
 
+        /// <summary>
+        /// Invoked via reflection.
+        /// </summary>
         private void HandleMessage(RequestFailureMessage message)
         {
             OnRequestFailureReceived(message);
         }
 
+        /// <summary>
+        /// Invoked via reflection.
+        /// </summary>
         private void HandleMessage(ChannelOpenMessage message)
         {
             OnChannelOpenReceived(message);
         }
 
+        /// <summary>
+        /// Invoked via reflection.
+        /// </summary>
         private void HandleMessage(ChannelOpenConfirmationMessage message)
         {
             OnChannelOpenConfirmationReceived(message);
         }
 
+        /// <summary>
+        /// Invoked via reflection.
+        /// </summary>
         private void HandleMessage(ChannelOpenFailureMessage message)
         {
             OnChannelOpenFailureReceived(message);
         }
 
+        /// <summary>
+        /// Invoked via reflection.
+        /// </summary>
         private void HandleMessage(ChannelWindowAdjustMessage message)
         {
             OnChannelWindowAdjustReceived(message);
         }
 
+        /// <summary>
+        /// Invoked via reflection.
+        /// </summary>
         private void HandleMessage(ChannelDataMessage message)
         {
             OnChannelDataReceived(message);
         }
 
+        /// <summary>
+        /// Invoked via reflection.
+        /// </summary>
         private void HandleMessage(ChannelExtendedDataMessage message)
         {
             OnChannelExtendedDataReceived(message);
         }
 
+        /// <summary>
+        /// Invoked via reflection.
+        /// </summary>
         private void HandleMessage(ChannelEofMessage message)
         {
             OnChannelEofReceived(message);
         }
 
+        /// <summary>
+        /// Invoked via reflection.
+        /// </summary>
         private void HandleMessage(ChannelCloseMessage message)
         {
             OnChannelCloseReceived(message);
         }
 
+        /// <summary>
+        /// Invoked via reflection.
+        /// </summary>
         private void HandleMessage(ChannelRequestMessage message)
         {
             OnChannelRequestReceived(message);
         }
 
+        /// <summary>
+        /// Invoked via reflection.
+        /// </summary>
         private void HandleMessage(ChannelSuccessMessage message)
         {
             OnChannelSuccessReceived(message);
         }
 
+        /// <summary>
+        /// Invoked via reflection.
+        /// </summary>
         private void HandleMessage(ChannelFailureMessage message)
         {
             OnChannelFailureReceived(message);
@@ -1764,7 +1845,21 @@ namespace Renci.SshNet
         /// <param name="port">The port to connect to.</param>
         /// <exception cref="SshOperationTimeoutException">The connection failed to establish within the configured <see cref="Renci.SshNet.ConnectionInfo.Timeout"/>.</exception>
         /// <exception cref="SocketException">An error occurred trying to establish the connection.</exception>
-        partial void SocketConnect(string host, int port);
+        private void SocketConnect(string host, int port)
+        {
+            var ipAddress = DnsAbstraction.GetHostAddresses(host)[0];
+            var ep = new IPEndPoint(ipAddress, port);
+
+            Log(string.Format("Initiating connect to '{0}:{1}'.", host, port));
+
+            _socket = SocketAbstraction.Connect(ep, ConnectionInfo.Timeout);
+
+#if FEATURE_SOCKET_SETSOCKETOPTION
+            const int socketBufferSize = 2 * MaximumSshPacketSize;
+            _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendBuffer, socketBufferSize);
+            _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer, socketBufferSize);
+#endif // FEATURE_SOCKET_SETSOCKETOPTION
+        }
 
         /// <summary>
         /// Closes the socket.
@@ -1780,30 +1875,74 @@ namespace Renci.SshNet
         /// <exception cref="SshConnectionException">The socket is closed.</exception>
         /// <exception cref="SshOperationTimeoutException">The read has timed-out.</exception>
         /// <exception cref="SocketException">The read failed.</exception>
-        partial void SocketRead(int length, ref byte[] buffer);
+        void SocketRead(int length, ref byte[] buffer)
+        {
+            if (SocketAbstraction.Read(_socket, buffer, 0, length, ConnectionInfo.Timeout) > 0)
+                return;
+
+            // 2012-09-11: Kenneth_aa
+            // When Disconnect or Dispose is called, this throws SshConnectionException(), which...
+            // 1 - goes up to ReceiveMessage() 
+            // 2 - up again to MessageListener()
+            // which is where there is a catch-all exception block so it can notify event listeners.
+            // 3 - MessageListener then again calls RaiseError().
+            // There the exception is checked for the exception thrown here (ConnectionLost), and if it matches it will not call Session.SendDisconnect().
+            //
+            // Adding a check for _isDisconnecting causes ReceiveMessage() to throw SshConnectionException: "Bad packet length {0}".
+            //
+
+            if (_isDisconnecting)
+                throw new SshConnectionException(
+                    "An established connection was aborted by the software in your host machine.",
+                    DisconnectReason.ConnectionLost);
+            throw new SshConnectionException("An established connection was aborted by the server.",
+                DisconnectReason.ConnectionLost);
+        }
 
         /// <summary>
         /// Performs a blocking read on the socket until a line is read.
         /// </summary>
-        /// <param name="response">The line read from the socket, or <c>null</c> when the remote server has shutdown and all data has been received.</param>
         /// <param name="timeout">A <see cref="TimeSpan"/> that represents the time to wait until a line is read.</param>
         /// <exception cref="SshOperationTimeoutException">The read has timed-out.</exception>
         /// <exception cref="SocketException">An error occurred when trying to access the socket.</exception>
-        partial void SocketReadLine(ref string response, TimeSpan timeout);
+        /// <returns>
+        /// The line read from the socket, or <c>null</c> when the remote server has shutdown and all data has been received.
+        /// </returns>
+        private string SocketReadLine(TimeSpan timeout)
+        {
+            var encoding = SshData.Ascii;
+            var buffer = new List<byte>();
+            var data = new byte[1];
 
-        partial void Log(string text);
+            // read data one byte at a time to find end of line and leave any unhandled information in the buffer
+            // to be processed by subsequent invocations
+            do
+            {
+                var bytesRead = SocketAbstraction.Read(_socket, data, 0, data.Length, timeout);
+                if (bytesRead == 0)
+                    // the remote server shut down the socket
+                    break;
 
-        /// <summary>
-        /// Writes the specified data to the server.
-        /// </summary>
-        /// <param name="data">The data to write to the server.</param>
-        /// <exception cref="SshOperationTimeoutException">The write has timed-out.</exception>
-        /// <exception cref="SocketException">The write failed.</exception>
-        private void SocketWrite(byte[] data)
-        {
-            SocketWrite(data, 0, data.Length);
+                buffer.Add(data[0]);
+            }
+            while (!(buffer.Count > 0 && (buffer[buffer.Count - 1] == LineFeed || buffer[buffer.Count - 1] == Null)));
+
+            if (buffer.Count == 0)
+                return null;
+            if (buffer.Count == 1 && buffer[buffer.Count - 1] == 0x00)
+                // return an empty version string if the buffer consists of only a 0x00 character
+                return string.Empty;
+            if (buffer.Count > 1 && buffer[buffer.Count - 2] == CarriageReturn)
+                // strip trailing CRLF
+                return encoding.GetString(buffer.ToArray(), 0, buffer.Count - 2);
+            if (buffer.Count > 1 && buffer[buffer.Count - 1] == LineFeed)
+                // strip trailing LF
+                return encoding.GetString(buffer.ToArray(), 0, buffer.Count - 1);
+            return encoding.GetString(buffer.ToArray(), 0, buffer.Count);
         }
 
+        partial void Log(string text);
+
         /// <summary>
         /// Disconnects and disposes the socket.
         /// </summary>
@@ -1859,7 +1998,7 @@ namespace Renci.SshNet
 
         private void SocketWriteByte(byte data)
         {
-            SocketWrite(new[] {data});
+            SocketAbstraction.Send(_socket, new[] {data});
         }
 
         private void ConnectSocks4()
@@ -1876,11 +2015,11 @@ namespace Renci.SshNet
 
             //  Send IP
             var ipAddress = DnsAbstraction.GetHostAddresses(ConnectionInfo.Host)[0];
-            SocketWrite(ipAddress.GetAddressBytes());
+            SocketAbstraction.Send(_socket, ipAddress.GetAddressBytes());
 
             //  Send username
             var username = SshData.Ascii.GetBytes(ConnectionInfo.ProxyUsername);
-            SocketWrite(username);
+            SocketAbstraction.Send(_socket, username);
             SocketWriteByte(0x00);
 
             //  Read 0
@@ -1950,7 +2089,7 @@ namespace Renci.SshNet
                     SocketWriteByte((byte)username.Length);
 
                     //  Send username
-                    SocketWrite(username);
+                    SocketAbstraction.Send(_socket, username);
 
                     var password = SshData.Ascii.GetBytes(ConnectionInfo.ProxyPassword);
 
@@ -1961,7 +2100,7 @@ namespace Renci.SshNet
                     SocketWriteByte((byte)password.Length);
 
                     //  Send username
-                    SocketWrite(password);
+                    SocketAbstraction.Send(_socket, password);
 
                     var serverVersion = SocketReadByte();
 
@@ -1993,13 +2132,13 @@ namespace Renci.SshNet
             {
                 SocketWriteByte(0x01);
                 var address = ip.GetAddressBytes();
-                SocketWrite(address);
+                SocketAbstraction.Send(_socket, address);
             }
             else if (ip.AddressFamily == AddressFamily.InterNetworkV6)
             {
                 SocketWriteByte(0x04);
                 var address = ip.GetAddressBytes();
-                SocketWrite(address);
+                SocketAbstraction.Send(_socket, address);
             }
             else
             {
@@ -2075,7 +2214,7 @@ namespace Renci.SshNet
             var httpResponseRe = new Regex(@"HTTP/(?<version>\d[.]\d) (?<statusCode>\d{3}) (?<reasonPhrase>.+)$");
             var httpHeaderRe = new Regex(@"(?<fieldName>[^\[\]()<>@,;:\""/?={} \t]+):(?<fieldValue>.+)?");
 
-            SocketWrite(SshData.Ascii.GetBytes(string.Format("CONNECT {0}:{1} HTTP/1.0\r\n", ConnectionInfo.Host, ConnectionInfo.Port)));
+            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
             if (!string.IsNullOrEmpty(ConnectionInfo.ProxyUsername))
@@ -2083,18 +2222,17 @@ namespace Renci.SshNet
                 var authorization = string.Format("Proxy-Authorization: Basic {0}\r\n",
                                                   Convert.ToBase64String(SshData.Ascii.GetBytes(string.Format("{0}:{1}", ConnectionInfo.ProxyUsername, ConnectionInfo.ProxyPassword)))
                                                   );
-                SocketWrite(SshData.Ascii.GetBytes(authorization));
+                SocketAbstraction.Send(_socket, SshData.Ascii.GetBytes(authorization));
             }
 
-            SocketWrite(SshData.Ascii.GetBytes("\r\n"));
+            SocketAbstraction.Send(_socket, SshData.Ascii.GetBytes("\r\n"));
 
             HttpStatusCode? statusCode = null;
-            var response = string.Empty;
             var contentLength = 0;
 
             while (true)
             {
-                SocketReadLine(ref response, ConnectionInfo.Timeout);
+                var response = SocketReadLine(ConnectionInfo.Timeout);
                 if (response == null)
                     // server shut down socket
                     break;