Ver Fonte

Attempt to run tests against .NET 3.5 and 4.0.

Gert Driesen há 8 anos atrás
pai
commit
b6ee090ba5

+ 13 - 3
src/Renci.SshNet.NET35/Renci.SshNet.NET35.csproj

@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <PropertyGroup>
     <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
     <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
@@ -12,29 +12,33 @@
     <AssemblyName>Renci.SshNet</AssemblyName>
     <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
     <FileAlignment>512</FileAlignment>
+    <TargetFrameworkProfile />
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
     <DebugSymbols>true</DebugSymbols>
     <DebugType>full</DebugType>
     <Optimize>false</Optimize>
     <OutputPath>bin\Debug\</OutputPath>
-    <DefineConstants>TRACE;DEBUG;FEATURE_REGEX_COMPILE;FEATURE_BINARY_SERIALIZATION;FEATURE_RNG_CREATE;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_EAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_SETSOCKETOPTION;FEATURE_SOCKET_POLL;FEATURE_STREAM_APM;FEATURE_DNS_SYNC;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HASH_RIPEMD160_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_HMAC_RIPEMD160;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_ENCODING_ASCII</DefineConstants>
+    <DefineConstants>TRACE;DEBUG;FEATURE_REGEX_COMPILE;FEATURE_BINARY_SERIALIZATION;FEATURE_RNG_CREATE;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_EAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_SETSOCKETOPTION;FEATURE_SOCKET_POLL;FEATURE_SOCKET_SELECT;FEATURE_STREAM_APM;FEATURE_DNS_SYNC;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HASH_RIPEMD160_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_HMAC_RIPEMD160;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_ENCODING_ASCII</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
     <DocumentationFile>bin\Debug\Renci.SshNet.xml</DocumentationFile>
+    <LangVersion>5</LangVersion>
+    <Prefer32Bit>false</Prefer32Bit>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
     <DebugType>none</DebugType>
     <Optimize>true</Optimize>
     <OutputPath>bin\Release\</OutputPath>
-    <DefineConstants>TRACE;FEATURE_REGEX_COMPILE;FEATURE_BINARY_SERIALIZATION;FEATURE_RNG_CREATE;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_EAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_SETSOCKETOPTION;FEATURE_SOCKET_POLL;FEATURE_STREAM_APM;FEATURE_DNS_SYNC;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HASH_RIPEMD160_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_HMAC_RIPEMD160;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_ENCODING_ASCII</DefineConstants>
+    <DefineConstants>TRACE;FEATURE_REGEX_COMPILE;FEATURE_BINARY_SERIALIZATION;FEATURE_RNG_CREATE;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_EAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_SETSOCKETOPTION;FEATURE_SOCKET_POLL;FEATURE_SOCKET_SELECT;FEATURE_STREAM_APM;FEATURE_DNS_SYNC;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HASH_RIPEMD160_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_HMAC_RIPEMD160;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_ENCODING_ASCII</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <DocumentationFile>bin\Release\Renci.SshNet.xml</DocumentationFile>
     <NoWarn>
     </NoWarn>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+    <Prefer32Bit>false</Prefer32Bit>
   </PropertyGroup>
   <PropertyGroup>
     <SignAssembly>true</SignAssembly>
@@ -861,6 +865,9 @@
     <Compile Include="..\Renci.SshNet\Sftp\SftpFileAttributes.cs">
       <Link>Sftp\SftpFileAttributes.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet\Sftp\SftpFileReader.cs">
+      <Link>Sftp\SftpFileReader.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\Sftp\SftpFileStream.cs">
       <Link>Sftp\SftpFileStream.cs</Link>
     </Compile>
@@ -876,6 +883,9 @@
     <Compile Include="..\Renci.SshNet\Sftp\SftpMessageTypes.cs">
       <Link>Sftp\SftpMessageTypes.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet\Sftp\SftpReadAsyncResult.cs">
+      <Link>Sftp\SftpReadAsyncResult.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\Sftp\SftpSession.cs">
       <Link>Sftp\SftpSession.cs</Link>
     </Compile>

+ 2 - 2
src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj

@@ -42,8 +42,8 @@
   </PropertyGroup>
   <ItemGroup>
     <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
-    <Reference Include="Moq">
-      <HintPath>..\..\packages\Moq.4.2.1409.1722\lib\net40\Moq.dll</HintPath>
+    <Reference Include="Moq, Version=4.2.1409.1722, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
+      <HintPath>..\..\..\..\..\Users\Gert Driesen\Dropbox\Privé\Projects\SShNetTests\packages\Moq.4.2.1409.1722\lib\net40\Moq.dll</HintPath>
     </Reference>
     <Reference Include="System" />
     <Reference Include="System.Core">

+ 27 - 3
src/Renci.SshNet/Common/AsyncResult.cs

@@ -29,10 +29,12 @@ namespace Renci.SshNet.Common
         private Exception _exception;
 
         /// <summary>
-        /// Gets or sets a value indicating whether EndInvoke has been called on the current AsyncResult.
+        /// Gets or sets a value indicating whether <see cref="EndInvoke()"/> has been called on the current
+        /// <see cref="AsyncResult"/>.
         /// </summary>
         /// <value>
-        /// <c>true</c> if <see cref="EndInvoke()"/> has been called on the current <see cref="AsyncResult"/>; otherwise, <c>false</c>.
+        /// <c>true</c> if <see cref="EndInvoke()"/> has been called on the current <see cref="AsyncResult"/>;
+        /// otherwise, <c>false</c>.
         /// </value>
         public bool EndInvokeCalled { get; private set; }
 
@@ -75,7 +77,7 @@ namespace Renci.SshNet.Common
         /// <summary>
         /// Waits until the asynchronous operation completes, and then returns. 
         /// </summary>
-        public void EndInvoke()
+        internal void EndInvoke()
         {
             // This method assumes that only 1 thread calls EndInvoke for this object
             if (!IsCompleted)
@@ -93,6 +95,28 @@ namespace Renci.SshNet.Common
                 throw _exception;
         }
 
+        /// <summary>
+        /// Waits until the asynchronous operation completes, and then returns. 
+        /// </summary>
+        internal void EndInvoke(TimeSpan timeout)
+        {
+            // This method assumes that only 1 thread calls EndInvoke for this object
+            if (!IsCompleted)
+            {
+                // If the operation isn't done, wait for it
+                var completed = AsyncWaitHandle.WaitOne(timeout);
+                _asyncWaitHandle = null;  // Allow early GC
+                AsyncWaitHandle.Dispose();
+
+            }
+
+            EndInvokeCalled = true;
+
+            // Operation is done: if an exception occurred, throw it
+            if (_exception != null)
+                throw _exception;
+        }
+
         #region Implementation of IAsyncResult
 
         /// <summary>

+ 0 - 1
src/Renci.SshNet/Common/SemaphoreLight.cs

@@ -64,7 +64,6 @@ namespace Renci.SshNet.Common
         /// </summary>
         public void Wait()
         {
-
             lock (_lock)
             {
                 while (_currentCount < 1)

+ 5 - 2
src/Renci.SshNet/Renci.SshNet.csproj

@@ -18,7 +18,7 @@
     <DebugType>full</DebugType>
     <Optimize>false</Optimize>
     <OutputPath>bin\Debug\</OutputPath>
-    <DefineConstants>TRACE;DEBUG;FEATURE_REGEX_COMPILE;FEATURE_BINARY_SERIALIZATION;FEATURE_RNG_CREATE;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_EAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_SETSOCKETOPTION;FEATURE_SOCKET_POLL;FEATURE_STREAM_APM;FEATURE_DNS_SYNC;FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HASH_RIPEMD160_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_HMAC_RIPEMD160;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_ENCODING_ASCII</DefineConstants>
+    <DefineConstants>TRACE;DEBUG;FEATURE_REGEX_COMPILE;FEATURE_BINARY_SERIALIZATION;FEATURE_RNG_CREATE;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_EAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_SETSOCKETOPTION;FEATURE_SOCKET_SELECT;FEATURE_SOCKET_POLL;FEATURE_STREAM_APM;FEATURE_DNS_SYNC;FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HASH_RIPEMD160_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_HMAC_RIPEMD160;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_ENCODING_ASCII</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <DocumentationFile>bin\Debug\Renci.SshNet.xml</DocumentationFile>
@@ -29,13 +29,14 @@
     <DebugType>none</DebugType>
     <Optimize>true</Optimize>
     <OutputPath>bin\Release\</OutputPath>
-    <DefineConstants>FEATURE_REGEX_COMPILE;FEATURE_BINARY_SERIALIZATION;FEATURE_RNG_CREATE;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_EAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_SETSOCKETOPTION;FEATURE_SOCKET_POLL;FEATURE_STREAM_APM;FEATURE_DNS_SYNC;FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HASH_RIPEMD160_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_HMAC_RIPEMD160;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_ENCODING_ASCII</DefineConstants>
+    <DefineConstants>FEATURE_REGEX_COMPILE;FEATURE_BINARY_SERIALIZATION;FEATURE_RNG_CREATE;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_EAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_SETSOCKETOPTION;FEATURE_SOCKET_SELECT;FEATURE_SOCKET_POLL;FEATURE_STREAM_APM;FEATURE_DNS_SYNC;FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HASH_RIPEMD160_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_HMAC_RIPEMD160;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_ENCODING_ASCII</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <DocumentationFile>bin\Release\Renci.SshNet.xml</DocumentationFile>
     <NoWarn>
     </NoWarn>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+    <LangVersion>5</LangVersion>
   </PropertyGroup>
   <PropertyGroup>
     <SignAssembly>true</SignAssembly>
@@ -416,6 +417,7 @@
     <Compile Include="Sftp\SftpDownloadAsyncResult.cs" />
     <Compile Include="Sftp\SftpFile.cs" />
     <Compile Include="Sftp\SftpFileAttributes.cs" />
+    <Compile Include="Sftp\SftpFileReader.cs" />
     <Compile Include="Sftp\SftpFileStream.cs" />
     <Compile Include="Sftp\SftpFileSystemInformation.cs">
       <SubType>Code</SubType>
@@ -425,6 +427,7 @@
     <Compile Include="Sftp\SftpMessageTypes.cs">
       <SubType>Code</SubType>
     </Compile>
+    <Compile Include="Sftp\SftpReadAsyncResult.cs" />
     <Compile Include="Sftp\SftpSession.cs">
       <SubType>Code</SubType>
     </Compile>

+ 22 - 6
src/Renci.SshNet/Session.cs

@@ -1815,14 +1815,16 @@ namespace Renci.SshNet
         /// </summary>
         private void MessageListener()
         {
+#if FEATURE_SOCKET_SELECT
+            var readSockets = new List<Socket> { _socket };
+#endif // FEATURE_SOCKET_SELECT
+
             try
             {
-                var readSockets = new List<Socket> {_socket};
-
                 // remain in message loop until socket is shut down or until we're disconnecting
                 while (_socket.IsConnected())
                 {
-#if FEATURE_SOCKET_POLL
+#if FEATURE_SOCKET_SELECT
                     // if the socket is already disposed when Select is invoked, then a SocketException
                     // stating "An operation was attempted on something that is not a socket" is thrown;
                     // we attempt to avoid this exception by having an IsConnected() that can break the
@@ -1835,7 +1837,8 @@ namespace Renci.SshNet
 
                     // perform a blocking select to determine whether there's is data available to be
                     // read; we do not use a blocking read to allow us to use Socket.Poll to determine
-                    // if the connection is still available (in IsSocketConnected
+                    // if the connection is still available (in IsSocketConnected)
+
                     Socket.Select(readSockets, null, null, -1);
 
                     // the Select invocation will be interrupted in one of the following conditions:
@@ -1855,7 +1858,19 @@ namespace Renci.SshNet
                         // break out of the message loop
                         break;
                     }
-#endif // FEATURE_SOCKET_POLL
+#elif FEATURE_SOCKET_POLL
+                    // when Socket.Select(IList, IList, IList, Int32) is not available or is buggy, we use
+                    // Socket.Poll(Int, SelectMode) to block until either data is available or the socket
+                    // is closed
+
+                    var status = _socket.Poll(-1, SelectMode.SelectRead);
+                    if (!_socket.IsConnected())
+                    {
+                        // connection with SSH server was closed;
+                        // break out of the message loop
+                        break;
+                    }
+#endif // FEATURE_SOCKET_SELECT
 
                     var message = ReceiveMessage();
                     if (message == null)
@@ -1869,7 +1884,7 @@ namespace Renci.SshNet
                     message.Process(this);
                 }
 
-                // connection with SSH server was closed
+                // connection with SSH server was closed or socket was disposed
                 RaiseError(CreateConnectionAbortedByServerException());
             }
             catch (SocketException ex)
@@ -2201,6 +2216,7 @@ namespace Renci.SshNet
                     return;
             }
 
+            // "save" exception and set exception wait handle to ensure any waits are interrupted
             _exception = exp;
             _exceptionWaitHandle.Set();
 

+ 13 - 0
src/Renci.SshNet/Sftp/ISftpSession.cs

@@ -2,11 +2,20 @@
 using System.Collections.Generic;
 using System.Threading;
 using Renci.SshNet.Sftp.Responses;
+using Renci.SshNet.Channels;
 
 namespace Renci.SshNet.Sftp
 {
     internal interface ISftpSession : ISubsystemSession
     {
+        /// <summary>
+        /// Gets the channel associated with this session.
+        /// </summary>
+        /// <value>
+        /// The channel associated with this session.
+        /// </value>
+        IChannelSession Channel { get; }
+
         /// <summary>
         /// Gets the SFTP protocol version.
         /// </summary>
@@ -95,6 +104,10 @@ namespace Renci.SshNet.Sftp
         /// <returns>data array; null if EOF</returns>
         byte[] RequestRead(byte[] handle, ulong offset, uint length);
 
+        SftpReadAsyncResult BeginRead(byte[] handle, ulong offset, uint length, AsyncCallback callback, object state);
+
+        byte[] EndRead(IAsyncResult asyncResult);
+
         /// <summary>
         /// Performs SSH_FXP_READDIR request
         /// </summary>

+ 229 - 0
src/Renci.SshNet/Sftp/SftpFileReader.cs

@@ -0,0 +1,229 @@
+using Renci.SshNet.Abstractions;
+using Renci.SshNet.Common;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace Renci.SshNet.Sftp
+{
+    internal class SftpFileReader
+    {
+        private const int MaxReadAhead = 10;
+
+        private readonly byte[] _handle;
+        private readonly ISftpSession _sftpSession;
+        private SemaphoreLight _semaphore;
+        private bool _isCompleted;
+        private uint _chunkLength;
+        private int _readAheadChunkIndex;
+        private int _nextChunkIndex;
+        private ulong _readAheadOffset;
+        private ulong _offset;
+        private ulong _fileSize;
+        private readonly IDictionary<int, BufferedRead> _queue;
+        private readonly object _readLock;
+
+        public SftpFileReader(byte[] handle, ISftpSession sftpSession)
+        {
+            _handle = handle;
+            _sftpSession = sftpSession;
+            _chunkLength = 16 * 1024; // TODO !
+            _semaphore = new SemaphoreLight(MaxReadAhead);
+            _queue = new Dictionary<int, BufferedRead>(MaxReadAhead);
+            _readLock = new object();
+
+            _fileSize = (ulong)_sftpSession.RequestFStat(_handle).Size;
+
+            ThreadAbstraction.ExecuteThread(() =>
+            {
+                while (!_isCompleted)
+                {
+                    if (_readAheadOffset >= _fileSize)
+                        break;
+
+                    // TODO implement cancellation!?
+                    _semaphore.Wait();
+
+                    // start reading next chunk
+                    _sftpSession.BeginRead(_handle, _readAheadOffset, _chunkLength, ReadCompleted,
+                                           new BufferedRead(_readAheadChunkIndex, _readAheadOffset));
+
+                    // advance read-ahead offset
+                    _readAheadOffset += _chunkLength;
+
+                    _readAheadChunkIndex++;
+                }
+            });
+        }
+
+        public byte[] Read()
+        {
+            lock (_readLock)
+            {
+                BufferedRead nextChunk;
+
+                while (!_queue.TryGetValue(_nextChunkIndex, out nextChunk) && !_isCompleted)
+                    Monitor.Wait(_readLock);
+
+                if (_isCompleted)
+                    return new byte[0];
+
+                if (nextChunk.Offset == _offset)
+                {
+                    var data = nextChunk.Data;
+                    _offset += (ulong) data.Length;
+
+                    // remove processed chunk
+                    _queue.Remove(_nextChunkIndex);
+                    // move to next chunk
+                    _nextChunkIndex++;
+                    // allow read ahead of a new chunk
+                    _semaphore.Release();
+                    return data;
+                }
+
+                // when the server returned less bytes than requested (for the previous chunk)
+                // we'll synchronously request the remaining data
+
+                var catchUp = new byte[nextChunk.Offset - _offset];
+                var bytesCaughtUp = 0L;
+
+                while (bytesCaughtUp < catchUp.Length)
+                {
+                    // TODO: break loop and interrupt blocking wait in case of exception
+                    var read = _sftpSession.RequestRead(_handle, _offset, (uint) catchUp.Length);
+                    if (read.Length == 0)
+                    {
+                        // break in loop in "read-ahead" thread (once a blocking wait is interrupted)
+                        _isCompleted = true;
+                        // interrupt blocking wait in "read-ahead" thread
+                        lock (_readLock)
+                            Monitor.PulseAll(_readLock);
+                        // signal failure
+                        throw new SshException("Unexpectedly reached end of file.");
+                    }
+
+                    bytesCaughtUp += read.Length;
+                    _offset += (ulong) bytesCaughtUp;
+                }
+
+                return catchUp;
+            }
+        }
+
+        private void ReadCompleted(IAsyncResult result)
+        {
+            var readAsyncResult = result as SftpReadAsyncResult;
+            if (readAsyncResult == null)
+                return;
+
+            var data = readAsyncResult.EndInvoke();
+            if (data.Length == 0)
+            {
+                _isCompleted = true;
+            }
+            else
+            {
+                var bufferedRead = (BufferedRead)readAsyncResult.AsyncState;
+                bufferedRead.Complete(data);
+                _queue.Add(bufferedRead.ChunkIndex, bufferedRead);
+            }
+
+            // signal that a chunk has been read or EOF has been reached;
+            // in both cases, we want to unblock the "read-ahead" thread
+            lock (_readLock)
+            {
+                Monitor.Pulse(_readLock);
+            }
+        }
+    }
+
+    //private class BufferedReadState
+    //{
+    //    private BlockingQueue<BufferedRead> _queue;
+    //    private long _offset;
+
+    //    public BufferedReadState(long offset, SemaphoreLight semaphore, BlockingQueue<BufferedRead> queue)
+    //    {
+    //        _queue = queue;
+    //        _offset = offset;
+    //        _semaphore = semaphore;
+    //    }
+
+    //    public long Offset
+    //    {
+    //        get { return _offset; }
+    //    }
+
+    //    public BlockingQueue<BufferedRead> Queue
+    //    {
+    //        get { return _queue; }
+    //    }
+
+    //    public SemaphoreLight Semaphore
+    //    {
+    //        get { return _semaphore; }
+    //    }
+    //}
+
+    //private class BlockingQueue<T>
+    //{
+    //    private readonly object _lock = new object();
+    //    private Queue<T> _queue;
+    //    private bool _isClosed;
+
+    //    public BlockingQueue(int capacity)
+    //    {
+    //        _queue = new Queue<T>(capacity);
+    //    }
+
+    //    public T Dequeue()
+    //    {
+    //        lock (_lock)
+    //        {
+    //            while (!_isClosed && _queue.Count == 0)
+    //                Monitor.Wait(_lock);
+
+    //            if (_queue.Count == 0)
+    //                return default(T);
+
+    //            return _queue.Dequeue();
+    //        }
+    //    }
+
+    //    public void Enqueue(T item)
+    //    {
+    //        lock (_lock)
+    //        {
+    //            _queue.Enqueue(item);
+    //            Monitor.PulseAll(_lock);
+    //        }
+    //    }
+
+    //    public void Close()
+    //    {
+    //        _isClosed = true;
+    //        Monitor.PulseAll(_lock);
+    //    }
+    //}
+
+    internal class BufferedRead
+    {
+        public int ChunkIndex { get; private set; }
+
+        public byte[] Data { get; private set; }
+
+        public ulong Offset { get; private set; }
+
+        public BufferedRead(int chunkIndex, ulong offset)
+        {
+            ChunkIndex = chunkIndex;
+            Offset = offset;
+        }
+
+        public void Complete(byte[] data)
+        {
+            Data = data;
+        }
+    }
+}

+ 12 - 0
src/Renci.SshNet/Sftp/SftpReadAsyncResult.cs

@@ -0,0 +1,12 @@
+using Renci.SshNet.Common;
+using System;
+
+namespace Renci.SshNet.Sftp
+{
+    internal class SftpReadAsyncResult : AsyncResult<byte[]>
+    {
+        public SftpReadAsyncResult(AsyncCallback asyncCallback, object state) : base(asyncCallback, state)
+        {
+        }
+    }
+}

+ 46 - 0
src/Renci.SshNet/Sftp/SftpSession.cs

@@ -169,6 +169,8 @@ namespace Renci.SshNet.Sftp
             var offset = 0;
             var count = data.Length;
 
+            //Console.WriteLine("Data received: " + count);
+
             // improve performance and reduce GC pressure by not buffering channel data if the received
             // chunk contains the complete packet data.
             //
@@ -184,6 +186,8 @@ namespace Renci.SshNet.Sftp
 
                     var packetTotalLength = packetDataLength + packetLengthByteCount;
 
+                    //Console.WriteLine("Current: " + data.Length + " | Total: " + packetTotalLength);
+
                     // check if complete packet data (or more) is available
                     if (count >= packetTotalLength)
                     {
@@ -241,6 +245,8 @@ namespace Renci.SshNet.Sftp
 
                 var packetTotalLength = packetDataLength + packetLengthByteCount;
 
+                //Console.WriteLine("Current: " + data.Length + " | Total: " + packetTotalLength);
+
                 // check if complete packet data is available
                 if (_data.Count < packetTotalLength)
                 {
@@ -397,6 +403,46 @@ namespace Renci.SshNet.Sftp
             }
         }
 
+        public SftpReadAsyncResult BeginRead(byte[] handle, ulong offset, uint length, AsyncCallback callback, object state)
+        {
+            var asyncResult = new SftpReadAsyncResult(callback, state);
+
+            var request = new SftpReadRequest(ProtocolVersion, NextRequestId, handle, offset, length,
+                response =>
+                {
+                    asyncResult.SetAsCompleted(response.Data, false);
+                },
+                response =>
+                {
+                    if (response.StatusCode != StatusCodes.Eof)
+                    {
+                        asyncResult.SetAsCompleted(GetSftpException(response), false);
+                    }
+                    else
+                    {
+                        asyncResult.SetAsCompleted(Array<byte>.Empty, false);
+                    }
+                });
+            SendRequest(request);
+
+            return asyncResult;
+        }
+
+        public byte[] EndRead(IAsyncResult asyncResult)
+        {
+            if (asyncResult == null)
+                throw new ArgumentNullException("asyncResult");
+
+            var sftpReadAsyncResult = asyncResult as SftpReadAsyncResult;
+            if (sftpReadAsyncResult == null)
+                throw new ArgumentException("IDIOT", "asyncResult");
+
+            if (sftpReadAsyncResult.EndInvokeCalled)
+                throw new InvalidOperationException("EndRead has already been called.");
+
+            return sftpReadAsyncResult.EndInvoke();
+        }
+
         /// <summary>
         /// Performs SSH_FXP_READ request.
         /// </summary>

+ 99 - 19
src/Renci.SshNet/SftpClient.cs

@@ -1996,40 +1996,120 @@ namespace Renci.SshNet
             var fullPath = _sftpSession.GetCanonicalPath(path);
 
             var handle = _sftpSession.RequestOpen(fullPath, Flags.Read);
+            var async = true;
+            if (async)
+            {
+                var fileReader = new SftpFileReader(handle, _sftpSession);
+                var totalBytesRead = 0UL;
 
-            ulong offset = 0;
+                while (true)
+                {
+                    var data = fileReader.Read();
+                    if (data.Length == 0)
+                        break;
 
-            var optimalReadLength = _sftpSession.CalculateOptimalReadLength(_bufferSize);
+                    output.Write(data, 0, data.Length);
 
-            var data = _sftpSession.RequestRead(handle, offset, optimalReadLength);
+                    totalBytesRead += (ulong) data.Length;
 
-            //  Read data while available
-            while (data.Length > 0)
+                    //Console.WriteLine(totalBytesRead);
+
+                    if (downloadCallback != null)
+                        downloadCallback(totalBytesRead);
+                }
+            }
+            else
             {
-                //  Cancel download
-                if (asyncResult != null && asyncResult.IsDownloadCanceled)
-                    break;
+                ulong offset = 0;
+
+                var optimalReadLength = _sftpSession.CalculateOptimalReadLength(_bufferSize);
+
+                var data = _sftpSession.RequestRead(handle, offset, optimalReadLength);
+
+                //  Read data while available
+                while (data.Length > 0)
+                {
+                    //  Cancel download
+                    if (asyncResult != null && asyncResult.IsDownloadCanceled)
+                        break;
 
-                output.Write(data, 0, data.Length);
+                    output.Write(data, 0, data.Length);
 
-                output.Flush();
+                    output.Flush();
 
-                offset += (ulong)data.Length;
+                    offset += (ulong)data.Length;
+
+                    //Console.WriteLine("" + data.Length + " => " + offset);
+
+                    //  Call callback to report number of bytes read
+                    if (downloadCallback != null)
+                    {
+                        // copy offset to ensure it's not modified between now and execution of callback
+                        var downloadOffset = offset;
+
+                        //  Execute callback on different thread
+                        ThreadAbstraction.ExecuteThread(() => { downloadCallback(downloadOffset); });
+                    }
+
+                    data = _sftpSession.RequestRead(handle, offset, optimalReadLength);
+                }
+            }
+
+            _sftpSession.RequestClose(handle);
+        }
 
-                //  Call callback to report number of bytes read
-                if (downloadCallback != null)
+        private class BlockingQueue<T>
+        {
+            private readonly object _lock = new object();
+            private Queue<T> _queue;
+            private bool _isClosed;
+
+            public BlockingQueue(int capacity)
+            {
+                _queue = new Queue<T>(capacity);
+            }
+
+            public T Dequeue()
+            {
+                lock (_lock)
                 {
-                    // copy offset to ensure it's not modified between now and execution of callback
-                    var downloadOffset = offset;
+                    while (!_isClosed && _queue.Count == 0)
+                        Monitor.Wait(_lock);
 
-                    //  Execute callback on different thread
-                    ThreadAbstraction.ExecuteThread(() => { downloadCallback(downloadOffset); });
+                    if (_queue.Count == 0)
+                        return default(T);
+
+                    return _queue.Dequeue();
+                }
+            }
+
+            public void Enqueue(T item)
+            {
+                lock (_lock)
+                {
+                    _queue.Enqueue(item);
+                    Monitor.PulseAll(_lock);
                 }
+            }
 
-                data = _sftpSession.RequestRead(handle, offset, optimalReadLength);
+            public void Close()
+            {
+                _isClosed = true;
+                Monitor.PulseAll(_lock);
             }
+        }
 
-            _sftpSession.RequestClose(handle);
+        private class BufferedRead
+        {
+            public byte[] Data { get; private set; }
+
+            public ulong Offset { get; private set; }
+
+            public BufferedRead(ulong offset, byte[] data)
+            {
+                Offset = offset;
+                Data = data;
+            }
         }
 
         /// <summary>

+ 1 - 1
src/Renci.SshNet/SubsystemSession.cs

@@ -42,7 +42,7 @@ namespace Renci.SshNet
         /// <value>
         /// The channel associated with this session.
         /// </value>
-        internal IChannelSession Channel
+        public IChannelSession Channel
         {
             get
             {