Browse Source

Merge branch 'develop' of https://github.com/sshnet/SSH.NET into develop

drieseng 3 years ago
parent
commit
c728fba850
56 changed files with 3532 additions and 173 deletions
  1. 2 2
      appveyor.yml
  2. 2 2
      build/sandcastle/SSH.NET.shfbproj
  3. 1 1
      src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAliveInterval_NegativeOne.cs
  4. 4 0
      src/Renci.SshNet.Tests/Classes/Common/PacketDumpTest.cs
  5. 2 2
      src/Renci.SshNet.Tests/Classes/PrivateKeyFileTest.cs
  6. 8 4
      src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndDirectoryInfo_SendExecRequestReturnsFalse.cs
  7. 8 4
      src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndFileInfo_SendExecRequestReturnsFalse.cs
  8. 8 4
      src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndStream_SendExecRequestReturnsFalse.cs
  9. 8 4
      src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_DirectoryInfoAndPath_SendExecRequestReturnsFalse.cs
  10. 9 5
      src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_SendExecRequestReturnsFalse.cs
  11. 8 4
      src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_Success.cs
  12. 8 4
      src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_StreamAndPath_SendExecRequestReturnsFalse.cs
  13. 69 0
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamAsyncTestBase.cs
  14. 57 0
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileAccessInvalid.cs
  15. 58 0
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeAppend_FileAccessRead.cs
  16. 58 0
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeAppend_FileAccessReadWrite.cs
  17. 155 0
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeAppend_FileAccessWrite.cs
  18. 58 0
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeCreateNew_FileAccessRead.cs
  19. 136 0
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeCreateNew_FileAccessReadWrite.cs
  20. 136 0
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeCreateNew_FileAccessWrite.cs
  21. 58 0
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeCreate_FileAccessRead.cs
  22. 136 0
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeCreate_FileAccessReadWrite_FileDoesNotExist.cs
  23. 136 0
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeCreate_FileAccessReadWrite_FileExists.cs
  24. 136 0
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeCreate_FileAccessWrite_FileDoesNotExist.cs
  25. 136 0
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeCreate_FileAccessWrite_FileExists.cs
  26. 57 0
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeInvalid.cs
  27. 141 0
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeOpenOrCreate_FileAccessRead.cs
  28. 136 0
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeOpenOrCreate_FileAccessReadWrite.cs
  29. 136 0
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeOpenOrCreate_FileAccessWrite.cs
  30. 142 0
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeOpen_FileAccessRead.cs
  31. 136 0
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeOpen_FileAccessReadWrite.cs
  32. 136 0
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeOpen_FileAccessWrite.cs
  33. 59 0
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeTruncate_FileAccessRead.cs
  34. 136 0
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeTruncate_FileAccessReadWrite.cs
  35. 136 0
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeTruncate_FileAccessWrite.cs
  36. 159 0
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_ReadAsync_ReadMode_NoDataInReaderBufferAndReadLessBytesFromServerThanCountAndEqualToBufferSize.cs
  37. 152 0
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_ReadAsync_ReadMode_NoDataInReaderBufferAndReadLessBytesFromServerThanCountAndLessThanBufferSize.cs
  38. 143 0
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_ReadAsync_ReadMode_NoDataInReaderBufferAndReadMoreBytesFromServerThanCount.cs
  39. 143 0
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_WriteAsync_SessionOpen_CountGreatherThanTwoTimesTheWriteBufferSize.cs
  40. 48 0
      src/Renci.SshNet.Tests/Classes/SftpClientTest.ConnectAsync.cs
  41. 27 0
      src/Renci.SshNet.Tests/Classes/SftpClientTest.DeleteFileAsync.cs
  42. 66 0
      src/Renci.SshNet.Tests/Classes/SftpClientTest.RenameFileAsync.cs
  43. 8 0
      src/Renci.SshNet.Tests/Classes/SftpClientTest.cs
  44. 25 36
      src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj
  45. 15 0
      src/Renci.SshNet/IPrivateKeySource.cs
  46. 4 4
      src/Renci.SshNet/NetConfClient.cs
  47. 4 4
      src/Renci.SshNet/PrivateKeyAuthenticationMethod.cs
  48. 10 10
      src/Renci.SshNet/PrivateKeyConnectionInfo.cs
  49. 13 5
      src/Renci.SshNet/PrivateKeyFile.cs
  50. 3 3
      src/Renci.SshNet/ScpClient.cs
  51. 9 0
      src/Renci.SshNet/Security/Cryptography/ED25519Key.cs
  52. 6 6
      src/Renci.SshNet/Security/Cryptography/EcdsaDigitalSignature.cs
  53. 71 60
      src/Renci.SshNet/Security/Cryptography/EcdsaKey.cs
  54. 5 0
      src/Renci.SshNet/Security/Cryptography/Key.cs
  55. 6 6
      src/Renci.SshNet/SftpClient.cs
  56. 3 3
      src/Renci.SshNet/SshClient.cs

+ 2 - 2
appveyor.yml

@@ -9,6 +9,6 @@ build:
 
 test_script:
 - cmd: >-
-    vstest.console /logger:Appveyor src\Renci.SshNet.Tests\bin\Debug\net40\Renci.SshNet.Tests.dll /TestCaseFilter:"TestCategory!=integration&TestCategory!=LongRunning"
+    vstest.console /logger:Appveyor src\Renci.SshNet.Tests\bin\Debug\net35\Renci.SshNet.Tests.dll /TestCaseFilter:"TestCategory!=integration&TestCategory!=LongRunning"
 
-    vstest.console /logger:Appveyor src\Renci.SshNet.Tests\bin\Debug\net35\Renci.SshNet.Tests.dll /TestCaseFilter:"TestCategory!=integration&TestCategory!=LongRunning"
+    vstest.console /logger:Appveyor src\Renci.SshNet.Tests\bin\Debug\net472\Renci.SshNet.Tests.dll /TestCaseFilter:"TestCategory!=integration&TestCategory!=LongRunning"

+ 2 - 2
build/sandcastle/SSH.NET.shfbproj

@@ -27,7 +27,7 @@
     <PresentationStyle>VS2010</PresentationStyle>
     <Preliminary>False</Preliminary>
     <NamingMethod>Guid</NamingMethod>
-    <HelpTitle>SSH.NET Client Library Documenation</HelpTitle>
+    <HelpTitle>SSH.NET Client Library Documentation</HelpTitle>
     <ContentPlacement>AboveNamespaces</ContentPlacement>
     <ComponentConfigurations>
       <ComponentConfig id="Code Block Component" enabled="True">
@@ -73,4 +73,4 @@
   </PropertyGroup>
   <!-- Import the SHFB build targets -->
   <Import Project="$(SHFBROOT)\SandcastleHelpFileBuilder.targets" />
-</Project>
+</Project>

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

@@ -94,7 +94,7 @@ namespace Renci.SshNet.Tests.Classes
         }
 
         [TestMethod]
-        public void SendMessageOnSessionShouldBeInvokedThreeTimes()
+        public void SendMessageOnSessionShouldBeInvokedOneTime()
         {
             // allow keep-alive to be sent once
             Thread.Sleep(100);

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

@@ -37,7 +37,11 @@ namespace Renci.SshNet.Tests.Classes.Common
             catch (ArgumentOutOfRangeException ex)
             {
                 Assert.IsNull(ex.InnerException);
+#if NETFRAMEWORK
                 Assert.AreEqual(string.Format("Cannot be less than zero.{0}Parameter name: {1}", Environment.NewLine, ex.ParamName), ex.Message);
+#else
+                Assert.AreEqual(string.Format("Cannot be less than zero. (Parameter '{1}')", Environment.NewLine, ex.ParamName), ex.Message);
+#endif
                 Assert.AreEqual("indentLevel", ex.ParamName);
             }
         }

+ 2 - 2
src/Renci.SshNet.Tests/Classes/PrivateKeyFileTest.cs

@@ -435,7 +435,7 @@ namespace Renci.SshNet.Tests.Classes
         /// A test for <see cref="PrivateKeyFile(string, string)"/> ctor.
         ///</summary>
         [TestMethod()]
-        public void ConstructorWithFileNameAndPassphraseShouldThrowSshPassPhraseNullOrEmptyExceptionWhenPrivateKeyIsEncryptedAndPassphraseIsEmpty()
+        public void ConstructorWithFileNameAndPassphraseShouldThrowSshPassPhraseNullOrEmptyExceptionWhenNeededPassphraseIsEmpty()
         {
             var passphrase = string.Empty;
 
@@ -460,7 +460,7 @@ namespace Renci.SshNet.Tests.Classes
         /// A test for <see cref="PrivateKeyFile(string, string)"/> ctor.
         ///</summary>
         [TestMethod()]
-        public void ConstructorWithFileNameAndPassphraseShouldThrowSshPassPhraseNullOrEmptyExceptionWhenPrivateKeyIsEncryptedAndPassphraseIsNull()
+        public void ConstructorWithFileNameAndPassphraseShouldThrowSshPassPhraseNullOrEmptyExceptionWhenNeededPassphraseIsNull()
         {
             string passphrase = null;
 

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

@@ -54,11 +54,11 @@ namespace Renci.SshNet.Tests.Classes
                                .Setup(p => p.SendExecRequest(string.Format("scp -prf {0}", _transformedPath)))
                                .Returns(false);
             _channelSessionMock.InSequence(sequence).Setup(p => p.Dispose());
+#if NET35
             _pipeStreamMock.As<IDisposable>().InSequence(sequence).Setup(p => p.Dispose());
-
-            // On .NET Core, Dispose() in turn invokes Close() and since we're not mocking
-            // an interface, we need to expect this call as well
-            _pipeStreamMock.Setup(p => p.Close());
+#else
+            _pipeStreamMock.InSequence(sequence).Setup(p => p.Close());
+#endif
         }
 
         protected override void Arrange()
@@ -106,7 +106,11 @@ namespace Renci.SshNet.Tests.Classes
         [TestMethod]
         public void DisposeOnPipeStreamShouldBeInvokedOnce()
         {
+#if NET35
             _pipeStreamMock.As<IDisposable>().Verify(p => p.Dispose(), Times.Once);
+#else
+            _pipeStreamMock.Verify(p => p.Close(), Times.Once);
+#endif
         }
 
         [TestMethod]

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

@@ -53,11 +53,11 @@ namespace Renci.SshNet.Tests.Classes
             _channelSessionMock.InSequence(sequence)
                 .Setup(p => p.SendExecRequest(string.Format("scp -pf {0}", _transformedPath))).Returns(false);
             _channelSessionMock.InSequence(sequence).Setup(p => p.Dispose());
+#if NET35
             _pipeStreamMock.As<IDisposable>().InSequence(sequence).Setup(p => p.Dispose());
-
-            // On .NET Core, Dispose() in turn invokes Close() and since we're not mocking
-            // an interface, we need to expect this call as well
-            _pipeStreamMock.Setup(p => p.Close());
+#else
+            _pipeStreamMock.InSequence(sequence).Setup(p => p.Close());
+#endif
         }
 
         protected override void Arrange()
@@ -105,7 +105,11 @@ namespace Renci.SshNet.Tests.Classes
         [TestMethod]
         public void DisposeOnPipeStreamShouldBeInvokedOnce()
         {
+#if NET35
             _pipeStreamMock.As<IDisposable>().Verify(p => p.Dispose(), Times.Once);
+#else
+            _pipeStreamMock.Verify(p => p.Close(), Times.Once);
+#endif
         }
 
         [TestMethod]

+ 8 - 4
src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndStream_SendExecRequestReturnsFalse.cs

@@ -54,11 +54,11 @@ namespace Renci.SshNet.Tests.Classes
                                .Setup(p => p.SendExecRequest(string.Format("scp -f {0}", _transformedPath)))
                                .Returns(false);
             _channelSessionMock.InSequence(sequence).Setup(p => p.Dispose());
+#if NET35
             _pipeStreamMock.As<IDisposable>().InSequence(sequence).Setup(p => p.Dispose());
-
-            // On .NET Core, Dispose() in turn invokes Close() and since we're not mocking
-            // an interface, we need to expect this call as well
-            _pipeStreamMock.Setup(p => p.Close());
+#else
+            _pipeStreamMock.InSequence(sequence).Setup(p => p.Close());
+#endif
         }
 
         protected override void Arrange()
@@ -116,7 +116,11 @@ namespace Renci.SshNet.Tests.Classes
         [TestMethod]
         public void DisposeOnPipeStreamShouldBeInvokedOnce()
         {
+#if NET35
             _pipeStreamMock.As<IDisposable>().Verify(p => p.Dispose(), Times.Once);
+#else
+            _pipeStreamMock.Verify(p => p.Close(), Times.Once);
+#endif
         }
 
         [TestMethod]

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

@@ -53,11 +53,11 @@ namespace Renci.SshNet.Tests.Classes
                                .Setup(p => p.SendExecRequest(string.Format("scp -r -p -d -t {0}", _transformedPath)))
                                .Returns(false);
             _channelSessionMock.InSequence(sequence).Setup(p => p.Dispose());
+#if NET35
             _pipeStreamMock.As<IDisposable>().InSequence(sequence).Setup(p => p.Dispose());
-
-            // On .NET Core, Dispose() in turn invokes Close() and since we're not mocking
-            // an interface, we need to expect this call as well
-            _pipeStreamMock.Setup(p => p.Close());
+#else
+            _pipeStreamMock.InSequence(sequence).Setup(p => p.Close());
+#endif
         }
 
         protected override void Arrange()
@@ -105,7 +105,11 @@ namespace Renci.SshNet.Tests.Classes
         [TestMethod]
         public void DisposeOnPipeStreamShouldBeInvokedOnce()
         {
+#if NET35
             _pipeStreamMock.As<IDisposable>().Verify(p => p.Dispose(), Times.Once);
+#else
+            _pipeStreamMock.Verify(p => p.Close(), Times.Once);
+#endif
         }
 
         [TestMethod]

+ 9 - 5
src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_SendExecRequestReturnsFalse.cs

@@ -59,12 +59,12 @@ namespace Renci.SshNet.Tests.Classes
                                .Setup(p => p.SendExecRequest(string.Format("scp -t -d {0}", _transformedPath)))
                                .Returns(false);
             _channelSessionMock.InSequence(sequence).Setup(p => p.Dispose());
+#if NET35
             _pipeStreamMock.As<IDisposable>().InSequence(sequence).Setup(p => p.Dispose());
-
-            // On .NET Core, Dispose() in turn invokes Close() and since we're not mocking
-            // an interface, we need to expect this call as well
-            _pipeStreamMock.Setup(p => p.Close());
-}
+#else
+            _pipeStreamMock.InSequence(sequence).Setup(p => p.Close());
+#endif
+        }
 
         protected override void Arrange()
         {
@@ -122,7 +122,11 @@ namespace Renci.SshNet.Tests.Classes
         [TestMethod]
         public void DisposeOnPipeStreamShouldBeInvokedOnce()
         {
+#if NET35
             _pipeStreamMock.As<IDisposable>().Verify(p => p.Dispose(), Times.Once);
+#else
+            _pipeStreamMock.Verify(p => p.Close(), Times.Once);
+#endif
         }
 
         [TestMethod]

+ 8 - 4
src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_Success.cs

@@ -84,11 +84,11 @@ namespace Renci.SshNet.Tests.Classes
                     p => p.SendData(It.Is<byte[]>(b => b.SequenceEqual(new byte[] {0}))));
             _pipeStreamMock.InSequence(sequence).Setup(p => p.ReadByte()).Returns(0);
             _channelSessionMock.InSequence(sequence).Setup(p => p.Dispose());
+#if NET35
             _pipeStreamMock.As<IDisposable>().InSequence(sequence).Setup(p => p.Dispose());
-
-            // On .NET Core, Dispose() in turn invokes Close() and since we're not mocking
-            // an interface, we need to expect this call as well
-            _pipeStreamMock.Setup(p => p.Close());
+#else
+            _pipeStreamMock.InSequence(sequence).Setup(p => p.Close());
+#endif
         }
 
         protected override void Arrange()
@@ -134,7 +134,11 @@ namespace Renci.SshNet.Tests.Classes
         [TestMethod]
         public void DisposeOnPipeStreamShouldBeInvokedOnce()
         {
+#if NET35
             _pipeStreamMock.As<IDisposable>().Verify(p => p.Dispose(), Times.Once);
+#else
+            _pipeStreamMock.Verify(p => p.Close(), Times.Once);
+#endif
         }
 
         [TestMethod]

+ 8 - 4
src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_StreamAndPath_SendExecRequestReturnsFalse.cs

@@ -57,11 +57,11 @@ namespace Renci.SshNet.Tests.Classes
                                .Setup(p => p.SendExecRequest(string.Format("scp -t -d {0}", _transformedPath)))
                                .Returns(false);
             _channelSessionMock.InSequence(sequence).Setup(p => p.Dispose());
+#if NET35
             _pipeStreamMock.As<IDisposable>().InSequence(sequence).Setup(p => p.Dispose());
-
-            // On .NET Core, Dispose() in turn invokes Close() and since we're not mocking
-            // an interface, we need to expect this call as well
-            _pipeStreamMock.Setup(p => p.Close());
+#else
+            _pipeStreamMock.InSequence(sequence).Setup(p => p.Close());
+#endif
         }
 
         protected override void Arrange()
@@ -119,7 +119,11 @@ namespace Renci.SshNet.Tests.Classes
         [TestMethod]
         public void DisposeOnPipeStreamShouldBeInvokedOnce()
         {
+#if NET35
             _pipeStreamMock.As<IDisposable>().Verify(p => p.Dispose(), Times.Once);
+#else
+            _pipeStreamMock.Verify(p => p.Close(), Times.Once);
+#endif
         }
 
         [TestMethod]

+ 69 - 0
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamAsyncTestBase.cs

@@ -0,0 +1,69 @@
+#if FEATURE_TAP
+using System;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Sftp;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+    public abstract class SftpFileStreamAsyncTestBase
+    {
+        internal Mock<ISftpSession> SftpSessionMock;
+        protected MockSequence MockSequence;
+
+        protected virtual Task ArrangeAsync()
+        {
+            SetupData();
+            CreateMocks();
+            SetupMocks();
+            return Task.CompletedTask;
+        }
+
+        protected virtual void SetupData()
+        {
+            MockSequence = new MockSequence();
+        }
+
+        protected abstract void SetupMocks();
+
+        private void CreateMocks()
+        {
+            SftpSessionMock = new Mock<ISftpSession>(MockBehavior.Strict);
+        }
+
+        [TestInitialize]
+        public async Task SetUpAsync()
+        {
+            await ArrangeAsync();
+            await ActAsync();
+        }
+
+        protected abstract Task ActAsync();
+
+        protected byte[] GenerateRandom(int length)
+        {
+            return GenerateRandom(length, new Random());
+        }
+
+        protected byte[] GenerateRandom(int length, Random random)
+        {
+            var buffer = new byte[length];
+            random.NextBytes(buffer);
+            return buffer;
+        }
+
+        protected byte[] GenerateRandom(uint length)
+        {
+            return GenerateRandom(length, new Random());
+        }
+
+        protected byte[] GenerateRandom(uint length, Random random)
+        {
+            var buffer = new byte[length];
+            random.NextBytes(buffer);
+            return buffer;
+        }
+    }
+}
+#endif

+ 57 - 0
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileAccessInvalid.cs

@@ -0,0 +1,57 @@
+#if FEATURE_TAP
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Renci.SshNet.Sftp;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+    [TestClass]
+    public class SftpFileStreamTest_OpenAsync_FileAccessInvalid : SftpFileStreamAsyncTestBase
+    {
+        private Random _random;
+        private string _path;
+        private FileMode _fileMode;
+        private FileAccess _fileAccess;
+        private int _bufferSize;
+        private ArgumentOutOfRangeException _actualException;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _random = new Random();
+            _path = _random.Next().ToString();
+            _fileMode = FileMode.Open;
+            _fileAccess = 0;
+            _bufferSize = _random.Next(5, 1000);
+        }
+
+        protected override void SetupMocks()
+        {
+        }
+
+        protected override async Task ActAsync()
+        {
+            try
+            {
+                await SftpFileStream.OpenAsync(SftpSessionMock.Object, _path, _fileMode, _fileAccess, _bufferSize, default);
+                Assert.Fail();
+            }
+            catch (ArgumentOutOfRangeException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void CtorShouldHaveThrownArgumentException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual("access", _actualException.ParamName);
+        }
+    }
+}
+#endif

+ 58 - 0
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeAppend_FileAccessRead.cs

@@ -0,0 +1,58 @@
+#if FEATURE_TAP
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Renci.SshNet.Sftp;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+    [TestClass]
+    public class SftpFileStreamTest_OpenAsync_FileModeAppend_FileAccessRead : SftpFileStreamAsyncTestBase
+    {
+        private Random _random;
+        private string _path;
+        private FileMode _fileMode;
+        private FileAccess _fileAccess;
+        private int _bufferSize;
+        private ArgumentException _actualException;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _random = new Random();
+            _path = _random.Next().ToString();
+            _fileMode = FileMode.Append;
+            _fileAccess = FileAccess.Read;
+            _bufferSize = _random.Next(5, 1000);
+        }
+
+        protected override void SetupMocks()
+        {
+        }
+
+        protected override async Task ActAsync()
+        {
+            try
+            {
+                await SftpFileStream.OpenAsync(SftpSessionMock.Object, _path, _fileMode, _fileAccess, _bufferSize, default);
+                Assert.Fail();
+            }
+            catch (ArgumentException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void CtorShouldHaveThrownArgumentException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual(string.Format("{0} mode can be requested only when combined with write-only access.", _fileMode), _actualException.Message);
+            Assert.IsNull(_actualException.ParamName);
+        }
+    }
+}
+#endif

+ 58 - 0
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeAppend_FileAccessReadWrite.cs

@@ -0,0 +1,58 @@
+#if FEATURE_TAP
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Renci.SshNet.Sftp;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+    [TestClass]
+    public class SftpFileStreamTest_OpenAsync_FileModeAppend_FileAccessReadWrite : SftpFileStreamAsyncTestBase
+    {
+        private Random _random;
+        private string _path;
+        private FileMode _fileMode;
+        private FileAccess _fileAccess;
+        private int _bufferSize;
+        private ArgumentException _actualException;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _random = new Random();
+            _path = _random.Next().ToString();
+            _fileMode = FileMode.Append;
+            _fileAccess = FileAccess.ReadWrite;
+            _bufferSize = _random.Next(5, 1000);
+        }
+
+        protected override void SetupMocks()
+        {
+        }
+
+        protected override async Task ActAsync()
+        {
+            try
+            {
+                await SftpFileStream.OpenAsync(SftpSessionMock.Object, _path, _fileMode, _fileAccess, _bufferSize, default);
+                Assert.Fail();
+            }
+            catch (ArgumentException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void CtorShouldHaveThrownArgumentException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual(string.Format("{0} mode can be requested only when combined with write-only access.", _fileMode), _actualException.Message);
+            Assert.IsNull(_actualException.ParamName);
+        }
+    }
+}
+#endif

+ 155 - 0
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeAppend_FileAccessWrite.cs

@@ -0,0 +1,155 @@
+#if FEATURE_TAP
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Sftp;
+using Renci.SshNet.Tests.Common;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+    [TestClass]
+    public class SftpFileStreamTest_OpenAsync_FileModeAppend_FileAccessWrite : SftpFileStreamAsyncTestBase
+    {
+        private Random _random;
+        private string _path;
+        private FileMode _fileMode;
+        private FileAccess _fileAccess;
+        private int _bufferSize;
+        private uint _readBufferSize;
+        private uint _writeBufferSize;
+        private byte[] _handle;
+        private SftpFileStream _target;
+        private SftpFileAttributes _fileAttributes;
+        private CancellationToken _cancellationToken;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _random = new Random();
+            _path = _random.Next().ToString();
+            _fileMode = FileMode.Append;
+            _fileAccess = FileAccess.Write;
+            _bufferSize = _random.Next(5, 1000);
+            _readBufferSize = (uint) _random.Next(5, 1000);
+            _writeBufferSize = (uint) _random.Next(5, 1000);
+            _handle = GenerateRandom(_random.Next(1, 10), _random);
+            _fileAttributes = new SftpFileAttributesBuilder().WithLastAccessTime(DateTime.UtcNow.AddSeconds(_random.Next()))
+                                                             .WithLastWriteTime(DateTime.UtcNow.AddSeconds(_random.Next()))
+                                                             .WithSize(_random.Next())
+                                                             .WithUserId(_random.Next())
+                                                             .WithGroupId(_random.Next())
+                                                             .WithPermissions((uint) _random.Next())
+                                                             .Build();
+            _cancellationToken = new CancellationToken();
+        }
+
+        protected override void SetupMocks()
+        {
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.RequestOpenAsync(_path, Flags.Write | Flags.Append | Flags.CreateNewOrOpen, _cancellationToken))
+                           .ReturnsAsync(_handle);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.RequestFStatAsync(_handle, _cancellationToken))
+                           .ReturnsAsync(_fileAttributes);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.CalculateOptimalReadLength((uint)_bufferSize))
+                           .Returns(_readBufferSize);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.CalculateOptimalWriteLength((uint)_bufferSize, _handle))
+                           .Returns(_writeBufferSize);
+        }
+
+        protected override async Task ActAsync()
+        {
+            _target = await SftpFileStream.OpenAsync(SftpSessionMock.Object, _path, _fileMode, _fileAccess, _bufferSize, _cancellationToken);
+        }
+
+
+        [TestMethod]
+        public void CanReadShouldReturnFalse()
+        {
+            Assert.IsFalse(_target.CanRead);
+        }
+
+        [TestMethod]
+        public void CanSeekShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanSeek);
+        }
+
+        [TestMethod]
+        public void CanWriteShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanWrite);
+        }
+
+        [TestMethod]
+        public void CanTimeoutShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanTimeout);
+        }
+
+        [TestMethod]
+        public void PositionShouldReturnSizeOfFile()
+        {
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+            var actual = _target.Position;
+
+            Assert.AreEqual(_fileAttributes.Size, actual);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+        }
+
+        [TestMethod]
+        public async Task ReadShouldThrowNotSupportedException()
+        {
+            var buffer = new byte[_readBufferSize];
+
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+            try
+            {
+                await _target.ReadAsync(buffer, 0, buffer.Length, _cancellationToken);
+                Assert.Fail();
+            }
+            catch (NotSupportedException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+            }
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+        }
+
+        [TestMethod]
+        public async Task WriteShouldStartWritingAtEndOfFile()
+        {
+            var buffer = new byte[_writeBufferSize];
+
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.RequestWriteAsync(_handle, (ulong)_fileAttributes.Size, buffer, 0, buffer.Length, _cancellationToken)).Returns(Task.CompletedTask);
+
+            await _target.WriteAsync(buffer, 0, buffer.Length, _cancellationToken);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+            SftpSessionMock.Verify(p => p.RequestWriteAsync(_handle, (ulong)_fileAttributes.Size, buffer, 0, buffer.Length, _cancellationToken), Times.Once);
+        }
+
+        [TestMethod]
+        public void RequestOpenOnSftpSessionShouldBeInvokedOnce()
+        {
+            SftpSessionMock.Verify(p => p.RequestOpenAsync(_path, Flags.Write | Flags.Append | Flags.CreateNewOrOpen, default), Times.Once);
+        }
+
+        [TestMethod]
+        public void RequestFStatOnSftpSessionShouldBeInvokedOnce()
+        {
+            SftpSessionMock.Verify(p => p.RequestFStatAsync(_handle, default), Times.Once);
+        }
+    }
+}
+#endif

+ 58 - 0
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeCreateNew_FileAccessRead.cs

@@ -0,0 +1,58 @@
+#if FEATURE_TAP
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Renci.SshNet.Sftp;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+    [TestClass]
+    public class SftpFileStreamTest_OpenAsync_FileModeCreateNew_FileAccessRead : SftpFileStreamAsyncTestBase
+    {
+        private Random _random;
+        private string _path;
+        private FileMode _fileMode;
+        private FileAccess _fileAccess;
+        private int _bufferSize;
+        private ArgumentException _actualException;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _random = new Random();
+            _path = _random.Next().ToString();
+            _fileMode = FileMode.CreateNew;
+            _fileAccess = FileAccess.Read;
+            _bufferSize = _random.Next(5, 1000);
+        }
+
+        protected override void SetupMocks()
+        {
+        }
+
+        protected override async Task ActAsync()
+        {
+            try
+            {
+                await SftpFileStream.OpenAsync(SftpSessionMock.Object, _path, _fileMode, _fileAccess, _bufferSize, default);
+                Assert.Fail();
+            }
+            catch (ArgumentException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void CtorShouldHaveThrownArgumentException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual(string.Format("Combining {0}: {1} with {2}: {3} is invalid.", typeof(FileMode).Name, _fileMode, typeof(FileAccess).Name, _fileAccess), _actualException.Message);
+            Assert.IsNull(_actualException.ParamName);
+        }
+    }
+}
+#endif

+ 136 - 0
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeCreateNew_FileAccessReadWrite.cs

@@ -0,0 +1,136 @@
+#if FEATURE_TAP
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Sftp;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+    [TestClass]
+    public class SftpFileStreamTest_OpenAsync_FileModeCreateNew_FileAccessReadWrite : SftpFileStreamAsyncTestBase
+    {
+        private Random _random;
+        private string _path;
+        private FileMode _fileMode;
+        private FileAccess _fileAccess;
+        private int _bufferSize;
+        private uint _readBufferSize;
+        private uint _writeBufferSize;
+        private byte[] _handle;
+        private SftpFileStream _target;
+        private CancellationToken _cancellationToken;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _random = new Random();
+            _path = _random.Next().ToString();
+            _fileMode = FileMode.CreateNew;
+            _fileAccess = FileAccess.ReadWrite;
+            _bufferSize = _random.Next(5, 1000);
+            _readBufferSize = (uint)_random.Next(5, 1000);
+            _writeBufferSize = (uint)_random.Next(5, 1000);
+            _handle = GenerateRandom(_random.Next(1, 10), _random);
+            _cancellationToken = new CancellationToken();
+        }
+
+        protected override void SetupMocks()
+        {
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.RequestOpenAsync(_path, Flags.Read | Flags.Write | Flags.CreateNew, _cancellationToken))
+                           .ReturnsAsync(_handle);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.CalculateOptimalReadLength((uint) _bufferSize))
+                           .Returns(_readBufferSize);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.CalculateOptimalWriteLength((uint) _bufferSize, _handle))
+                           .Returns(_writeBufferSize);
+        }
+
+        protected override async Task ActAsync()
+        {
+            _target = await SftpFileStream.OpenAsync(SftpSessionMock.Object, _path, _fileMode, _fileAccess, _bufferSize, _cancellationToken);
+        }
+
+        [TestMethod]
+        public void CanReadShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanRead);
+        }
+
+        [TestMethod]
+        public void CanSeekShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanSeek);
+        }
+
+        [TestMethod]
+        public void CanWriteShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanWrite);
+        }
+
+        [TestMethod]
+        public void CanTimeoutShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanTimeout);
+        }
+
+        [TestMethod]
+        public void PositionShouldReturnZero()
+        {
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+            var actual = _target.Position;
+
+            Assert.AreEqual(0L, actual);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+        }
+
+        [TestMethod]
+        public async Task ReadShouldStartReadingAtBeginningOfFile()
+        {
+            var buffer = new byte[8];
+            var data = new byte[] { 5, 4, 3, 2, 1 };
+            var expected = new byte[] { 0, 5, 4, 3, 2, 1, 0, 0 };
+
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.RequestReadAsync(_handle, 0UL, _readBufferSize, _cancellationToken)).ReturnsAsync(data);
+
+            var actual = await _target.ReadAsync(buffer, 1, data.Length, _cancellationToken);
+
+            Assert.AreEqual(data.Length, actual);
+            Assert.IsTrue(buffer.IsEqualTo(expected));
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+            SftpSessionMock.Verify(p => p.RequestReadAsync(_handle, 0UL, _readBufferSize, _cancellationToken), Times.Once);
+        }
+
+        [TestMethod]
+        public async Task WriteShouldStartWritingAtBeginningOfFile()
+        {
+            var buffer = new byte[_writeBufferSize];
+
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.RequestWriteAsync(_handle, 0UL, buffer, 0, buffer.Length, _cancellationToken)).Returns(Task.CompletedTask);
+
+            await _target.WriteAsync(buffer, 0, buffer.Length, _cancellationToken);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+            SftpSessionMock.Verify(p => p.RequestWriteAsync(_handle, 0UL, buffer, 0, buffer.Length, _cancellationToken), Times.Once);
+        }
+
+        [TestMethod]
+        public void RequestOpenOnSftpSessionShouldBeInvokedOnce()
+        {
+            SftpSessionMock.Verify(p => p.RequestOpenAsync(_path, Flags.Read | Flags.Write | Flags.CreateNew, _cancellationToken), Times.Once);
+        }
+    }
+}
+#endif

+ 136 - 0
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeCreateNew_FileAccessWrite.cs

@@ -0,0 +1,136 @@
+#if FEATURE_TAP
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Sftp;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+    [TestClass]
+    public class SftpFileStreamTest_OpenAsync_FileModeCreateNew_FileAccessWrite : SftpFileStreamAsyncTestBase
+    {
+        private Random _random;
+        private string _path;
+        private FileMode _fileMode;
+        private FileAccess _fileAccess;
+        private int _bufferSize;
+        private uint _readBufferSize;
+        private uint _writeBufferSize;
+        private byte[] _handle;
+        private SftpFileStream _target;
+        private CancellationToken _cancellationToken;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _random = new Random();
+            _path = _random.Next().ToString();
+            _fileMode = FileMode.CreateNew;
+            _fileAccess = FileAccess.Write;
+            _bufferSize = _random.Next(5, 1000);
+            _readBufferSize = (uint) _random.Next(5, 1000);
+            _writeBufferSize = (uint) _random.Next(5, 1000);
+            _handle = GenerateRandom(_random.Next(1, 10), _random);
+            _cancellationToken = new CancellationToken();
+        }
+
+        protected override void SetupMocks()
+        {
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.RequestOpenAsync(_path, Flags.Write | Flags.CreateNew, _cancellationToken))
+                           .ReturnsAsync(_handle);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.CalculateOptimalReadLength((uint) _bufferSize))
+                           .Returns(_readBufferSize);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.CalculateOptimalWriteLength((uint) _bufferSize, _handle))
+                           .Returns(_writeBufferSize);
+        }
+
+        protected override async Task ActAsync()
+        {
+            _target = await SftpFileStream.OpenAsync(SftpSessionMock.Object, _path, _fileMode, _fileAccess, _bufferSize, _cancellationToken);
+        }
+
+        [TestMethod]
+        public void CanReadShouldReturnFalse()
+        {
+            Assert.IsFalse(_target.CanRead);
+        }
+
+        [TestMethod]
+        public void CanSeekShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanSeek);
+        }
+
+        [TestMethod]
+        public void CanWriteShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanWrite);
+        }
+
+        [TestMethod]
+        public void CanTimeoutShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanTimeout);
+        }
+
+        [TestMethod]
+        public void PositionShouldReturnZero()
+        {
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+            var actual = _target.Position;
+
+            Assert.AreEqual(0L, actual);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+        }
+
+        [TestMethod]
+        public async Task ReadShouldThrowNotSupportedException()
+        {
+            var buffer = new byte[_readBufferSize];
+
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+            try
+            {
+                await _target.ReadAsync(buffer, 0, buffer.Length);
+                Assert.Fail();
+            }
+            catch (NotSupportedException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+            }
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+        }
+
+        [TestMethod]
+        public async Task WriteShouldStartWritingAtBeginningOfFile()
+        {
+            var buffer = new byte[_writeBufferSize];
+
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.RequestWriteAsync(_handle, 0UL, buffer, 0, buffer.Length, _cancellationToken)).Returns(Task.CompletedTask);
+
+            await _target.WriteAsync(buffer, 0, buffer.Length);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+            SftpSessionMock.Verify(p => p.RequestWriteAsync(_handle, 0UL, buffer, 0, buffer.Length, _cancellationToken), Times.Once);
+        }
+
+        [TestMethod]
+        public void RequestOpenOnSftpSessionShouldBeInvokedOnce()
+        {
+            SftpSessionMock.Verify(p => p.RequestOpenAsync(_path, Flags.Write | Flags.CreateNew, _cancellationToken), Times.Once);
+        }
+    }
+}
+#endif

+ 58 - 0
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeCreate_FileAccessRead.cs

@@ -0,0 +1,58 @@
+#if FEATURE_TAP
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Renci.SshNet.Sftp;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+    [TestClass]
+    public class SftpFileStreamTest_OpenAsync_FileModeCreate_FileAccessRead : SftpFileStreamAsyncTestBase
+    {
+        private Random _random;
+        private string _path;
+        private FileMode _fileMode;
+        private FileAccess _fileAccess;
+        private int _bufferSize;
+        private ArgumentException _actualException;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _random = new Random();
+            _path = _random.Next().ToString();
+            _fileMode = FileMode.Create;
+            _fileAccess = FileAccess.Read;
+            _bufferSize = _random.Next(5, 1000);
+        }
+
+        protected override void SetupMocks()
+        {
+        }
+
+        protected override async Task ActAsync()
+        {
+            try
+            {
+                await SftpFileStream.OpenAsync(SftpSessionMock.Object, _path, _fileMode, _fileAccess, _bufferSize, default);
+                Assert.Fail();
+            }
+            catch (ArgumentException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void CtorShouldHaveThrownArgumentException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual(string.Format("Combining {0}: {1} with {2}: {3} is invalid.", typeof(FileMode).Name, _fileMode, typeof(FileAccess).Name, _fileAccess), _actualException.Message);
+            Assert.IsNull(_actualException.ParamName);
+        }
+    }
+}
+#endif

+ 136 - 0
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeCreate_FileAccessReadWrite_FileDoesNotExist.cs

@@ -0,0 +1,136 @@
+#if FEATURE_TAP
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Sftp;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+    [TestClass]
+    public class SftpFileStreamTest_OpenAsync_FileModeCreate_FileAccessReadWrite_FileDoesNotExist : SftpFileStreamAsyncTestBase
+    {
+        private Random _random;
+        private string _path;
+        private FileMode _fileMode;
+        private FileAccess _fileAccess;
+        private int _bufferSize;
+        private uint _readBufferSize;
+        private uint _writeBufferSize;
+        private byte[] _handle;
+        private SftpFileStream _target;
+        private CancellationToken _cancellationToken;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _random = new Random();
+            _path = _random.Next().ToString();
+            _fileMode = FileMode.Create;
+            _fileAccess = FileAccess.ReadWrite;
+            _bufferSize = _random.Next(5, 1000);
+            _readBufferSize = (uint)_random.Next(5, 1000);
+            _writeBufferSize = (uint)_random.Next(5, 1000);
+            _handle = GenerateRandom(_random.Next(1, 10), _random);
+            _cancellationToken = new CancellationToken();
+        }
+
+        protected override void SetupMocks()
+        {
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.RequestOpenAsync(_path, Flags.Read | Flags.Write | Flags.CreateNewOrOpen | Flags.Truncate, _cancellationToken))
+                           .ReturnsAsync(_handle);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.CalculateOptimalReadLength((uint) _bufferSize))
+                           .Returns(_readBufferSize);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.CalculateOptimalWriteLength((uint) _bufferSize, _handle))
+                           .Returns(_writeBufferSize);
+        }
+
+        protected override async Task ActAsync()
+        {
+            _target = await SftpFileStream.OpenAsync(SftpSessionMock.Object, _path, _fileMode, _fileAccess, _bufferSize, _cancellationToken);
+        }
+
+        [TestMethod]
+        public void CanReadShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanRead);
+        }
+
+        [TestMethod]
+        public void CanSeekShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanSeek);
+        }
+
+        [TestMethod]
+        public void CanWriteShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanWrite);
+        }
+
+        [TestMethod]
+        public void CanTimeoutShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanTimeout);
+        }
+
+        [TestMethod]
+        public void PositionShouldReturnZero()
+        {
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+            var actual = _target.Position;
+
+            Assert.AreEqual(0L, actual);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+        }
+
+        [TestMethod]
+        public async Task ReadShouldStartReadingAtBeginningOfFile()
+        {
+            var buffer = new byte[8];
+            var data = new byte[] { 5, 4, 3, 2, 1 };
+            var expected = new byte[] { 0, 5, 4, 3, 2, 1, 0, 0 };
+
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.RequestReadAsync(_handle, 0UL, _readBufferSize, _cancellationToken)).ReturnsAsync(data);
+
+            var actual = await _target.ReadAsync(buffer, 1, data.Length);
+
+            Assert.AreEqual(data.Length, actual);
+            Assert.IsTrue(buffer.IsEqualTo(expected));
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+            SftpSessionMock.Verify(p => p.RequestReadAsync(_handle, 0UL, _readBufferSize, _cancellationToken), Times.Once);
+        }
+
+        [TestMethod]
+        public async Task WriteShouldStartWritingAtBeginningOfFile()
+        {
+            var buffer = new byte[_writeBufferSize];
+
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.RequestWriteAsync(_handle, 0UL, buffer, 0, buffer.Length, _cancellationToken)).Returns(Task.CompletedTask);
+
+            await _target.WriteAsync(buffer, 0, buffer.Length);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+            SftpSessionMock.Verify(p => p.RequestWriteAsync(_handle, 0UL, buffer, 0, buffer.Length, _cancellationToken), Times.Once);
+        }
+
+        [TestMethod]
+        public void RequestOpenOnSftpSessionShouldBeInvokedOnce()
+        {
+            SftpSessionMock.Verify(p => p.RequestOpenAsync(_path, Flags.Read | Flags.Write | Flags.CreateNewOrOpen | Flags.Truncate, _cancellationToken), Times.Once);
+        }
+    }
+}
+#endif

+ 136 - 0
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeCreate_FileAccessReadWrite_FileExists.cs

@@ -0,0 +1,136 @@
+#if FEATURE_TAP
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Sftp;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+    [TestClass]
+    public class SftpFileStreamTest_OpenAsync_FileModeCreate_FileAccessReadWrite_FileExists : SftpFileStreamAsyncTestBase
+    {
+        private Random _random;
+        private string _path;
+        private FileMode _fileMode;
+        private FileAccess _fileAccess;
+        private int _bufferSize;
+        private uint _readBufferSize;
+        private uint _writeBufferSize;
+        private byte[] _handle;
+        private SftpFileStream _target;
+        private CancellationToken _cancellationToken;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _random = new Random();
+            _path = _random.Next().ToString();
+            _fileMode = FileMode.Create;
+            _fileAccess = FileAccess.ReadWrite;
+            _bufferSize = _random.Next(5, 1000);
+            _readBufferSize = (uint)_random.Next(5, 1000);
+            _writeBufferSize = (uint)_random.Next(5, 1000);
+            _handle = GenerateRandom(_random.Next(1, 10), _random);
+            _cancellationToken = new CancellationToken();
+        }
+
+        protected override void SetupMocks()
+        {
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.RequestOpenAsync(_path, Flags.Read | Flags.Write | Flags.CreateNewOrOpen | Flags.Truncate, _cancellationToken))
+                           .ReturnsAsync(_handle);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.CalculateOptimalReadLength((uint) _bufferSize))
+                           .Returns(_readBufferSize);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.CalculateOptimalWriteLength((uint) _bufferSize, _handle))
+                           .Returns(_writeBufferSize);
+        }
+
+        protected override async Task ActAsync()
+        {
+            _target = await SftpFileStream.OpenAsync(SftpSessionMock.Object, _path, _fileMode, _fileAccess, _bufferSize, _cancellationToken);
+        }
+
+        [TestMethod]
+        public void CanReadShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanRead);
+        }
+
+        [TestMethod]
+        public void CanSeekShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanSeek);
+        }
+
+        [TestMethod]
+        public void CanWriteShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanWrite);
+        }
+
+        [TestMethod]
+        public void CanTimeoutShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanTimeout);
+        }
+
+        [TestMethod]
+        public void PositionShouldReturnZero()
+        {
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+            var actual = _target.Position;
+
+            Assert.AreEqual(0L, actual);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+        }
+
+        [TestMethod]
+        public async Task ReadShouldStartReadingAtBeginningOfFile()
+        {
+            var buffer = new byte[8];
+            var data = new byte[] { 5, 4, 3, 2, 1 };
+            var expected = new byte[] { 0, 5, 4, 3, 2, 1, 0, 0 };
+
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.RequestReadAsync(_handle, 0UL, _readBufferSize, _cancellationToken)).ReturnsAsync(data);
+
+            var actual = await _target.ReadAsync(buffer, 1, data.Length);
+
+            Assert.AreEqual(data.Length, actual);
+            Assert.IsTrue(buffer.IsEqualTo(expected));
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+            SftpSessionMock.Verify(p => p.RequestReadAsync(_handle, 0UL, _readBufferSize, _cancellationToken), Times.Once);
+        }
+
+        [TestMethod]
+        public async Task WriteShouldStartWritingAtBeginningOfFile()
+        {
+            var buffer = new byte[_writeBufferSize];
+
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.RequestWriteAsync(_handle, 0UL, buffer, 0, buffer.Length, _cancellationToken)).Returns(Task.CompletedTask);
+
+            await _target.WriteAsync(buffer, 0, buffer.Length);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+            SftpSessionMock.Verify(p => p.RequestWriteAsync(_handle, 0UL, buffer, 0, buffer.Length, _cancellationToken), Times.Once);
+        }
+
+        [TestMethod]
+        public void RequestOpenOnSftpSessionShouldBeInvokedOnce()
+        {
+            SftpSessionMock.Verify(p => p.RequestOpenAsync(_path, Flags.Read | Flags.Write | Flags.CreateNewOrOpen | Flags.Truncate, _cancellationToken), Times.Once);
+        }
+    }
+}
+#endif

+ 136 - 0
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeCreate_FileAccessWrite_FileDoesNotExist.cs

@@ -0,0 +1,136 @@
+#if FEATURE_TAP
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Sftp;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+    [TestClass]
+    public class SftpFileStreamTest_OpenAsync_FileModeCreate_FileAccessWrite_FileDoesNotExist : SftpFileStreamAsyncTestBase
+    {
+        private Random _random;
+        private string _path;
+        private FileMode _fileMode;
+        private FileAccess _fileAccess;
+        private int _bufferSize;
+        private uint _readBufferSize;
+        private uint _writeBufferSize;
+        private byte[] _handle;
+        private SftpFileStream _target;
+        private CancellationToken _cancellationToken;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _random = new Random();
+            _path = _random.Next().ToString();
+            _fileMode = FileMode.Create;
+            _fileAccess = FileAccess.Write;
+            _bufferSize = _random.Next(5, 1000);
+            _readBufferSize = (uint)_random.Next(5, 1000);
+            _writeBufferSize = (uint)_random.Next(5, 1000);
+            _handle = GenerateRandom(_random.Next(1, 10), _random);
+            _cancellationToken = new CancellationToken();
+        }
+
+        protected override void SetupMocks()
+        {
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.RequestOpenAsync(_path, Flags.Write | Flags.CreateNewOrOpen | Flags.Truncate, _cancellationToken))
+                           .ReturnsAsync(_handle);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.CalculateOptimalReadLength((uint) _bufferSize))
+                           .Returns(_readBufferSize);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.CalculateOptimalWriteLength((uint) _bufferSize, _handle))
+                           .Returns(_writeBufferSize);
+        }
+
+        protected override async Task ActAsync()
+        {
+            _target = await SftpFileStream.OpenAsync(SftpSessionMock.Object, _path, _fileMode, _fileAccess, _bufferSize, _cancellationToken);
+        }
+
+        [TestMethod]
+        public void CanReadShouldReturnFalse()
+        {
+            Assert.IsFalse(_target.CanRead);
+        }
+
+        [TestMethod]
+        public void CanSeekShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanSeek);
+        }
+
+        [TestMethod]
+        public void CanWriteShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanWrite);
+        }
+
+        [TestMethod]
+        public void CanTimeoutShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanTimeout);
+        }
+
+        [TestMethod]
+        public void PositionShouldReturnZero()
+        {
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+            var actual = _target.Position;
+
+            Assert.AreEqual(0L, actual);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+        }
+
+        [TestMethod]
+        public async Task ReadShouldThrowNotSupportedException()
+        {
+            var buffer = new byte[_readBufferSize];
+
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+            try
+            {
+                await _target.ReadAsync(buffer, 0, buffer.Length);
+                Assert.Fail();
+            }
+            catch (NotSupportedException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+            }
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+        }
+
+        [TestMethod]
+        public async Task WriteShouldStartWritingAtBeginningOfFile()
+        {
+            var buffer = new byte[_writeBufferSize];
+
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.RequestWriteAsync(_handle, 0UL, buffer, 0, buffer.Length, _cancellationToken)).Returns(Task.CompletedTask);
+
+            await _target.WriteAsync(buffer, 0, buffer.Length);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+            SftpSessionMock.Verify(p => p.RequestWriteAsync(_handle, 0UL, buffer, 0, buffer.Length, _cancellationToken), Times.Once);
+        }
+
+        [TestMethod]
+        public void RequestOpenOnSftpSessionShouldBeInvokedOnceWithTruncateAndOnceWithCreateNew()
+        {
+            SftpSessionMock.Verify(p => p.RequestOpenAsync(_path, Flags.Write | Flags.CreateNewOrOpen | Flags.Truncate, _cancellationToken), Times.Once);
+        }
+    }
+}
+#endif

+ 136 - 0
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeCreate_FileAccessWrite_FileExists.cs

@@ -0,0 +1,136 @@
+#if FEATURE_TAP
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Sftp;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+    [TestClass]
+    public class SftpFileStreamTest_OpenAsync_FileModeCreate_FileAccessWrite_FileExists : SftpFileStreamAsyncTestBase
+    {
+        private Random _random;
+        private string _path;
+        private FileMode _fileMode;
+        private FileAccess _fileAccess;
+        private int _bufferSize;
+        private uint _readBufferSize;
+        private uint _writeBufferSize;
+        private byte[] _handle;
+        private SftpFileStream _target;
+        private CancellationToken _cancellationToken;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _random = new Random();
+            _path = _random.Next().ToString();
+            _fileMode = FileMode.Create;
+            _fileAccess = FileAccess.Write;
+            _bufferSize = _random.Next(5, 1000);
+            _readBufferSize = (uint) _random.Next(5, 1000);
+            _writeBufferSize = (uint) _random.Next(5, 1000);
+            _handle = GenerateRandom(_random.Next(1, 10), _random);
+            _cancellationToken = new CancellationToken();
+        }
+
+        protected override void SetupMocks()
+        {
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.RequestOpenAsync(_path, Flags.Write | Flags.CreateNewOrOpen | Flags.Truncate, _cancellationToken))
+                           .ReturnsAsync(_handle);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.CalculateOptimalReadLength((uint) _bufferSize))
+                           .Returns(_readBufferSize);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.CalculateOptimalWriteLength((uint) _bufferSize, _handle))
+                           .Returns(_writeBufferSize);
+        }
+
+        protected override async Task ActAsync()
+        {
+            _target = await SftpFileStream.OpenAsync(SftpSessionMock.Object, _path, _fileMode, _fileAccess, _bufferSize, _cancellationToken);
+        }
+
+        [TestMethod]
+        public void CanReadShouldReturnFalse()
+        {
+            Assert.IsFalse(_target.CanRead);
+        }
+
+        [TestMethod]
+        public void CanSeekShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanSeek);
+        }
+
+        [TestMethod]
+        public void CanWriteShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanWrite);
+        }
+
+        [TestMethod]
+        public void CanTimeoutShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanTimeout);
+        }
+
+        [TestMethod]
+        public void PositionShouldReturnZero()
+        {
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+            var actual = _target.Position;
+
+            Assert.AreEqual(0L, actual);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+        }
+
+        [TestMethod]
+        public async Task ReadShouldThrowNotSupportedException()
+        {
+            var buffer = new byte[_readBufferSize];
+
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+            try
+            {
+                await _target.ReadAsync(buffer, 0, buffer.Length);
+                Assert.Fail();
+            }
+            catch (NotSupportedException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+            }
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+        }
+
+        [TestMethod]
+        public async Task WriteShouldStartWritingAtBeginningOfFile()
+        {
+            var buffer = new byte[_writeBufferSize];
+
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.RequestWriteAsync(_handle, 0UL, buffer, 0, buffer.Length, _cancellationToken)).Returns(Task.CompletedTask);
+
+            await _target.WriteAsync(buffer, 0, buffer.Length);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+            SftpSessionMock.Verify(p => p.RequestWriteAsync(_handle, 0UL, buffer, 0, buffer.Length, _cancellationToken), Times.Once);
+        }
+
+        [TestMethod]
+        public void RequestOpenOnSftpSessionShouldBeInvokedOnce()
+        {
+            SftpSessionMock.Verify(p => p.RequestOpenAsync(_path, Flags.Write | Flags.CreateNewOrOpen | Flags.Truncate, _cancellationToken), Times.Once);
+        }
+    }
+}
+#endif

+ 57 - 0
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeInvalid.cs

@@ -0,0 +1,57 @@
+#if FEATURE_TAP
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Renci.SshNet.Sftp;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+    [TestClass]
+    public class SftpFileStreamTest_OpenAsync_FileModeInvalid : SftpFileStreamAsyncTestBase
+    {
+        private Random _random;
+        private string _path;
+        private FileMode _fileMode;
+        private FileAccess _fileAccess;
+        private int _bufferSize;
+        private ArgumentOutOfRangeException _actualException;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _random = new Random();
+            _path = _random.Next().ToString();
+            _fileMode = 0;
+            _fileAccess = FileAccess.Read;
+            _bufferSize = _random.Next(5, 1000);
+        }
+
+        protected override void SetupMocks()
+        {
+        }
+
+        protected override async Task ActAsync()
+        {
+            try
+            {
+                await SftpFileStream.OpenAsync(SftpSessionMock.Object, _path, _fileMode, _fileAccess, _bufferSize, default);
+                Assert.Fail();
+            }
+            catch (ArgumentOutOfRangeException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void CtorShouldHaveThrownArgumentException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual("mode", _actualException.ParamName);
+        }
+    }
+}
+#endif

+ 141 - 0
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeOpenOrCreate_FileAccessRead.cs

@@ -0,0 +1,141 @@
+#if FEATURE_TAP
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Sftp;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+    [TestClass]
+    public class SftpFileStreamTest_OpenAsync_FileModeOpenOrCreate_FileAccessRead : SftpFileStreamAsyncTestBase
+    {
+        private Random _random;
+        private string _path;
+        private FileMode _fileMode;
+        private FileAccess _fileAccess;
+        private int _bufferSize;
+        private uint _readBufferSize;
+        private uint _writeBufferSize;
+        private byte[] _handle;
+        private SftpFileStream _target;
+        private CancellationToken _cancellationToken;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _random = new Random();
+            _path = _random.Next().ToString();
+            _fileMode = FileMode.OpenOrCreate;
+            _fileAccess = FileAccess.Read;
+            _bufferSize = _random.Next(5, 1000);
+            _readBufferSize = (uint) _random.Next(5, 1000);
+            _writeBufferSize = (uint) _random.Next(5, 1000);
+            _handle = GenerateRandom(_random.Next(1, 10), _random);
+            _cancellationToken = new CancellationToken();
+        }
+
+        protected override void SetupMocks()
+        {
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.RequestOpenAsync(_path, Flags.Read | Flags.CreateNewOrOpen, _cancellationToken))
+                           .ReturnsAsync(_handle);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.CalculateOptimalReadLength((uint) _bufferSize))
+                           .Returns(_readBufferSize);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.CalculateOptimalWriteLength((uint) _bufferSize, _handle))
+                           .Returns(_writeBufferSize);
+        }
+
+        protected override async Task ActAsync()
+        {
+            _target = await SftpFileStream.OpenAsync(SftpSessionMock.Object, _path, _fileMode, _fileAccess, _bufferSize, _cancellationToken);
+        }
+
+        [TestMethod]
+        public void CanReadShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanRead);
+        }
+
+        [TestMethod]
+        public void CanSeekShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanSeek);
+        }
+
+        [TestMethod]
+        public void CanWriteShouldReturnFalse()
+        {
+            Assert.IsFalse(_target.CanWrite);
+        }
+
+        [TestMethod]
+        public void CanTimeoutShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanTimeout);
+        }
+
+        [TestMethod]
+        public void PositionShouldReturnZero()
+        {
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+            var actual = _target.Position;
+
+            Assert.AreEqual(0L, actual);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+        }
+
+        [TestMethod]
+        public async Task ReadShouldStartReadingAtBeginningOfFile()
+        {
+            var buffer = new byte[8];
+            var data = new byte[] { 5, 4, 3, 2, 1 };
+            var expected = new byte[] { 0, 5, 4, 3, 2, 1, 0, 0 };
+
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.RequestReadAsync(_handle, 0UL, _readBufferSize, _cancellationToken)).ReturnsAsync(data);
+
+            var actual = await _target.ReadAsync(buffer, 1, data.Length);
+
+            Assert.AreEqual(data.Length, actual);
+            Assert.IsTrue(buffer.IsEqualTo(expected));
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+            SftpSessionMock.Verify(p => p.RequestReadAsync(_handle, 0UL, _readBufferSize, _cancellationToken), Times.Once);
+        }
+
+        [TestMethod]
+        public async Task WriteShouldThrowNotSupportedException()
+        {
+            var buffer = new byte[_writeBufferSize];
+
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+            try
+            {
+                await _target.WriteAsync(buffer, 0, buffer.Length);
+            }
+            catch (NotSupportedException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+            }
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+        }
+
+        [TestMethod]
+        public void RequestOpenOnSftpSessionShouldBeInvokedOnce()
+        {
+            SftpSessionMock.Verify(p => p.RequestOpenAsync(_path, Flags.Read | Flags.CreateNewOrOpen, _cancellationToken), Times.Once);
+        }
+    }
+}
+#endif

+ 136 - 0
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeOpenOrCreate_FileAccessReadWrite.cs

@@ -0,0 +1,136 @@
+#if FEATURE_TAP
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Sftp;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+    [TestClass]
+    public class SftpFileStreamTest_OpenAsync_FileModeOpenOrCreate_FileAccessReadWrite : SftpFileStreamAsyncTestBase
+    {
+        private Random _random;
+        private string _path;
+        private FileMode _fileMode;
+        private FileAccess _fileAccess;
+        private int _bufferSize;
+        private uint _readBufferSize;
+        private uint _writeBufferSize;
+        private byte[] _handle;
+        private SftpFileStream _target;
+        private CancellationToken _cancellationToken;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _random = new Random();
+            _path = _random.Next().ToString();
+            _fileMode = FileMode.OpenOrCreate;
+            _fileAccess = FileAccess.ReadWrite;
+            _bufferSize = _random.Next(5, 1000);
+            _readBufferSize = (uint)_random.Next(5, 1000);
+            _writeBufferSize = (uint)_random.Next(5, 1000);
+            _handle = GenerateRandom(_random.Next(1, 10), _random);
+            _cancellationToken = new CancellationToken();
+        }
+
+        protected override void SetupMocks()
+        {
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.RequestOpenAsync(_path, Flags.Read | Flags.Write | Flags.CreateNewOrOpen, _cancellationToken))
+                           .ReturnsAsync(_handle);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.CalculateOptimalReadLength((uint) _bufferSize))
+                           .Returns(_readBufferSize);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.CalculateOptimalWriteLength((uint) _bufferSize, _handle))
+                           .Returns(_writeBufferSize);
+        }
+
+        protected override async Task ActAsync()
+        {
+            _target = await SftpFileStream.OpenAsync(SftpSessionMock.Object, _path, _fileMode, _fileAccess, _bufferSize, _cancellationToken);
+        }
+
+        [TestMethod]
+        public void CanReadShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanRead);
+        }
+
+        [TestMethod]
+        public void CanSeekShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanSeek);
+        }
+
+        [TestMethod]
+        public void CanWriteShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanWrite);
+        }
+
+        [TestMethod]
+        public void CanTimeoutShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanTimeout);
+        }
+
+        [TestMethod]
+        public void PositionShouldReturnZero()
+        {
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+            var actual = _target.Position;
+
+            Assert.AreEqual(0L, actual);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+        }
+
+        [TestMethod]
+        public async Task ReadShouldStartReadingAtBeginningOfFile()
+        {
+            var buffer = new byte[8];
+            var data = new byte[] { 5, 4, 3, 2, 1 };
+            var expected = new byte[] { 0, 5, 4, 3, 2, 1, 0, 0 };
+
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.RequestReadAsync(_handle, 0UL, _readBufferSize, _cancellationToken)).ReturnsAsync(data);
+
+            var actual = await _target.ReadAsync(buffer, 1, data.Length);
+
+            Assert.AreEqual(data.Length, actual);
+            Assert.IsTrue(buffer.IsEqualTo(expected));
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+            SftpSessionMock.Verify(p => p.RequestReadAsync(_handle, 0UL, _readBufferSize, _cancellationToken), Times.Once);
+        }
+
+        [TestMethod]
+        public async Task WriteShouldStartWritingAtBeginningOfFile()
+        {
+            var buffer = new byte[_writeBufferSize];
+
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.RequestWriteAsync(_handle, 0UL, buffer, 0, buffer.Length, _cancellationToken)).Returns(Task.CompletedTask);
+
+            await _target.WriteAsync(buffer, 0, buffer.Length);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+            SftpSessionMock.Verify(p => p.RequestWriteAsync(_handle, 0UL, buffer, 0, buffer.Length, _cancellationToken), Times.Once);
+        }
+
+        [TestMethod]
+        public void RequestOpenOnSftpSessionShouldBeInvokedOnce()
+        {
+            SftpSessionMock.Verify(p => p.RequestOpenAsync(_path, Flags.Read | Flags.Write | Flags.CreateNewOrOpen, _cancellationToken), Times.Once);
+        }
+    }
+}
+#endif

+ 136 - 0
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeOpenOrCreate_FileAccessWrite.cs

@@ -0,0 +1,136 @@
+#if FEATURE_TAP
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Sftp;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+    [TestClass]
+    public class SftpFileStreamTest_OpenAsync_FileModeOpenOrCreate_FileAccessWrite : SftpFileStreamAsyncTestBase
+    {
+        private Random _random;
+        private string _path;
+        private FileMode _fileMode;
+        private FileAccess _fileAccess;
+        private int _bufferSize;
+        private uint _readBufferSize;
+        private uint _writeBufferSize;
+        private byte[] _handle;
+        private SftpFileStream _target;
+        private CancellationToken _cancellationToken;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _random = new Random();
+            _path = _random.Next().ToString();
+            _fileMode = FileMode.OpenOrCreate;
+            _fileAccess = FileAccess.Write;
+            _bufferSize = _random.Next(5, 1000);
+            _readBufferSize = (uint) _random.Next(5, 1000);
+            _writeBufferSize = (uint) _random.Next(5, 1000);
+            _handle = GenerateRandom(_random.Next(1, 10), _random);
+            _cancellationToken = new CancellationToken();
+        }
+
+        protected override void SetupMocks()
+        {
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.RequestOpenAsync(_path, Flags.Write | Flags.CreateNewOrOpen, _cancellationToken))
+                           .ReturnsAsync(_handle);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.CalculateOptimalReadLength((uint) _bufferSize))
+                           .Returns(_readBufferSize);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.CalculateOptimalWriteLength((uint) _bufferSize, _handle))
+                           .Returns(_writeBufferSize);
+        }
+
+        protected override async Task ActAsync()
+        {
+            _target = await SftpFileStream.OpenAsync(SftpSessionMock.Object, _path, _fileMode, _fileAccess, _bufferSize, _cancellationToken);
+        }
+
+        [TestMethod]
+        public void CanReadShouldReturnFalse()
+        {
+            Assert.IsFalse(_target.CanRead);
+        }
+
+        [TestMethod]
+        public void CanSeekShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanSeek);
+        }
+
+        [TestMethod]
+        public void CanWriteShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanWrite);
+        }
+
+        [TestMethod]
+        public void CanTimeoutShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanTimeout);
+        }
+
+        [TestMethod]
+        public void PositionShouldReturnZero()
+        {
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+            var actual = _target.Position;
+
+            Assert.AreEqual(0L, actual);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+        }
+
+        [TestMethod]
+        public async Task ReadShouldThrowNotSupportedException()
+        {
+            var buffer = new byte[_readBufferSize];
+
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+            try
+            {
+                await _target.ReadAsync(buffer, 0, buffer.Length);
+                Assert.Fail();
+            }
+            catch (NotSupportedException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+            }
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+        }
+
+        [TestMethod]
+        public async Task WriteShouldStartWritingAtBeginningOfFile()
+        {
+            var buffer = new byte[_writeBufferSize];
+
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.RequestWriteAsync(_handle, 0UL, buffer, 0, buffer.Length, _cancellationToken)).Returns(Task.CompletedTask);
+
+            await _target.WriteAsync(buffer, 0, buffer.Length);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+            SftpSessionMock.Verify(p => p.RequestWriteAsync(_handle, 0UL, buffer, 0, buffer.Length, _cancellationToken), Times.Once);
+        }
+
+        [TestMethod]
+        public void RequestOpenOnSftpSessionShouldBeInvokedOnce()
+        {
+            SftpSessionMock.Verify(p => p.RequestOpenAsync(_path, Flags.Write | Flags.CreateNewOrOpen, _cancellationToken), Times.Once);
+        }
+    }
+}
+#endif

+ 142 - 0
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeOpen_FileAccessRead.cs

@@ -0,0 +1,142 @@
+#if FEATURE_TAP
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Sftp;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+    [TestClass]
+    public class SftpFileStreamTest_OpenAsync_FileModeOpen_FileAccessRead : SftpFileStreamAsyncTestBase
+    {
+        private Random _random;
+        private string _path;
+        private FileMode _fileMode;
+        private FileAccess _fileAccess;
+        private int _bufferSize;
+        private uint _readBufferSize;
+        private uint _writeBufferSize;
+        private byte[] _handle;
+        private SftpFileStream _target;
+        private CancellationToken _cancellationToken;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _random = new Random();
+            _path = _random.Next().ToString();
+            _fileMode = FileMode.Open;
+            _fileAccess = FileAccess.Read;
+            _bufferSize = _random.Next(5, 1000);
+            _readBufferSize = (uint)_random.Next(5, 1000);
+            _writeBufferSize = (uint)_random.Next(5, 1000);
+            _handle = GenerateRandom(_random.Next(1, 10), _random);
+            _cancellationToken = new CancellationToken();
+        }
+
+        protected override void SetupMocks()
+        {
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.RequestOpenAsync(_path, Flags.Read, _cancellationToken))
+                           .ReturnsAsync(_handle);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.CalculateOptimalReadLength((uint) _bufferSize))
+                           .Returns(_readBufferSize);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.CalculateOptimalWriteLength((uint) _bufferSize, _handle))
+                           .Returns(_writeBufferSize);
+        }
+
+        protected override async Task ActAsync()
+        {
+            _target = await SftpFileStream.OpenAsync(SftpSessionMock.Object, _path, _fileMode, _fileAccess, _bufferSize, _cancellationToken);
+        }
+
+        [TestMethod]
+        public void CanReadShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanRead);
+        }
+
+        [TestMethod]
+        public void CanSeekShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanSeek);
+        }
+
+        [TestMethod]
+        public void CanWriteShouldReturnFalse()
+        {
+            Assert.IsFalse(_target.CanWrite);
+        }
+
+        [TestMethod]
+        public void CanTimeoutShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanTimeout);
+        }
+
+        [TestMethod]
+        public void PositionShouldReturnZero()
+        {
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+            var actual = _target.Position;
+
+            Assert.AreEqual(0L, actual);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+        }
+
+        [TestMethod]
+        public async Task ReadShouldStartReadingAtBeginningOfFile()
+        {
+            var buffer = new byte[8];
+            var data = new byte[] { 5, 4, 3, 2, 1 };
+            var expected = new byte[] { 0, 5, 4, 3, 2, 1, 0, 0 };
+
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.RequestReadAsync(_handle, 0UL, _readBufferSize, _cancellationToken)).ReturnsAsync(data);
+
+            var actual = await _target.ReadAsync(buffer, 1, data.Length, _cancellationToken);
+
+            Assert.AreEqual(data.Length, actual);
+            Assert.IsTrue(buffer.IsEqualTo(expected));
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+            SftpSessionMock.Verify(p => p.RequestReadAsync(_handle, 0UL, _readBufferSize, _cancellationToken), Times.Once);
+        }
+
+        [TestMethod]
+        public async Task WriteShouldThrowNotSupportedException()
+        {
+            var buffer = new byte[_writeBufferSize];
+
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+            try
+            {
+                await _target.WriteAsync(buffer, 0, buffer.Length, _cancellationToken);
+            }
+            catch (NotSupportedException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+                Assert.AreEqual("Write not supported.", ex.Message);
+            }
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+        }
+
+        [TestMethod]
+        public void RequestOpenOnSftpSessionShouldBeInvokedOnce()
+        {
+            SftpSessionMock.Verify(p => p.RequestOpenAsync(_path, Flags.Read, _cancellationToken), Times.Once);
+        }
+    }
+}
+#endif

+ 136 - 0
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeOpen_FileAccessReadWrite.cs

@@ -0,0 +1,136 @@
+#if FEATURE_TAP
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Sftp;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+    [TestClass]
+    public class SftpFileStreamTest_OpenAsync_FileModeOpen_FileAccessReadWrite : SftpFileStreamAsyncTestBase
+    {
+        private Random _random;
+        private string _path;
+        private FileMode _fileMode;
+        private FileAccess _fileAccess;
+        private int _bufferSize;
+        private uint _readBufferSize;
+        private uint _writeBufferSize;
+        private byte[] _handle;
+        private SftpFileStream _target;
+        private CancellationToken _cancellationToken;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _random = new Random();
+            _path = _random.Next().ToString();
+            _fileMode = FileMode.Open;
+            _fileAccess = FileAccess.ReadWrite;
+            _bufferSize = _random.Next(5, 1000);
+            _readBufferSize = (uint) _random.Next(5, 1000);
+            _writeBufferSize = (uint) _random.Next(5, 1000);
+            _handle = GenerateRandom(_random.Next(1, 10), _random);
+            _cancellationToken = new CancellationToken();
+        }
+
+        protected override void SetupMocks()
+        {
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.RequestOpenAsync(_path, Flags.Read | Flags.Write, _cancellationToken))
+                           .ReturnsAsync(_handle);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.CalculateOptimalReadLength((uint) _bufferSize))
+                           .Returns(_readBufferSize);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.CalculateOptimalWriteLength((uint) _bufferSize, _handle))
+                           .Returns(_writeBufferSize);
+        }
+
+        protected override async Task ActAsync()
+        {
+            _target = await SftpFileStream.OpenAsync(SftpSessionMock.Object, _path, _fileMode, _fileAccess, _bufferSize, _cancellationToken);
+        }
+
+        [TestMethod]
+        public void CanReadShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanRead);
+        }
+
+        [TestMethod]
+        public void CanSeekShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanSeek);
+        }
+
+        [TestMethod]
+        public void CanWriteShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanWrite);
+        }
+
+        [TestMethod]
+        public void CanTimeoutShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanTimeout);
+        }
+
+        [TestMethod]
+        public void PositionShouldReturnZero()
+        {
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+            var actual = _target.Position;
+
+            Assert.AreEqual(0L, actual);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+        }
+
+        [TestMethod]
+        public async Task ReadShouldStartReadingAtBeginningOfFile()
+        {
+            var buffer = new byte[8];
+            var data = new byte[] { 5, 4, 3, 2, 1 };
+            var expected = new byte[] { 0, 5, 4, 3, 2, 1, 0, 0 };
+
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.RequestReadAsync(_handle, 0UL, _readBufferSize, _cancellationToken)).ReturnsAsync(data);
+
+            var actual = await _target.ReadAsync(buffer, 1, data.Length);
+
+            Assert.AreEqual(data.Length, actual);
+            Assert.IsTrue(buffer.IsEqualTo(expected));
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+            SftpSessionMock.Verify(p => p.RequestReadAsync(_handle, 0UL, _readBufferSize, _cancellationToken), Times.Once);
+        }
+
+        [TestMethod]
+        public async Task WriteShouldStartWritingAtBeginningOfFile()
+        {
+            var buffer = new byte[_writeBufferSize];
+
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.RequestWriteAsync(_handle, 0UL, buffer, 0, buffer.Length, _cancellationToken)).Returns(Task.CompletedTask);
+
+            await _target.WriteAsync(buffer, 0, buffer.Length);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+            SftpSessionMock.Verify(p => p.RequestWriteAsync(_handle, 0UL, buffer, 0, buffer.Length, _cancellationToken), Times.Once);
+        }
+
+        [TestMethod]
+        public void RequestOpenOnSftpSessionShouldBeInvokedOnce()
+        {
+            SftpSessionMock.Verify(p => p.RequestOpenAsync(_path, Flags.Read | Flags.Write, _cancellationToken), Times.Once);
+        }
+    }
+}
+#endif

+ 136 - 0
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeOpen_FileAccessWrite.cs

@@ -0,0 +1,136 @@
+#if FEATURE_TAP
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Sftp;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+    [TestClass]
+    public class SftpFileStreamTest_OpenAsync_FileModeOpen_FileAccessWrite : SftpFileStreamAsyncTestBase
+    {
+        private Random _random;
+        private string _path;
+        private FileMode _fileMode;
+        private FileAccess _fileAccess;
+        private int _bufferSize;
+        private uint _readBufferSize;
+        private uint _writeBufferSize;
+        private byte[] _handle;
+        private SftpFileStream _target;
+        private CancellationToken _cancellationToken;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _random = new Random();
+            _path = _random.Next().ToString();
+            _fileMode = FileMode.Open;
+            _fileAccess = FileAccess.Write;
+            _bufferSize = _random.Next(5, 1000);
+            _readBufferSize = (uint) _random.Next(5, 1000);
+            _writeBufferSize = (uint) _random.Next(5, 1000);
+            _handle = GenerateRandom(_random.Next(1, 10), _random);
+            _cancellationToken = new CancellationToken();
+        }
+
+        protected override void SetupMocks()
+        {
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.RequestOpenAsync(_path, Flags.Write, _cancellationToken))
+                           .ReturnsAsync(_handle);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.CalculateOptimalReadLength((uint) _bufferSize))
+                           .Returns(_readBufferSize);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.CalculateOptimalWriteLength((uint) _bufferSize, _handle))
+                           .Returns(_writeBufferSize);
+        }
+
+        protected override async Task ActAsync()
+        {
+            _target = await SftpFileStream.OpenAsync(SftpSessionMock.Object, _path, _fileMode, _fileAccess, _bufferSize, _cancellationToken);
+        }
+
+        [TestMethod]
+        public void CanReadShouldReturnFalse()
+        {
+            Assert.IsFalse(_target.CanRead);
+        }
+
+        [TestMethod]
+        public void CanSeekShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanSeek);
+        }
+
+        [TestMethod]
+        public void CanWriteShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanWrite);
+        }
+
+        [TestMethod]
+        public void CanTimeoutShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanTimeout);
+        }
+
+        [TestMethod]
+        public void PositionShouldReturnZero()
+        {
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+            var actual = _target.Position;
+
+            Assert.AreEqual(0L, actual);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+        }
+
+        [TestMethod]
+        public async Task ReadShouldThrowNotSupportedException()
+        {
+            var buffer = new byte[_readBufferSize];
+
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+            try
+            {
+                await _target.ReadAsync(buffer, 0, buffer.Length);
+                Assert.Fail();
+            }
+            catch (NotSupportedException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+            }
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+        }
+
+        [TestMethod]
+        public async Task WriteShouldStartWritingAtBeginningOfFile()
+        {
+            var buffer = new byte[_writeBufferSize];
+
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.RequestWriteAsync(_handle, 0UL, buffer, 0, buffer.Length, _cancellationToken)).Returns(Task.CompletedTask);
+
+            await _target.WriteAsync(buffer, 0, buffer.Length);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+            SftpSessionMock.Verify(p => p.RequestWriteAsync(_handle, 0UL, buffer, 0, buffer.Length, _cancellationToken), Times.Once);
+        }
+
+        [TestMethod]
+        public void RequestOpenOnSftpSessionShouldBeInvokedOnce()
+        {
+            SftpSessionMock.Verify(p => p.RequestOpenAsync(_path, Flags.Write, _cancellationToken), Times.Once);
+        }
+    }
+}
+#endif

+ 59 - 0
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeTruncate_FileAccessRead.cs

@@ -0,0 +1,59 @@
+#if FEATURE_TAP
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Renci.SshNet.Sftp;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+    [TestClass]
+    public class SftpFileStreamTest_OpenAsync_FileModeTruncate_FileAccessRead : SftpFileStreamAsyncTestBase
+    {
+        private Random _random;
+        private string _path;
+        private FileMode _fileMode;
+        private FileAccess _fileAccess;
+        private int _bufferSize;
+        private ArgumentException _actualException;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _random = new Random();
+            _path = _random.Next().ToString();
+            _fileMode = FileMode.Truncate;
+            _fileAccess = FileAccess.Read;
+            _bufferSize = _random.Next(5, 1000);
+        }
+
+        protected override void SetupMocks()
+        {
+        }
+
+        protected override async Task ActAsync()
+        {
+            try
+            {
+                await SftpFileStream.OpenAsync(SftpSessionMock.Object, _path, _fileMode, _fileAccess, _bufferSize, default);
+                Assert.Fail();
+            }
+            catch (ArgumentException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void CtorShouldHaveThrownArgumentException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.IsNull(_actualException.InnerException);
+            Assert.AreEqual(string.Format("Combining {0}: {1} with {2}: {3} is invalid.", typeof(FileMode).Name, _fileMode, typeof(FileAccess).Name, _fileAccess), _actualException.Message);
+            Assert.IsNull(_actualException.ParamName);
+        }
+    }
+}
+#endif

+ 136 - 0
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeTruncate_FileAccessReadWrite.cs

@@ -0,0 +1,136 @@
+#if FEATURE_TAP
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Sftp;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+    [TestClass]
+    public class SftpFileStreamTest_OpenAsync_FileModeTruncate_FileAccessReadWrite : SftpFileStreamAsyncTestBase
+    {
+        private Random _random;
+        private string _path;
+        private FileMode _fileMode;
+        private FileAccess _fileAccess;
+        private int _bufferSize;
+        private uint _readBufferSize;
+        private uint _writeBufferSize;
+        private byte[] _handle;
+        private SftpFileStream _target;
+        private CancellationToken _cancellationToken;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _random = new Random();
+            _path = _random.Next().ToString();
+            _fileMode = FileMode.Truncate;
+            _fileAccess = FileAccess.ReadWrite;
+            _bufferSize = _random.Next(5, 1000);
+            _readBufferSize = (uint) _random.Next(5, 1000);
+            _writeBufferSize = (uint) _random.Next(5, 1000);
+            _handle = GenerateRandom(_random.Next(1, 10), _random);
+            _cancellationToken = new CancellationToken();
+        }
+
+        protected override void SetupMocks()
+        {
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.RequestOpenAsync(_path, Flags.Read | Flags.Write | Flags.Truncate, _cancellationToken))
+                           .ReturnsAsync(_handle);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.CalculateOptimalReadLength((uint) _bufferSize))
+                           .Returns(_readBufferSize);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.CalculateOptimalWriteLength((uint) _bufferSize, _handle))
+                           .Returns(_writeBufferSize);
+        }
+
+        protected override async Task ActAsync()
+        {
+            _target = await SftpFileStream.OpenAsync(SftpSessionMock.Object, _path, _fileMode, _fileAccess, _bufferSize, _cancellationToken);
+        }
+
+        [TestMethod]
+        public void CanReadShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanRead);
+        }
+
+        [TestMethod]
+        public void CanSeekShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanSeek);
+        }
+
+        [TestMethod]
+        public void CanWriteShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanWrite);
+        }
+
+        [TestMethod]
+        public void CanTimeoutShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanTimeout);
+        }
+
+        [TestMethod]
+        public void PositionShouldReturnZero()
+        {
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+            var actual = _target.Position;
+
+            Assert.AreEqual(0L, actual);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+        }
+
+        [TestMethod]
+        public async Task ReadShouldStartReadingAtBeginningOfFile()
+        {
+            var buffer = new byte[8];
+            var data = new byte[] { 5, 4, 3, 2, 1 };
+            var expected = new byte[] { 0, 5, 4, 3, 2, 1, 0, 0 };
+
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.RequestReadAsync(_handle, 0UL, _readBufferSize, _cancellationToken)).ReturnsAsync(data);
+
+            var actual = await _target.ReadAsync(buffer, 1, data.Length);
+
+            Assert.AreEqual(data.Length, actual);
+            Assert.IsTrue(buffer.IsEqualTo(expected));
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+            SftpSessionMock.Verify(p => p.RequestReadAsync(_handle, 0UL, _readBufferSize, _cancellationToken), Times.Once);
+        }
+
+        [TestMethod]
+        public async Task WriteShouldStartWritingAtBeginningOfFile()
+        {
+            var buffer = new byte[_writeBufferSize];
+
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.RequestWriteAsync(_handle, 0UL, buffer, 0, buffer.Length, _cancellationToken)).Returns(Task.CompletedTask);
+
+            await _target.WriteAsync(buffer, 0, buffer.Length);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+            SftpSessionMock.Verify(p => p.RequestWriteAsync(_handle, 0UL, buffer, 0, buffer.Length, _cancellationToken), Times.Once);
+        }
+
+        [TestMethod]
+        public void RequestOpenOnSftpSessionShouldBeInvokedOnce()
+        {
+            SftpSessionMock.Verify(p => p.RequestOpenAsync(_path, Flags.Read | Flags.Write | Flags.Truncate, _cancellationToken), Times.Once);
+        }
+    }
+}
+#endif

+ 136 - 0
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeTruncate_FileAccessWrite.cs

@@ -0,0 +1,136 @@
+#if FEATURE_TAP
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Sftp;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+    [TestClass]
+    public class SftpFileStreamTest_OpenAsync_FileModeTruncate_FileAccessWrite : SftpFileStreamAsyncTestBase
+    {
+        private Random _random;
+        private string _path;
+        private FileMode _fileMode;
+        private FileAccess _fileAccess;
+        private int _bufferSize;
+        private uint _readBufferSize;
+        private uint _writeBufferSize;
+        private byte[] _handle;
+        private SftpFileStream _target;
+        private CancellationToken _cancellationToken;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _random = new Random();
+            _path = _random.Next().ToString();
+            _fileMode = FileMode.Truncate;
+            _fileAccess = FileAccess.Write;
+            _bufferSize = _random.Next(5, 1000);
+            _readBufferSize = (uint) _random.Next(5, 1000);
+            _writeBufferSize = (uint) _random.Next(5, 1000);
+            _handle = GenerateRandom(_random.Next(1, 10), _random);
+            _cancellationToken = new CancellationToken();
+        }
+
+        protected override void SetupMocks()
+        {
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.RequestOpenAsync(_path, Flags.Write | Flags.Truncate, _cancellationToken))
+                           .ReturnsAsync(_handle);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.CalculateOptimalReadLength((uint) _bufferSize))
+                           .Returns(_readBufferSize);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.CalculateOptimalWriteLength((uint) _bufferSize, _handle))
+                           .Returns(_writeBufferSize);
+        }
+
+        protected override async Task ActAsync()
+        {
+            _target = await SftpFileStream.OpenAsync(SftpSessionMock.Object, _path, _fileMode, _fileAccess, _bufferSize, _cancellationToken);
+        }
+
+        [TestMethod]
+        public void CanReadShouldReturnFalse()
+        {
+            Assert.IsFalse(_target.CanRead);
+        }
+
+        [TestMethod]
+        public void CanSeekShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanSeek);
+        }
+
+        [TestMethod]
+        public void CanWriteShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanWrite);
+        }
+
+        [TestMethod]
+        public void CanTimeoutShouldReturnTrue()
+        {
+            Assert.IsTrue(_target.CanTimeout);
+        }
+
+        [TestMethod]
+        public void PositionShouldReturnZero()
+        {
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+            var actual = _target.Position;
+
+            Assert.AreEqual(0L, actual);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+        }
+
+        [TestMethod]
+        public async Task ReadShouldThrowNotSupportedException()
+        {
+            var buffer = new byte[_readBufferSize];
+
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+            try
+            {
+                await _target.ReadAsync(buffer, 0, buffer.Length);
+                Assert.Fail();
+            }
+            catch (NotSupportedException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+            }
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+        }
+
+        [TestMethod]
+        public async Task WriteShouldStartWritingAtBeginningOfFile()
+        {
+            var buffer = new byte[_writeBufferSize];
+
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.RequestWriteAsync(_handle, 0UL, buffer, 0, buffer.Length, _cancellationToken)).Returns(Task.CompletedTask);
+
+            await _target.WriteAsync(buffer, 0, buffer.Length);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(1));
+            SftpSessionMock.Verify(p => p.RequestWriteAsync(_handle, 0UL, buffer, 0, buffer.Length, _cancellationToken), Times.Once);
+        }
+
+        [TestMethod]
+        public void RequestOpenOnSftpSessionShouldBeInvokedOnce()
+        {
+            SftpSessionMock.Verify(p => p.RequestOpenAsync(_path, Flags.Write | Flags.Truncate, _cancellationToken), Times.Once);
+        }
+    }
+}
+#endif

+ 159 - 0
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_ReadAsync_ReadMode_NoDataInReaderBufferAndReadLessBytesFromServerThanCountAndEqualToBufferSize.cs

@@ -0,0 +1,159 @@
+#if FEATURE_TAP
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Sftp;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+    [TestClass]
+    public class SftpFileStreamTest_ReadAsync_ReadMode_NoDataInReaderBufferAndReadLessBytesFromServerThanCountAndEqualToBufferSize : SftpFileStreamAsyncTestBase
+    {
+        private string _path;
+        private SftpFileStream _target;
+        private byte[] _handle;
+        private uint _bufferSize;
+        private uint _readBufferSize;
+        private uint _writeBufferSize;
+        private int _actual;
+        private byte[] _buffer;
+        private byte[] _serverData1;
+        private byte[] _serverData2;
+        private int _serverData1Length;
+        private int _serverData2Length;
+        private int _numberOfBytesToRead;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            var random = new Random();
+            _path = random.Next().ToString();
+            _handle = GenerateRandom(5, random);
+            _bufferSize = (uint)random.Next(1, 1000);
+            _readBufferSize = 20;
+            _writeBufferSize = 500;
+
+            _numberOfBytesToRead = (int) _readBufferSize + 5; // greather than read buffer size
+            _buffer = new byte[_numberOfBytesToRead];
+            _serverData1Length = (int) _readBufferSize; // equal to read buffer size
+            _serverData1 = GenerateRandom(_serverData1Length, random);
+            _serverData2Length = (int) _readBufferSize; // equal to read buffer size
+            _serverData2 = GenerateRandom(_serverData2Length, random);
+
+            Assert.IsTrue(_serverData1Length < _numberOfBytesToRead && _serverData1Length == _readBufferSize);
+        }
+
+        protected override void SetupMocks()
+        {
+            SftpSessionMock.InSequence(MockSequence)
+                .Setup(p => p.RequestOpenAsync(_path, Flags.Read, default))
+                .ReturnsAsync(_handle);
+            SftpSessionMock.InSequence(MockSequence)
+                .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
+                .Returns(_readBufferSize);
+            SftpSessionMock.InSequence(MockSequence)
+                .Setup(p => p.CalculateOptimalWriteLength(_bufferSize, _handle))
+                .Returns(_writeBufferSize);
+            SftpSessionMock.InSequence(MockSequence)
+                .Setup(p => p.IsOpen)
+                .Returns(true);
+            SftpSessionMock.InSequence(MockSequence)
+                .Setup(p => p.RequestReadAsync(_handle, 0UL, _readBufferSize, default))
+                .ReturnsAsync(_serverData1);
+            SftpSessionMock.InSequence(MockSequence)
+                .Setup(p => p.RequestReadAsync(_handle, (ulong)_serverData1.Length, _readBufferSize, default))
+                .ReturnsAsync(_serverData2);
+        }
+
+        [TestCleanup]
+        public void TearDown()
+        {
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.RequestClose(_handle));
+        }
+
+        protected override async Task ArrangeAsync()
+        {
+            await base.ArrangeAsync();
+
+            _target = await SftpFileStream.OpenAsync(SftpSessionMock.Object,
+                                         _path,
+                                         FileMode.Open,
+                                         FileAccess.Read,
+                                         (int)_bufferSize,
+                                         default);
+        }
+
+        protected override async Task ActAsync()
+        {
+            _actual = await _target.ReadAsync(_buffer, 0, _numberOfBytesToRead, default);
+        }
+
+        [TestMethod]
+        public void ReadShouldHaveReturnedTheNumberOfBytesRequested()
+        {
+            Assert.AreEqual(_numberOfBytesToRead, _actual);
+        }
+
+        [TestMethod]
+        public void ReadShouldHaveWrittenBytesToTheCallerSuppliedBuffer()
+        {
+            Assert.IsTrue(_serverData1.IsEqualTo(_buffer.Take(_serverData1Length)));
+
+            var bytesWrittenFromSecondRead = _numberOfBytesToRead - _serverData1Length;
+            Assert.IsTrue(_serverData2.Take(bytesWrittenFromSecondRead).IsEqualTo(_buffer.Take(_serverData1Length, bytesWrittenFromSecondRead)));
+        }
+
+        [TestMethod]
+        public void PositionShouldReturnNumberOfBytesWrittenToCallerProvidedBuffer()
+        {
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+            Assert.AreEqual(_actual, _target.Position);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(2));
+        }
+
+        [TestMethod]
+        public async Task ReadShouldReturnAllRemaningBytesFromReadBufferWhenCountIsEqualToNumberOfRemainingBytes()
+        {
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+            var numberOfBytesRemainingInReadBuffer = _serverData1Length + _serverData2Length - _numberOfBytesToRead;
+
+            _buffer = new byte[numberOfBytesRemainingInReadBuffer];
+
+            var actual = await _target.ReadAsync(_buffer, 0, _buffer.Length);
+
+            Assert.AreEqual(_buffer.Length, actual);
+            Assert.IsTrue(_serverData2.Take(_numberOfBytesToRead - _serverData1Length, _buffer.Length).IsEqualTo(_buffer));
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(2));
+        }
+
+        [TestMethod]
+        public async Task ReadShouldReturnAllRemaningBytesFromReadBufferAndReadAgainWhenCountIsGreaterThanNumberOfRemainingBytesAndNewReadReturnsZeroBytes()
+        {
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.RequestReadAsync(_handle, (ulong)(_serverData1Length + _serverData2Length), _readBufferSize, default)).ReturnsAsync(Array<byte>.Empty);
+
+            var numberOfBytesRemainingInReadBuffer = _serverData1Length + _serverData2Length - _numberOfBytesToRead;
+
+            _buffer = new byte[numberOfBytesRemainingInReadBuffer + 1];
+
+            var actual = await _target.ReadAsync(_buffer, 0, _buffer.Length);
+
+            Assert.AreEqual(numberOfBytesRemainingInReadBuffer, actual);
+            Assert.IsTrue(_serverData2.Take(_numberOfBytesToRead - _serverData1Length, numberOfBytesRemainingInReadBuffer).IsEqualTo(_buffer.Take(numberOfBytesRemainingInReadBuffer)));
+            Assert.AreEqual(0, _buffer[numberOfBytesRemainingInReadBuffer]);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(2));
+            SftpSessionMock.Verify(p => p.RequestReadAsync(_handle, (ulong)(_serverData1Length + _serverData2Length), _readBufferSize, default));
+        }
+    }
+}
+#endif

+ 152 - 0
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_ReadAsync_ReadMode_NoDataInReaderBufferAndReadLessBytesFromServerThanCountAndLessThanBufferSize.cs

@@ -0,0 +1,152 @@
+#if FEATURE_TAP
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Sftp;
+using Renci.SshNet.Tests.Common;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+    [TestClass]
+    public class SftpFileStreamTest_ReadAsync_ReadMode_NoDataInReaderBufferAndReadLessBytesFromServerThanCountAndLessThanBufferSize : SftpFileStreamAsyncTestBase
+    {
+        private string _path;
+        private SftpFileStream _target;
+        private byte[] _handle;
+        private uint _bufferSize;
+        private uint _readBufferSize;
+        private uint _writeBufferSize;
+        private int _actual;
+        private byte[] _buffer;
+        private byte[] _serverData;
+        private int _serverDataLength;
+        private int _numberOfBytesToRead;
+        private byte[] _originalBuffer;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            var random = new Random();
+            _path = random.Next().ToString();
+            _handle = GenerateRandom(5, random);
+            _bufferSize = (uint)random.Next(1, 1000);
+            _readBufferSize = 20;
+            _writeBufferSize = 500;
+
+            _numberOfBytesToRead = (int) _readBufferSize + 2; // greater than read buffer size
+            _originalBuffer = GenerateRandom(_numberOfBytesToRead, random);
+            _buffer = _originalBuffer.Copy();
+
+            _serverDataLength = (int) _readBufferSize - 1; // less than read buffer size
+            _serverData = GenerateRandom(_serverDataLength, random);
+
+            Assert.IsTrue(_serverDataLength < _numberOfBytesToRead && _serverDataLength < _readBufferSize);
+        }
+
+        protected override void SetupMocks()
+        {
+            SftpSessionMock.InSequence(MockSequence)
+                .Setup(p => p.RequestOpenAsync(_path, Flags.Read, default))
+                .ReturnsAsync(_handle);
+            SftpSessionMock.InSequence(MockSequence)
+                .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
+                .Returns(_readBufferSize);
+            SftpSessionMock.InSequence(MockSequence)
+                .Setup(p => p.CalculateOptimalWriteLength(_bufferSize, _handle))
+                .Returns(_writeBufferSize);
+            SftpSessionMock.InSequence(MockSequence)
+                .Setup(p => p.IsOpen)
+                .Returns(true);
+            SftpSessionMock.InSequence(MockSequence)
+                .Setup(p => p.RequestReadAsync(_handle, 0UL, _readBufferSize, default))
+                .ReturnsAsync(_serverData);
+        }
+
+        [TestCleanup]
+        public void TearDown()
+        {
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.RequestClose(_handle));
+        }
+
+        protected override async Task ArrangeAsync()
+        {
+            await base.ArrangeAsync();
+
+            _target = await SftpFileStream.OpenAsync(SftpSessionMock.Object,
+                                         _path,
+                                         FileMode.Open,
+                                         FileAccess.Read,
+                                         (int)_bufferSize,
+                                         default);
+        }
+
+        protected override async Task ActAsync()
+        {
+            _actual = await _target.ReadAsync(_buffer, 0, _numberOfBytesToRead, default);
+        }
+
+        [TestMethod]
+        public void ReadShouldHaveReturnedTheNumberOfBytesReturnedByTheReadFromTheServer()
+        {
+            Assert.AreEqual(_serverDataLength, _actual);
+        }
+
+        [TestMethod]
+        public void ReadShouldHaveWrittenBytesToTheCallerSuppliedBufferAndRemainingBytesShouldRemainUntouched()
+        {
+            Assert.IsTrue(_serverData.IsEqualTo(_buffer.Take(_serverDataLength)));
+            Assert.IsTrue(_originalBuffer.Take(_serverDataLength, _originalBuffer.Length - _serverDataLength).IsEqualTo(_buffer.Take(_serverDataLength, _buffer.Length - _serverDataLength)));
+        }
+
+        [TestMethod]
+        public void PositionShouldReturnNumberOfBytesWrittenToCallerProvidedBuffer()
+        {
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+            Assert.AreEqual(_actual, _target.Position);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(2));
+        }
+
+        [TestMethod]
+        public async Task SubsequentReadShouldReadAgainFromCurrentPositionFromServerAndReturnZeroWhenServerReturnsZeroBytes()
+        {
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(MockSequence)
+                .Setup(p => p.RequestReadAsync(_handle, (ulong) _actual, _readBufferSize, default))
+                .ReturnsAsync(Array<byte>.Empty);
+
+            var buffer = _originalBuffer.Copy();
+            var actual = await _target.ReadAsync(buffer, 0, buffer.Length);
+
+            Assert.AreEqual(0, actual);
+            Assert.IsTrue(_originalBuffer.IsEqualTo(buffer));
+
+            SftpSessionMock.Verify(p => p.RequestReadAsync(_handle, (ulong)_actual, _readBufferSize, default), Times.Once);
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(2));
+        }
+
+        [TestMethod]
+        public async Task SubsequentReadShouldReadAgainFromCurrentPositionFromServerAndNotUpdatePositionWhenServerReturnsZeroBytes()
+        {
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(MockSequence)
+                .Setup(p => p.RequestReadAsync(_handle, (ulong)_actual, _readBufferSize, default))
+                .ReturnsAsync(Array<byte>.Empty);
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+            await _target.ReadAsync(new byte[10], 0, 10);
+
+            Assert.AreEqual(_actual, _target.Position);
+
+            SftpSessionMock.Verify(p => p.RequestReadAsync(_handle, (ulong)_actual, _readBufferSize, default), Times.Once);
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(3));
+        }
+    }
+}
+#endif

+ 143 - 0
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_ReadAsync_ReadMode_NoDataInReaderBufferAndReadMoreBytesFromServerThanCount.cs

@@ -0,0 +1,143 @@
+#if FEATURE_TAP
+using System;
+using System.IO;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Sftp;
+using Renci.SshNet.Common;
+using System.Threading.Tasks;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+    [TestClass]
+    public class SftpFileStreamTest_ReadAsync_ReadMode_NoDataInReaderBufferAndReadMoreBytesFromServerThanCount : SftpFileStreamAsyncTestBase
+    {
+        private string _path;
+        private SftpFileStream _target;
+        private byte[] _handle;
+        private uint _bufferSize;
+        private uint _readBufferSize;
+        private uint _writeBufferSize;
+        private int _actual;
+        private byte[] _buffer;
+        private byte[] _serverData;
+        private int _numberOfBytesToWriteToReadBuffer;
+        private int _numberOfBytesToRead;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            var random = new Random();
+            _path = random.Next().ToString();
+            _handle = GenerateRandom(5, random);
+            _bufferSize = (uint) random.Next(1, 1000);
+            _readBufferSize = 20;
+            _writeBufferSize = 500;
+
+            _numberOfBytesToRead = 20;
+            _buffer = new byte[_numberOfBytesToRead];
+            _numberOfBytesToWriteToReadBuffer = 10; // should be less than _readBufferSize
+            _serverData = GenerateRandom(_numberOfBytesToRead + _numberOfBytesToWriteToReadBuffer, random);
+        }
+
+        protected override void SetupMocks()
+        {
+            SftpSessionMock.InSequence(MockSequence)
+                .Setup(p => p.RequestOpenAsync(_path, Flags.Read, default))
+                .ReturnsAsync(_handle);
+            SftpSessionMock.InSequence(MockSequence)
+                .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
+                .Returns(_readBufferSize);
+            SftpSessionMock.InSequence(MockSequence)
+                .Setup(p => p.CalculateOptimalWriteLength(_bufferSize, _handle))
+                .Returns(_writeBufferSize);
+            SftpSessionMock.InSequence(MockSequence)
+                .Setup(p => p.IsOpen)
+                .Returns(true);
+            SftpSessionMock.InSequence(MockSequence)
+                .Setup(p => p.RequestReadAsync(_handle, 0UL, _readBufferSize, default))
+                .ReturnsAsync(_serverData);
+        }
+
+        [TestCleanup]
+        public void TearDown()
+        {
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.RequestClose(_handle));
+        }
+
+        protected override async Task ArrangeAsync()
+        {
+            await base.ArrangeAsync();
+
+            _target = await SftpFileStream.OpenAsync(SftpSessionMock.Object,
+                                         _path,
+                                         FileMode.Open,
+                                         FileAccess.Read,
+                                         (int)_bufferSize,
+                                         default);
+        }
+
+        protected override async Task ActAsync()
+        {
+            _actual = await _target.ReadAsync(_buffer, 0, _numberOfBytesToRead, default);
+        }
+
+        [TestMethod]
+        public void ReadShouldHaveReturnedTheNumberOfBytesWrittenToCallerSuppliedBuffer()
+        {
+            Assert.AreEqual(_numberOfBytesToRead, _actual);
+        }
+
+        [TestMethod]
+        public void ReadShouldHaveWrittenBytesToTheCallerSuppliedBuffer()
+        {
+            Assert.IsTrue(_serverData.Take(_actual).IsEqualTo(_buffer));
+        }
+
+        [TestMethod]
+        public void PositionShouldReturnNumberOfBytesWrittenToCallerProvidedBuffer()
+        {
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+            Assert.AreEqual(_actual, _target.Position);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(2));
+        }
+
+        [TestMethod]
+        public async Task SubsequentReadShouldReturnAllRemaningBytesFromReadBufferWhenCountIsEqualToNumberOfRemainingBytes()
+        {
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+            var buffer = new byte[_numberOfBytesToWriteToReadBuffer];
+
+            var actual = await _target.ReadAsync(buffer, 0, _numberOfBytesToWriteToReadBuffer, default);
+
+            Assert.AreEqual(_numberOfBytesToWriteToReadBuffer, actual);
+            Assert.IsTrue(_serverData.Take(_numberOfBytesToRead, _numberOfBytesToWriteToReadBuffer).IsEqualTo(buffer));
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(2));
+        }
+
+        [TestMethod]
+        public async Task SubsequentReadShouldReturnAllRemaningBytesFromReadBufferAndReadAgainWhenCountIsGreaterThanNumberOfRemainingBytesAndNewReadReturnsZeroBytes()
+        {
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.RequestReadAsync(_handle, (ulong)(_serverData.Length), _readBufferSize, default)).ReturnsAsync(Array<byte>.Empty);
+
+            var buffer = new byte[_numberOfBytesToWriteToReadBuffer + 1];
+
+            var actual = await _target.ReadAsync(buffer, 0, buffer.Length);
+
+            Assert.AreEqual(_numberOfBytesToWriteToReadBuffer, actual);
+            Assert.IsTrue(_serverData.Take(_numberOfBytesToRead, _numberOfBytesToWriteToReadBuffer).IsEqualTo(buffer.Take(_numberOfBytesToWriteToReadBuffer)));
+            Assert.AreEqual(0, buffer[_numberOfBytesToWriteToReadBuffer]);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(2));
+            SftpSessionMock.Verify(p => p.RequestReadAsync(_handle, (ulong)(_serverData.Length), _readBufferSize, default));
+        }
+    }
+}
+#endif

+ 143 - 0
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_WriteAsync_SessionOpen_CountGreatherThanTwoTimesTheWriteBufferSize.cs

@@ -0,0 +1,143 @@
+#if FEATURE_TAP
+using System;
+using System.Globalization;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Sftp;
+using Renci.SshNet.Sftp.Responses;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+    [TestClass]
+    public class SftpFileStreamTest_WriteAsync_SessionOpen_CountGreatherThanTwoTimesTheWriteBufferSize : SftpFileStreamAsyncTestBase
+    {
+        private SftpFileStream _target;
+        private string _path;
+        private byte[] _handle;
+        private uint _bufferSize;
+        private uint _readBufferSize;
+        private uint _writeBufferSize;
+        private byte[] _data;
+        private int _count;
+        private int _offset;
+        private Random _random;
+        private uint _expectedWrittenByteCount;
+        private int _expectedBufferedByteCount;
+        private byte[] _expectedBufferedBytes;
+        private CancellationToken _cancellationToken;
+
+        protected override void SetupData()
+        {
+            base.SetupData();
+
+            _random = new Random();
+            _path = _random.Next().ToString(CultureInfo.InvariantCulture);
+            _handle = GenerateRandom(5, _random);
+            _bufferSize = (uint)_random.Next(1, 1000);
+            _readBufferSize = (uint) _random.Next(0, 1000);
+            _writeBufferSize = (uint) _random.Next(500, 1000);
+            _data = new byte[(_writeBufferSize * 2) + 15];
+            _random.NextBytes(_data);
+            _offset = _random.Next(1, 5);
+            // to get multiple SSH_FXP_WRITE messages (and verify the offset is updated correctly), we make sure
+            // the number of bytes to write is at least two times the write buffer size; we write a few extra bytes to
+            // ensure the buffer is not empty after the writes so we can verify whether Length, Dispose and Flush
+            // flush the buffer
+            _count = ((int) _writeBufferSize * 2) + _random.Next(1, 5);
+
+            _expectedWrittenByteCount = (2 * _writeBufferSize);
+            _expectedBufferedByteCount = (int)(_count - _expectedWrittenByteCount);
+            _expectedBufferedBytes = _data.Take(_offset + (int)_expectedWrittenByteCount, _expectedBufferedByteCount);
+            _cancellationToken = new CancellationToken();
+        }
+
+        protected override void SetupMocks()
+        {
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.RequestOpenAsync(_path, Flags.Write | Flags.CreateNewOrOpen | Flags.Truncate, _cancellationToken))
+                           .ReturnsAsync(_handle);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
+                           .Returns(_readBufferSize);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.CalculateOptimalWriteLength(_bufferSize, _handle))
+                           .Returns(_writeBufferSize);
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.RequestWriteAsync(_handle, 0, _data, _offset, (int)_writeBufferSize, _cancellationToken))
+                           .Returns(Task.CompletedTask);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.RequestWriteAsync(_handle, _writeBufferSize, _data, _offset + (int)_writeBufferSize, (int)_writeBufferSize, _cancellationToken))
+                           .Returns(Task.CompletedTask);
+        }
+
+        [TestCleanup]
+        public void TearDown()
+        {
+            if (SftpSessionMock != null)
+            {
+                // allow Dispose to complete successfully
+                SftpSessionMock.InSequence(MockSequence)
+                               .Setup(p => p.IsOpen)
+                               .Returns(true);
+                SftpSessionMock.InSequence(MockSequence)
+                               .Setup(p => p.RequestWriteAsync(_handle, _expectedWrittenByteCount, It.IsAny<byte[]>(), 0, _expectedBufferedByteCount, _cancellationToken))
+                               .Returns(Task.CompletedTask);
+                SftpSessionMock.InSequence(MockSequence)
+                               .Setup(p => p.RequestClose(_handle));
+            }
+        }
+
+        protected override async Task ArrangeAsync()
+        {
+            await base.ArrangeAsync();
+
+            _target = await SftpFileStream.OpenAsync(SftpSessionMock.Object, _path, FileMode.Create, FileAccess.Write, (int) _bufferSize, _cancellationToken);
+        }
+
+        protected override Task ActAsync()
+        {
+            return _target.WriteAsync(_data, _offset, _count);
+        }
+
+        [TestMethod]
+        public void RequestWriteOnSftpSessionShouldBeInvokedTwice()
+        {
+            SftpSessionMock.Verify(p => p.RequestWriteAsync(_handle, 0, _data, _offset, (int)_writeBufferSize, _cancellationToken), Times.Once);
+            SftpSessionMock.Verify(p => p.RequestWriteAsync(_handle, _writeBufferSize, _data, _offset + (int)_writeBufferSize, (int)_writeBufferSize, _cancellationToken), Times.Once);
+        }
+
+        [TestMethod]
+        public void PositionShouldBeNumberOfBytesWrittenToFileAndNUmberOfBytesInBuffer()
+        {
+            SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+            Assert.AreEqual(_count, _target.Position);
+        }
+
+        [TestMethod]
+        public async Task FlushShouldFlushBuffer()
+        {
+            byte[] actualFlushedData = null;
+
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.IsOpen)
+                           .Returns(true);
+            SftpSessionMock.InSequence(MockSequence)
+                           .Setup(p => p.RequestWriteAsync(_handle, _expectedWrittenByteCount, It.IsAny<byte[]>(), 0, _expectedBufferedByteCount, _cancellationToken))
+                           .Callback<byte[], ulong, byte[], int, int, CancellationToken>((handle, serverFileOffset, data, offset, length, ct) => actualFlushedData = data.Take(offset, length))
+                           .Returns(Task.CompletedTask);
+
+            await _target.FlushAsync();
+
+            Assert.IsTrue(actualFlushedData.IsEqualTo(_expectedBufferedBytes));
+
+            SftpSessionMock.Verify(p => p.RequestWriteAsync(_handle, _expectedWrittenByteCount, It.IsAny<byte[]>(), 0, _expectedBufferedByteCount, _cancellationToken), Times.Once);
+        }
+    }
+}
+#endif

+ 48 - 0
src/Renci.SshNet.Tests/Classes/SftpClientTest.ConnectAsync.cs

@@ -0,0 +1,48 @@
+#if FEATURE_TAP
+using System;
+using System.Net.Sockets;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    public partial class SftpClientTest
+    {
+        [TestMethod]
+        public async Task ConnectAsync_HostNameInvalid_ShouldThrowSocketExceptionWithErrorCodeHostNotFound()
+        {
+            var connectionInfo = new ConnectionInfo(Guid.NewGuid().ToString("N"), 40, "user",
+                new KeyboardInteractiveAuthenticationMethod("user"));
+            var sftpClient = new SftpClient(connectionInfo);
+
+            try
+            {
+                await sftpClient.ConnectAsync(default);
+                Assert.Fail();
+            }
+            catch (SocketException ex)
+            {
+                Assert.AreEqual(SocketError.HostNotFound, ex.SocketErrorCode);
+            }
+        }
+
+        [TestMethod]
+        public async Task ConnectAsync_ProxyHostNameInvalid_ShouldThrowSocketExceptionWithErrorCodeHostNotFound()
+        {
+            var connectionInfo = new ConnectionInfo("localhost", 40, "user", ProxyTypes.Http, Guid.NewGuid().ToString("N"), 80,
+                "proxyUser", "proxyPwd", new KeyboardInteractiveAuthenticationMethod("user"));
+            var sftpClient = new SftpClient(connectionInfo);
+
+            try
+            {
+                await sftpClient.ConnectAsync(default);
+                Assert.Fail();
+            }
+            catch (SocketException ex)
+            {
+                Assert.AreEqual(SocketError.HostNotFound, ex.SocketErrorCode);
+            }
+        }
+    }
+}
+#endif

+ 27 - 0
src/Renci.SshNet.Tests/Classes/SftpClientTest.DeleteFileAsync.cs

@@ -0,0 +1,27 @@
+#if FEATURE_TAP
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Renci.SshNet.Tests.Properties;
+using System;
+using System.Threading.Tasks;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    /// <summary>
+    /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
+    /// </summary>
+    public partial class SftpClientTest
+    {
+        [TestMethod]
+        [TestCategory("Sftp")]
+        [Description("Test passing null to DeleteFile.")]
+        [ExpectedException(typeof(ArgumentException))]
+        public async Task Test_Sftp_DeleteFileAsync_Null()
+        {
+            using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            {
+                await sftp.DeleteFileAsync(null, default);
+            }
+        }
+    }
+}
+#endif

+ 66 - 0
src/Renci.SshNet.Tests/Classes/SftpClientTest.RenameFileAsync.cs

@@ -0,0 +1,66 @@
+#if FEATURE_TAP
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Renci.SshNet.Tests.Properties;
+using System;
+using System.IO;
+using System.Threading.Tasks;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    /// <summary>
+    /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
+    /// </summary>
+    public partial class SftpClientTest
+    {
+        [TestMethod]
+        [TestCategory("Sftp")]
+        [TestCategory("integration")]
+        public async Task Test_Sftp_RenameFileAsync()
+        {
+            using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            {
+                await sftp.ConnectAsync(default);
+
+                string uploadedFileName = Path.GetTempFileName();
+                string remoteFileName1 = Path.GetRandomFileName();
+                string remoteFileName2 = Path.GetRandomFileName();
+
+                this.CreateTestFile(uploadedFileName, 1);
+
+                using (var file = File.OpenRead(uploadedFileName))
+                {
+                    using (Stream remoteStream = await sftp.OpenAsync(remoteFileName1, FileMode.CreateNew, FileAccess.Write, default))
+                    {
+                        await file.CopyToAsync(remoteStream, 81920, default);
+                    }
+                }
+
+                await sftp.RenameFileAsync(remoteFileName1, remoteFileName2, default);
+
+                File.Delete(uploadedFileName);
+
+                sftp.Disconnect();
+            }
+
+            RemoveAllFiles();
+        }
+
+        [TestMethod]
+        [TestCategory("Sftp")]
+        [TestCategory("integration")]
+        [Description("Test passing null to RenameFile.")]
+        [ExpectedException(typeof(ArgumentNullException))]
+        public async Task Test_Sftp_RenameFileAsync_Null()
+        {
+            using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            {
+                await sftp.ConnectAsync(default);
+
+                await sftp.RenameFileAsync(null, null, default);
+
+                sftp.Disconnect();
+            }
+        }
+    }
+}
+#endif

+ 8 - 0
src/Renci.SshNet.Tests/Classes/SftpClientTest.cs

@@ -94,7 +94,11 @@ namespace Renci.SshNet.Tests.Classes
             catch (ArgumentOutOfRangeException ex)
             {
                 Assert.IsNull(ex.InnerException);
+#if NETFRAMEWORK
                 Assert.AreEqual("The timeout must represent a value between -1 and Int32.MaxValue, inclusive." + Environment.NewLine + "Parameter name: " + ex.ParamName, ex.Message);
+#else
+                Assert.AreEqual("The timeout must represent a value between -1 and Int32.MaxValue, inclusive. (Parameter '" + ex.ParamName + "')", ex.Message);
+#endif
                 Assert.AreEqual("value", ex.ParamName);
             }
         }
@@ -113,7 +117,11 @@ namespace Renci.SshNet.Tests.Classes
             catch (ArgumentOutOfRangeException ex)
             {
                 Assert.IsNull(ex.InnerException);
+#if NETFRAMEWORK
                 Assert.AreEqual("The timeout must represent a value between -1 and Int32.MaxValue, inclusive." + Environment.NewLine + "Parameter name: " + ex.ParamName, ex.Message);
+#else
+                Assert.AreEqual("The timeout must represent a value between -1 and Int32.MaxValue, inclusive. (Parameter '" + ex.ParamName + "')", ex.Message);
+#endif
                 Assert.AreEqual("value", ex.ParamName);
             }
         }

+ 25 - 36
src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj

@@ -1,14 +1,18 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <SignAssembly>true</SignAssembly>
+	  <LangVersion>7.3</LangVersion>
+	  <SignAssembly>true</SignAssembly>
     <AssemblyOriginatorKeyFile>..\Renci.SshNet.snk</AssemblyOriginatorKeyFile>
   </PropertyGroup>
 
   <PropertyGroup Condition=" '$(VisualStudioVersion)' == '15.0' ">
-    <TargetFrameworks>net35;net40;netcoreapp2.1;netcoreapp2.2</TargetFrameworks>
+    <TargetFrameworks>net35;net472;netcoreapp2.1</TargetFrameworks>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(VisualStudioVersion)' == '16.0' ">
-    <TargetFrameworks>net35;net40;netcoreapp2.1;netcoreapp2.2;netcoreapp3.0</TargetFrameworks>
+    <TargetFrameworks>net35;net472;netcoreapp3.1;net5.0</TargetFrameworks>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(VisualStudioVersion)' == '17.0' ">
+    <TargetFrameworks>net472;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
   </PropertyGroup>
 
   <PropertyGroup Condition=" '$(TargetFramework)' == 'net35' ">
@@ -17,14 +21,23 @@
   <PropertyGroup Condition=" '$(TargetFramework)' == 'net40' ">
     <DefineConstants>FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_TPL</DefineConstants>
   </PropertyGroup>
+  <PropertyGroup Condition=" '$(TargetFramework)' == 'net472' ">
+    <DefineConstants>FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_TPL;FEATURE_TAP</DefineConstants>
+  </PropertyGroup>
   <PropertyGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.1' ">
-    <DefineConstants>FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_TPL</DefineConstants>
+    <DefineConstants>FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_TPL;FEATURE_TAP</DefineConstants>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.2' ">
-    <DefineConstants>FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_TPL</DefineConstants>
+    <DefineConstants>FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_TPL;FEATURE_TAP</DefineConstants>
   </PropertyGroup>
-  <PropertyGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.0' ">
-    <DefineConstants>FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_TPL</DefineConstants>
+  <PropertyGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1' ">
+    <DefineConstants>FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_TPL;FEATURE_TAP</DefineConstants>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(TargetFramework)' == 'net5.0' ">
+    <DefineConstants>FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_TPL;FEATURE_TAP</DefineConstants>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(TargetFramework)' == 'net6.0' ">
+    <DefineConstants>FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_TPL;FEATURE_TAP</DefineConstants>
   </PropertyGroup>
   <ItemGroup>
     <EmbeddedResource Include="Data\Key.ECDSA.Encrypted.txt" />
@@ -93,35 +106,11 @@
     </Reference>
     <PackageReference Include="Moq" Version="4.2.1409.1722" />
   </ItemGroup>
-  <ItemGroup Condition="'$(TargetFramework)' == 'net40'">
-    <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework">
-      <HintPath>$(MSTestV1UnitTestFrameworkAssembly)</HintPath>
-    </Reference>
-    <PackageReference Include="Moq" Version="4.2.1409.1722" />
-  </ItemGroup>
-  <ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'">
-    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
-    <PackageReference Include="MSTest.TestAdapter" Version="2.1.0" />
-    <PackageReference Include="MSTest.TestFramework">
-      <Version>2.1.0</Version>
-    </PackageReference>
-    <PackageReference Include="Moq" Version="4.13.1" />
-  </ItemGroup>    
-  <ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.2'">
-    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />    
-    <PackageReference Include="MSTest.TestAdapter" Version="2.1.0" />
-    <PackageReference Include="MSTest.TestFramework">
-      <Version>2.1.0</Version>
-    </PackageReference>
-    <PackageReference Include="Moq" Version="4.13.1" />
-  </ItemGroup>
-  <ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.0'">
-    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />    
-    <PackageReference Include="MSTest.TestAdapter" Version="2.1.0" />
-    <PackageReference Include="MSTest.TestFramework">
-      <Version>2.1.0</Version>
-    </PackageReference>
-    <PackageReference Include="Moq" Version="4.13.1" />
+  <ItemGroup Condition="'$(TargetFramework)' != 'net35'">
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
+    <PackageReference Include="MSTest.TestAdapter" Version="2.2.8" />
+    <PackageReference Include="MSTest.TestFramework" Version="2.2.8" />
+    <PackageReference Include="Moq" Version="4.16.1" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\Renci.SshNet\Renci.SshNet.csproj" />

+ 15 - 0
src/Renci.SshNet/IPrivateKeySource.cs

@@ -0,0 +1,15 @@
+using Renci.SshNet.Security;
+
+namespace Renci.SshNet
+{
+    /// <summary>
+    /// Represents private key source interface.
+    /// </summary>
+    public interface IPrivateKeySource
+    {
+        /// <summary>
+        /// Gets the host key.
+        /// </summary>
+        HostAlgorithm HostKey { get; }
+    }
+}

+ 4 - 4
src/Renci.SshNet/NetConfClient.cs

@@ -103,7 +103,7 @@ namespace Renci.SshNet
         /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, -or- <paramref name="username"/> is <c>null</c> or contains only whitespace characters.</exception>
         /// <exception cref="ArgumentOutOfRangeException"><paramref name="port"/> is not within <see cref="IPEndPoint.MinPort"/> and <see cref="IPEndPoint.MaxPort"/>.</exception>
         [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "Disposed in Dispose(bool) method.")]
-        public NetConfClient(string host, int port, string username, params PrivateKeyFile[] keyFiles)
+        public NetConfClient(string host, int port, string username, params IPrivateKeySource[] keyFiles)
             : this(new PrivateKeyConnectionInfo(host, port, username, keyFiles), true)
         {
         }
@@ -116,7 +116,7 @@ namespace Renci.SshNet
         /// <param name="keyFiles">Authentication private key file(s) .</param>
         /// <exception cref="ArgumentNullException"><paramref name="keyFiles"/> is <c>null</c>.</exception>
         /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, -or- <paramref name="username"/> is <c>null</c> or contains only whitespace characters.</exception>
-        public NetConfClient(string host, string username, params PrivateKeyFile[] keyFiles)
+        public NetConfClient(string host, string username, params IPrivateKeySource[] keyFiles)
             : this(host, ConnectionInfo.DefaultPort, username, keyFiles)
         {
         }
@@ -163,7 +163,7 @@ namespace Renci.SshNet
         /// <value>
         /// The NetConf server capabilities.
         /// </value>
-        public XmlDocument ServerCapabilities 
+        public XmlDocument ServerCapabilities
         {
             get { return _netConfSession.ServerCapabilities; }
         }
@@ -277,4 +277,4 @@ namespace Renci.SshNet
             }
         }
     }
-}
+}

+ 4 - 4
src/Renci.SshNet/PrivateKeyAuthenticationMethod.cs

@@ -28,7 +28,7 @@ namespace Renci.SshNet
         /// <summary>
         /// Gets the key files used for authentication.
         /// </summary>
-        public ICollection<PrivateKeyFile> KeyFiles { get; private set; }
+        public ICollection<IPrivateKeySource> KeyFiles { get; private set; }
 
         /// <summary>
         /// Initializes a new instance of the <see cref="PrivateKeyAuthenticationMethod"/> class.
@@ -36,13 +36,13 @@ namespace Renci.SshNet
         /// <param name="username">The username.</param>
         /// <param name="keyFiles">The key files.</param>
         /// <exception cref="ArgumentException"><paramref name="username"/> is whitespace or <c>null</c>.</exception>
-        public PrivateKeyAuthenticationMethod(string username, params PrivateKeyFile[] keyFiles)
+        public PrivateKeyAuthenticationMethod(string username, params IPrivateKeySource[] keyFiles)
             : base(username)
         {
             if (keyFiles == null)
                 throw new ArgumentNullException("keyFiles");
 
-            KeyFiles = new Collection<PrivateKeyFile>(keyFiles);
+            KeyFiles = new Collection<IPrivateKeySource>(keyFiles);
         }
 
         /// <summary>
@@ -250,4 +250,4 @@ namespace Renci.SshNet
             }
         }
     }
-}
+}

+ 10 - 10
src/Renci.SshNet/PrivateKeyConnectionInfo.cs

@@ -15,7 +15,7 @@ namespace Renci.SshNet
         /// <summary>
         /// Gets the key files used for authentication.
         /// </summary>
-        public ICollection<PrivateKeyFile> KeyFiles { get; private set; }
+        public ICollection<IPrivateKeySource> KeyFiles { get; private set; }
 
         /// <summary>
         /// Initializes a new instance of the <see cref="PrivateKeyConnectionInfo"/> class.
@@ -40,7 +40,7 @@ namespace Renci.SshNet
         /// <param name="port">Connection port.</param>
         /// <param name="username">Connection username.</param>
         /// <param name="keyFiles">Connection key files.</param>
-        public PrivateKeyConnectionInfo(string host, int port, string username, params PrivateKeyFile[] keyFiles)
+        public PrivateKeyConnectionInfo(string host, int port, string username, params IPrivateKeySource[] keyFiles)
             : this(host, port, username, ProxyTypes.None, string.Empty, 0, string.Empty, string.Empty, keyFiles)
         {
         }
@@ -55,7 +55,7 @@ namespace Renci.SshNet
         /// <param name="proxyHost">The proxy host.</param>
         /// <param name="proxyPort">The proxy port.</param>
         /// <param name="keyFiles">The key files.</param>
-        public PrivateKeyConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, params PrivateKeyFile[] keyFiles)
+        public PrivateKeyConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, params IPrivateKeySource[] keyFiles)
             : this(host, port, username, proxyType, proxyHost, proxyPort, string.Empty, string.Empty, keyFiles)
         {
         }
@@ -71,7 +71,7 @@ namespace Renci.SshNet
         /// <param name="proxyPort">The proxy port.</param>
         /// <param name="proxyUsername">The proxy username.</param>
         /// <param name="keyFiles">The key files.</param>
-        public PrivateKeyConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, params PrivateKeyFile[] keyFiles)
+        public PrivateKeyConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, params IPrivateKeySource[] keyFiles)
             : this(host, port, username, proxyType, proxyHost, proxyPort, proxyUsername, string.Empty, keyFiles)
         {
         }
@@ -85,7 +85,7 @@ namespace Renci.SshNet
         /// <param name="proxyHost">The proxy host.</param>
         /// <param name="proxyPort">The proxy port.</param>
         /// <param name="keyFiles">The key files.</param>
-        public PrivateKeyConnectionInfo(string host, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, params PrivateKeyFile[] keyFiles)
+        public PrivateKeyConnectionInfo(string host, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, params IPrivateKeySource[] keyFiles)
             : this(host, DefaultPort, username, proxyType, proxyHost, proxyPort, string.Empty, string.Empty, keyFiles)
         {
         }
@@ -100,7 +100,7 @@ namespace Renci.SshNet
         /// <param name="proxyPort">The proxy port.</param>
         /// <param name="proxyUsername">The proxy username.</param>
         /// <param name="keyFiles">The key files.</param>
-        public PrivateKeyConnectionInfo(string host, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, params PrivateKeyFile[] keyFiles)
+        public PrivateKeyConnectionInfo(string host, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, params IPrivateKeySource[] keyFiles)
             : this(host, DefaultPort, username, proxyType, proxyHost, proxyPort, proxyUsername, string.Empty, keyFiles)
         {
         }
@@ -116,7 +116,7 @@ namespace Renci.SshNet
         /// <param name="proxyUsername">The proxy username.</param>
         /// <param name="proxyPassword">The proxy password.</param>
         /// <param name="keyFiles">The key files.</param>
-        public PrivateKeyConnectionInfo(string host, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, string proxyPassword, params PrivateKeyFile[] keyFiles)
+        public PrivateKeyConnectionInfo(string host, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, string proxyPassword, params IPrivateKeySource[] keyFiles)
             : this(host, DefaultPort, username, proxyType, proxyHost, proxyPort, proxyUsername, proxyPassword, keyFiles)
         {
         }
@@ -133,10 +133,10 @@ namespace Renci.SshNet
         /// <param name="proxyUsername">The proxy username.</param>
         /// <param name="proxyPassword">The proxy password.</param>
         /// <param name="keyFiles">The key files.</param>
-        public PrivateKeyConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, string proxyPassword, params PrivateKeyFile[] keyFiles)
+        public PrivateKeyConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, string proxyPassword, params IPrivateKeySource[] keyFiles)
             : base(host, port, username, proxyType, proxyHost, proxyPort, proxyUsername, proxyPassword, new PrivateKeyAuthenticationMethod(username, keyFiles))
         {
-            KeyFiles = new Collection<PrivateKeyFile>(keyFiles);
+            KeyFiles = new Collection<IPrivateKeySource>(keyFiles);
         }
 
         #region IDisposable Members
@@ -194,4 +194,4 @@ namespace Renci.SshNet
 
         #endregion
     }
-}
+}

+ 13 - 5
src/Renci.SshNet/PrivateKeyFile.cs

@@ -63,7 +63,7 @@ namespace Renci.SshNet
     /// </list>
     /// </para>
     /// </remarks>
-    public class PrivateKeyFile : IDisposable
+    public class PrivateKeyFile : IPrivateKeySource, IDisposable
     {
         private static readonly Regex PrivateKeyRegex = new Regex(@"^-+ *BEGIN (?<keyName>\w+( \w+)*) PRIVATE KEY *-+\r?\n((Proc-Type: 4,ENCRYPTED\r?\nDEK-Info: (?<cipherName>[A-Z0-9-]+),(?<salt>[A-F0-9]+)\r?\n\r?\n)|(Comment: ""?[^\r\n]*""?\r?\n))?(?<data>([a-zA-Z0-9/+=]{1,80}\r?\n)+)-+ *END \k<keyName> PRIVATE KEY *-+",
 #if FEATURE_REGEX_COMPILE
@@ -79,6 +79,15 @@ namespace Renci.SshNet
         /// </summary>
         public HostAlgorithm HostKey { get; private set; }
 
+        /// <summary>
+        /// Initializes a new instance of the <see cref="PrivateKeyFile"/> class.
+        /// </summary>
+        /// <param name="key">The key.</param>
+        public PrivateKeyFile(Key key)
+        {
+            HostKey = new KeyHostAlgorithm(key.ToString(), key);
+        }
+
         /// <summary>
         /// Initializes a new instance of the <see cref="PrivateKeyFile"/> class.
         /// </summary>
@@ -262,7 +271,7 @@ namespace Renci.SshNet
 
                     if (decryptedLength > blobSize - 4)
                         throw new SshException("Invalid passphrase.");
-                    
+
                     if (keyType == "if-modn{sign{rsa-pkcs1-sha1},encrypt{rsa-pkcs1v2-oaep}}")
                     {
                         var exponent = reader.ReadBigIntWithBits();//e
@@ -515,8 +524,7 @@ namespace Renci.SshNet
                     throw new SshException("OpenSSH key type '" + keyType + "' is not supported.");
             }
 
-            //comment, we don't need this but we could log it, not sure if necessary
-            var comment = privateKeyReader.ReadString(Encoding.UTF8);
+            parsedKey.Comment = privateKeyReader.ReadString(Encoding.UTF8);
 
             //The list of privatekey/comment pairs is padded with the bytes 1, 2, 3, ...
             //until the total length is a multiple of the cipher block size.
@@ -642,4 +650,4 @@ namespace Renci.SshNet
             }
         }
     }
-}
+}

+ 3 - 3
src/Renci.SshNet/ScpClient.cs

@@ -142,7 +142,7 @@ namespace Renci.SshNet
         /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, -or- <paramref name="username"/> is <c>null</c> or contains only whitespace characters.</exception>
         /// <exception cref="ArgumentOutOfRangeException"><paramref name="port"/> is not within <see cref="IPEndPoint.MinPort"/> and <see cref="IPEndPoint.MaxPort"/>.</exception>
         [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "Disposed in Dispose(bool) method.")]
-        public ScpClient(string host, int port, string username, params PrivateKeyFile[] keyFiles)
+        public ScpClient(string host, int port, string username, params IPrivateKeySource[] keyFiles)
             : this(new PrivateKeyConnectionInfo(host, port, username, keyFiles), true)
         {
         }
@@ -155,7 +155,7 @@ namespace Renci.SshNet
         /// <param name="keyFiles">Authentication private key file(s) .</param>
         /// <exception cref="ArgumentNullException"><paramref name="keyFiles"/> is <c>null</c>.</exception>
         /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, -or- <paramref name="username"/> is <c>null</c> or contains only whitespace characters.</exception>
-        public ScpClient(string host, string username, params PrivateKeyFile[] keyFiles)
+        public ScpClient(string host, string username, params IPrivateKeySource[] keyFiles)
             : this(host, ConnectionInfo.DefaultPort, username, keyFiles)
         {
         }
@@ -466,4 +466,4 @@ namespace Renci.SshNet
             throw new SshException("Secure copy execution request was rejected by the server. Please consult the server logs.");
         }
     }
-}
+}

+ 9 - 0
src/Renci.SshNet/Security/Cryptography/ED25519Key.cs

@@ -99,6 +99,15 @@ namespace Renci.SshNet.Security
         {
         }
 
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ED25519Key"/> class.
+        /// </summary>
+        /// <param name="pk">pk data.</param>
+        public ED25519Key(byte[] pk)
+        {
+            publicKey = pk.TrimLeadingZeros().Pad(Ed25519.PublicKeySizeInBytes);
+        }
+
         /// <summary>
         /// Initializes a new instance of the <see cref="ED25519Key"/> class.
         /// </summary>

+ 6 - 6
src/Renci.SshNet/Security/Cryptography/EcdsaDigitalSignature.cs

@@ -39,12 +39,12 @@ namespace Renci.SshNet.Security.Cryptography
             // for 521 sig_size is 132
             var sig_size = _key.KeyLength == 521 ? 132 : _key.KeyLength / 4;
             var ssh_data = new SshDataSignature(signature, sig_size);
-#if NETSTANDARD2_0
-            return _key.Ecdsa.VerifyData(input, ssh_data.Signature, _key.HashAlgorithm);
-#else
+#if NETFRAMEWORK
             var ecdsa = (ECDsaCng)_key.Ecdsa;
             ecdsa.HashAlgorithm = _key.HashAlgorithm;
             return ecdsa.VerifyData(input, ssh_data.Signature);
+#else
+            return _key.Ecdsa.VerifyData(input, ssh_data.Signature, _key.HashAlgorithm);
 #endif
         }
 
@@ -57,12 +57,12 @@ namespace Renci.SshNet.Security.Cryptography
         /// </returns>
         public override byte[] Sign(byte[] input)
         {
-#if NETSTANDARD2_0
-            var signed = _key.Ecdsa.SignData(input, _key.HashAlgorithm);
-#else
+#if NETFRAMEWORK
             var ecdsa = (ECDsaCng)_key.Ecdsa;
             ecdsa.HashAlgorithm = _key.HashAlgorithm;
             var signed = ecdsa.SignData(input);
+#else
+            var signed = _key.Ecdsa.SignData(input, _key.HashAlgorithm);
 #endif
             var ssh_data = new SshDataSignature(signed.Length);
             ssh_data.Signature = signed;

+ 71 - 60
src/Renci.SshNet/Security/Cryptography/EcdsaKey.cs

@@ -18,7 +18,7 @@ namespace Renci.SshNet.Security
         internal const string ECDSA_P384_OID_VALUE = "1.3.132.0.34"; // Also called nistP384 or secP384r1
         internal const string ECDSA_P521_OID_VALUE = "1.3.132.0.35"; // Also called nistP521or secP521r1
 
-#if !NETSTANDARD2_0
+#if NETFRAMEWORK
         internal enum KeyBlobMagicNumber : int
         {
             BCRYPT_ECDSA_PUBLIC_P256_MAGIC = 0x31534345,
@@ -57,45 +57,45 @@ namespace Renci.SshNet.Security
             return string.Format("ecdsa-sha2-nistp{0}", KeyLength);
         }
 
-#if NETSTANDARD2_0
+#if NETFRAMEWORK
         /// <summary>
         /// Gets the HashAlgorithm to use
         /// </summary>
-        public HashAlgorithmName HashAlgorithm
+        public CngAlgorithm HashAlgorithm
         {
             get
             {
-                switch (KeyLength)
+                switch (Ecdsa.KeySize)
                 {
                     case 256:
-                        return HashAlgorithmName.SHA256;
+                        return CngAlgorithm.Sha256;
                     case 384:
-                        return HashAlgorithmName.SHA384;
+                        return CngAlgorithm.Sha384;
                     case 521:
-                        return HashAlgorithmName.SHA512;
+                        return CngAlgorithm.Sha512;
+                    default:
+                        throw new SshException("Unknown KeySize: " + Ecdsa.KeySize);
                 }
-                return HashAlgorithmName.SHA256;
             }
         }
 #else
         /// <summary>
         /// Gets the HashAlgorithm to use
         /// </summary>
-        public CngAlgorithm HashAlgorithm
+        public HashAlgorithmName HashAlgorithm
         {
             get
             {
-                switch (Ecdsa.KeySize)
+                switch (KeyLength)
                 {
                     case 256:
-                        return CngAlgorithm.Sha256;
+                        return HashAlgorithmName.SHA256;
                     case 384:
-                        return CngAlgorithm.Sha384;
+                        return HashAlgorithmName.SHA384;
                     case 521:
-                        return CngAlgorithm.Sha512;
-                    default:
-                        throw new SshException("Unknown KeySize: " + Ecdsa.KeySize);
+                        return HashAlgorithmName.SHA512;
                 }
+                return HashAlgorithmName.SHA256;
             }
         }
 #endif
@@ -144,28 +144,7 @@ namespace Renci.SshNet.Security
                 byte[] curve;
                 byte[] qx;
                 byte[] qy;
-#if NETSTANDARD2_0
-                var parameter = Ecdsa.ExportParameters(false);
-                qx = parameter.Q.X;
-                qy = parameter.Q.Y;
-                switch (parameter.Curve.Oid.FriendlyName)
-                {
-                    case "ECDSA_P256":
-                    case "nistP256":
-                        curve = Encoding.ASCII.GetBytes("nistp256");
-                        break;
-                    case "ECDSA_P384":
-                    case "nistP384":
-                        curve = Encoding.ASCII.GetBytes("nistp384");
-                        break;
-                    case "ECDSA_P521":
-                    case "nistP521":
-                        curve = Encoding.ASCII.GetBytes("nistp521");
-                        break;
-                    default:
-                        throw new SshException("Unexpected Curve Name: " + parameter.Curve.Oid.FriendlyName);
-                }
-#else
+#if NETFRAMEWORK
                 var blob = key.Export(CngKeyBlobFormat.EccPublicBlob);
 
                 KeyBlobMagicNumber magic;
@@ -191,6 +170,27 @@ namespace Renci.SshNet.Security
                     default:
                         throw new SshException("Unexpected Curve Magic: " + magic);
                 }
+#else
+                var parameter = Ecdsa.ExportParameters(false);
+                qx = parameter.Q.X;
+                qy = parameter.Q.Y;
+                switch (parameter.Curve.Oid.FriendlyName)
+                {
+                    case "ECDSA_P256":
+                    case "nistP256":
+                        curve = Encoding.ASCII.GetBytes("nistp256");
+                        break;
+                    case "ECDSA_P384":
+                    case "nistP384":
+                        curve = Encoding.ASCII.GetBytes("nistp384");
+                        break;
+                    case "ECDSA_P521":
+                    case "nistP521":
+                        curve = Encoding.ASCII.GetBytes("nistp521");
+                        break;
+                    default:
+                        throw new SshException("Unexpected Curve Name: " + parameter.Curve.Oid.FriendlyName);
+                }
 #endif
                 // Make ECPoint from x and y
                 // Prepend 04 (uncompressed format) + qx-bytes + qy-bytes
@@ -212,6 +212,11 @@ namespace Renci.SshNet.Security
             }
         }
 
+        /// <summary>
+        /// Gets the PrivateKey Bytes
+        /// </summary>
+        public byte[] PrivateKey { get; private set; }
+
         /// <summary>
         /// Gets ECDsa Object
         /// </summary>
@@ -278,29 +283,7 @@ namespace Renci.SshNet.Security
 
         private void Import(string curve_oid, byte[] publickey, byte[] privatekey)
         {
-#if NETSTANDARD2_0
-            var curve = ECCurve.CreateFromValue(curve_oid);
-            var parameter = new ECParameters
-            {
-                Curve = curve
-            };
-
-            // ECPoint as BigInteger(2)
-            var cord_size = (publickey.Length - 1) / 2;
-            var qx = new byte[cord_size];
-            Buffer.BlockCopy(publickey, 1, qx, 0, qx.Length);
-
-            var qy = new byte[cord_size];
-            Buffer.BlockCopy(publickey, cord_size + 1, qy, 0, qy.Length);
-
-            parameter.Q.X = qx;
-            parameter.Q.Y = qy;
-
-            if (privatekey != null)
-                parameter.D = privatekey.TrimLeadingZeros().Pad(cord_size);
-
-            Ecdsa = ECDsa.Create(parameter);
-#else
+#if NETFRAMEWORK
             var curve_magic = KeyBlobMagicNumber.BCRYPT_ECDH_PRIVATE_GENERIC_MAGIC;
             switch (GetCurveName(curve_oid))
             {
@@ -335,7 +318,10 @@ namespace Renci.SshNet.Security
             Buffer.BlockCopy(publickey, cord_size + 1, qy, 0, qy.Length);
 
             if (privatekey != null)
+            {
                 privatekey = privatekey.Pad(cord_size);
+                PrivateKey = privatekey;
+            }
 
             int headerSize = Marshal.SizeOf(typeof(BCRYPT_ECCKEY_BLOB));
             int blobSize = headerSize + qx.Length + qy.Length;
@@ -355,6 +341,31 @@ namespace Renci.SshNet.Security
             key = CngKey.Import(blob, privatekey == null ? CngKeyBlobFormat.EccPublicBlob : CngKeyBlobFormat.EccPrivateBlob);
 
             Ecdsa = new ECDsaCng(key);
+#else
+            var curve = ECCurve.CreateFromValue(curve_oid);
+            var parameter = new ECParameters
+            {
+                Curve = curve
+            };
+
+            // ECPoint as BigInteger(2)
+            var cord_size = (publickey.Length - 1) / 2;
+            var qx = new byte[cord_size];
+            Buffer.BlockCopy(publickey, 1, qx, 0, qx.Length);
+
+            var qy = new byte[cord_size];
+            Buffer.BlockCopy(publickey, cord_size + 1, qy, 0, qy.Length);
+
+            parameter.Q.X = qx;
+            parameter.Q.Y = qy;
+
+            if (privatekey != null)
+            {
+                parameter.D = privatekey.TrimLeadingZeros().Pad(cord_size);
+                PrivateKey = parameter.D;
+            }
+
+            Ecdsa = ECDsa.Create(parameter);
 #endif
         }
 

+ 5 - 0
src/Renci.SshNet/Security/Cryptography/Key.cs

@@ -36,6 +36,11 @@ namespace Renci.SshNet.Security
         /// </value>
         public abstract int KeyLength { get; }
 
+        /// <summary>
+        /// Gets the Key Comment
+        /// </summary>
+        public string Comment { get; set; }
+
         /// <summary>
         /// Initializes a new instance of the <see cref="Key"/> class.
         /// </summary>

+ 6 - 6
src/Renci.SshNet/SftpClient.cs

@@ -208,7 +208,7 @@ namespace Renci.SshNet
         /// <exception cref="ArgumentException"><paramref name="host"/> is invalid. <para>-or-</para> <paramref name="username"/> is nu<b>null</b>ll or contains only whitespace characters.</exception>
         /// <exception cref="ArgumentOutOfRangeException"><paramref name="port"/> is not within <see cref="IPEndPoint.MinPort"/> and <see cref="IPEndPoint.MaxPort"/>.</exception>
         [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "Disposed in Dispose(bool) method.")]
-        public SftpClient(string host, int port, string username, params PrivateKeyFile[] keyFiles)
+        public SftpClient(string host, int port, string username, params IPrivateKeySource[] keyFiles)
             : this(new PrivateKeyConnectionInfo(host, port, username, keyFiles), true)
         {
         }
@@ -221,7 +221,7 @@ namespace Renci.SshNet
         /// <param name="keyFiles">Authentication private key file(s) .</param>
         /// <exception cref="ArgumentNullException"><paramref name="keyFiles"/> is <b>null</b>.</exception>
         /// <exception cref="ArgumentException"><paramref name="host"/> is invalid. <para>-or-</para> <paramref name="username"/> is <b>null</b> or contains only whitespace characters.</exception>
-        public SftpClient(string host, string username, params PrivateKeyFile[] keyFiles)
+        public SftpClient(string host, string username, params IPrivateKeySource[] keyFiles)
             : this(host, ConnectionInfo.DefaultPort, username, keyFiles)
         {
         }
@@ -712,13 +712,13 @@ namespace Renci.SshNet
             // using SSH_FXP_REALPATH is not an alternative as the SFTP specification has not always
             // been clear on how the server should respond when the specified path is not present on
             // the server:
-            // 
+            //
             // SSH 1 to 4:
             // No mention of how the server should respond if the path is not present on the server.
             //
             // SSH 5:
             // The server SHOULD fail the request if the path is not present on the server.
-            // 
+            //
             // SSH 6:
             // Draft 06: The server SHOULD fail the request if the path is not present on the server.
             // Draft 07 to 13: The server MUST NOT fail the request if the path does not exist.
@@ -747,7 +747,7 @@ namespace Renci.SshNet
         /// <exception cref="ArgumentException"><paramref name="path" /> is <b>null</b> or contains only whitespace characters.</exception>
         /// <exception cref="SshConnectionException">Client is not connected.</exception>
         /// <exception cref="SftpPermissionDeniedException">Permission to perform the operation was denied by the remote host. <para>-or-</para> A SSH command was denied by the server.</exception>
-        /// <exception cref="SftpPathNotFoundException"><paramref name="path"/> was not found on the remote host.</exception>/// 
+        /// <exception cref="SftpPathNotFoundException"><paramref name="path"/> was not found on the remote host.</exception>///
         /// <exception cref="SshException">A SSH error where <see cref="Exception.Message" /> is the message from the remote host.</exception>
         /// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
         /// <remarks>
@@ -2400,4 +2400,4 @@ namespace Renci.SshNet
             }
         }
     }
-}
+}

+ 3 - 3
src/Renci.SshNet/SshClient.cs

@@ -104,7 +104,7 @@ namespace Renci.SshNet
         /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, -or- <paramref name="username"/> is <c>null</c> or contains only whitespace characters.</exception>
         /// <exception cref="ArgumentOutOfRangeException"><paramref name="port"/> is not within <see cref="IPEndPoint.MinPort"/> and <see cref="IPEndPoint.MaxPort"/>.</exception>
         [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "Disposed in Dispose(bool) method.")]
-        public SshClient(string host, int port, string username, params PrivateKeyFile[] keyFiles)
+        public SshClient(string host, int port, string username, params IPrivateKeySource[] keyFiles)
             : this(new PrivateKeyConnectionInfo(host, port, username, keyFiles), true)
         {
         }
@@ -121,7 +121,7 @@ namespace Renci.SshNet
         /// </example>
         /// <exception cref="ArgumentNullException"><paramref name="keyFiles"/> is <c>null</c>.</exception>
         /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, -or- <paramref name="username"/> is <c>null</c> or contains only whitespace characters.</exception>
-        public SshClient(string host, string username, params PrivateKeyFile[] keyFiles)
+        public SshClient(string host, string username, params IPrivateKeySource[] keyFiles)
             : this(host, ConnectionInfo.DefaultPort, username, keyFiles)
         {
         }
@@ -507,4 +507,4 @@ namespace Renci.SshNet
                 throw new SshConnectionException("Client not connected.");
         }
     }
-}
+}